Skip to content

Commit

Permalink
add settings, blockfrost support. fix extra rerenders caused by lucid…
Browse files Browse the repository at this point in the history
… provider
  • Loading branch information
Piefayth committed Mar 31, 2024
1 parent db3c8af commit b38e9b5
Show file tree
Hide file tree
Showing 13 changed files with 546 additions and 105 deletions.
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { FileManager } from './panels/editor/FileManager'
import Tooltip from './components/Tooltip'
import { ContextMenu } from './components/ContextMenu'
import { ManagementTabs } from './panels/management/ManagementTabs'
import { Settings } from './panels/settings/Settings'

function App() {
const aiken = useAiken()
Expand All @@ -28,6 +29,7 @@ function App() {
return (() => {
return (
<MonacoContext.Provider value={monaco}>
<Settings />
<Tooltip />
<ContextMenu />
<div className={`main-layout-container ${loadingVisibilityClass}`}>
Expand Down
5 changes: 3 additions & 2 deletions src/app/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import contextMenuReducer from '../features/contextMenu/contextMenuSlice'
import managementReducer from '../features/management/managementSlice'
import lucidReducer from '../features/management/lucidSlice'
import transactReducer from '../features/management/transactSlice'

import settingsReducer from '../features/settings/settingsSlice'

export const store = configureStore({
reducer: {
Expand All @@ -16,7 +16,8 @@ export const store = configureStore({
contextMenu: contextMenuReducer,
management: managementReducer,
lucid: lucidReducer,
transact: transactReducer
transact: transactReducer,
settings: settingsReducer
},
})

Expand Down
120 changes: 66 additions & 54 deletions src/components/LucidProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,87 +1,99 @@
import { useEffect, useState, createContext, useContext, useRef } from 'react'
import { useEffect, useState, createContext, useContext, useRef, useMemo } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Emulator, Lucid, generateSeedPhrase } from 'lucid-cardano'
import { Blockfrost, Emulator, Lucid, generateSeedPhrase } from 'lucid-cardano'
import { RootState } from '../app/store'
import { Wallet, addWallet, removeWallet } from '../features/management/managementSlice'
import { addWallet } from '../features/management/managementSlice'

interface LucidContextState {
lucid: Lucid | null
isLucidLoading: boolean
}

const LucidContext = createContext<LucidContextState>({
lucid: null,
isLucidLoading: false,
})


const genesisSeed = generateSeedPhrase()

export const LucidProvider = ({ children }: { children: React.ReactNode }) => {
const [lucid, setLucid] = useState<Lucid | null>(null)
const [isLucidLoading, setIsLucidLoading] = useState(false)
const [genesisWalletOrUndefined, setGenesisWallet] = useState<Wallet | undefined>(undefined)
const lucidConfig = useSelector((state: RootState) => state.lucid)
const wallets = useSelector((state: RootState) => state.management.wallets)
const [genesisAddress, setGenesisAddress] = useState('')
const settings = useSelector((state: RootState) => state.settings)
const dispatch = useDispatch()

const areSideEffectsSafe = useRef(true) // is this really the best way to prevent duplicate effect invocations?

const lucidContext = useMemo(() => {
return { lucid, isLucidLoading }
}, [lucid, isLucidLoading])

useEffect(() => {
let genesisWallet = genesisWalletOrUndefined
setIsLucidLoading(true)

const initializeLucid = async () => {
const network = lucidConfig.network === "Emulator" ? "Custom" : lucidConfig.network
if (lucid && lucid.network === network) {
// eventually we need to handle provider changes here
return
}

let lucidInstance = await Lucid.new(undefined, network)

let genesisSeed: string | undefined = undefined
let genesisWalletAddress = ''
if (lucidConfig.network === "Emulator" && wallets.length === 0) {
// need to have SOME wallet to provide the emulator provider
genesisSeed = generateSeedPhrase()
Lucid.new(undefined, 'Custom')
.then(lucidInstance => {
lucidInstance.selectWalletFromSeed(genesisSeed)
genesisWalletAddress = await lucidInstance.wallet.address()
} else if (wallets.length > 0) {
lucidInstance.selectWalletFromSeed(wallets[0].seed)
}
return lucidInstance.wallet.address()
})
.then((address) => {
setGenesisAddress(address)
})
}, [])

useEffect(() => {
if (!genesisAddress && settings.providerConfig.kind === "emulator") {
return
}

const initializeLucid = async () => {
setIsLucidLoading(true)

if (lucidConfig.network === "Emulator") {
if (areSideEffectsSafe.current) {
areSideEffectsSafe.current = false
genesisWallet = {
address: genesisWalletAddress,
seed: genesisSeed!!
const network = settings.providerConfig.kind === "emulator" ? "Custom" : settings.network

let lucidInstance
if (settings.providerConfig.kind === "emulator") {
const emulator = new Emulator([{
address: genesisAddress,
assets: {
lovelace: 20000000000n
}
}])

dispatch(addWallet(genesisWallet))
setGenesisWallet(genesisWallet)

const emulator = new Emulator([{
address: genesisWallet?.address || '',
assets: {
lovelace: 20000000000n
}
}])
lucidInstance = await Lucid.new(emulator, network)

setLucid(lucidInstance)
setIsLucidLoading(false)
} else {
areSideEffectsSafe.current = true
}
lucidInstance = await Lucid.new(emulator, network)
} else if (settings.providerConfig.kind === 'blockfrost') {
lucidInstance = await Lucid.new(new Blockfrost(settings.providerConfig.url, settings.providerConfig.apiKey))
} else {
throw Error('not implemented')
}

if (!lucidInstance) {
throw Error ('could not create lucid')
}

return lucidInstance
}

initializeLucid()

.then(instance => {
if (settings.providerConfig.kind === 'emulator') {
dispatch(addWallet({
address: genesisAddress,
seed: genesisSeed
}))
}
setLucid(instance!!)
setIsLucidLoading(false)
})
.catch(err => {
setIsLucidLoading(false)
console.log('Unexpected problem with lucid...')
console.error(err)
})
return
}, [lucidConfig])
}, [genesisAddress, settings.network, settings.providerConfig])

return (
<LucidContext.Provider value={{ lucid, isLucidLoading }}>
<LucidContext.Provider value={lucidContext}>
{children}
</LucidContext.Provider>
)
Expand Down
8 changes: 7 additions & 1 deletion src/features/management/managementSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,16 @@ const managementSlice = createSlice({
state.selectedTabIndex = action.payload
},
addWallet(state, action: PayloadAction<Wallet>) {
state.wallets.push(action.payload)
if (!state.wallets.find(wallet => wallet.address === action.payload.address)) {
state.wallets.push(action.payload)
}
},
removeWallet(state, action: PayloadAction<string>) {
state.wallets.filter(wallet => wallet.address === action.payload)
},
clearWallets(state) {
state.wallets = []
},
addContract(state, action: PayloadAction<ContractInput>) {
let version = 0
for (let contract of state.contracts) {
Expand Down Expand Up @@ -89,6 +94,7 @@ export const {
selectTab,
addWallet,
removeWallet,
clearWallets,
addContract,
removeContract,
clearAddContractError,
Expand Down
112 changes: 112 additions & 0 deletions src/features/settings/settingsSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { Network } from 'lucid-cardano'

export type ProviderKind = 'blockfrost' | 'kupmios' | 'emulator'

interface BlockfrostProviderConfig {
kind: 'blockfrost'
apiKey: string
url: string
}

interface KupmiosProviderConfig {
kind: 'kupmios'
kupoUrl: string
ogmiosUrl: string
}

interface EmulatorConfig {
kind: 'emulator'
}

type ProviderConfig = BlockfrostProviderConfig | KupmiosProviderConfig | EmulatorConfig

interface SettingsFormState {
providerKind: ProviderKind
network: Network | 'Emulator'
blockfrost: BlockfrostProviderConfig
kupmios: KupmiosProviderConfig
}

interface SettingsState {
open: boolean
network: Network
providerConfig: ProviderConfig,
form: SettingsFormState
}

const initialState: SettingsState = {
open: false,
network: 'Custom',
providerConfig: {
kind: 'emulator'
},
form: {
providerKind: 'emulator',
network: 'Emulator',
blockfrost: {
kind: 'blockfrost',
apiKey: '', // dont check this in please
url: 'https://cardano-mainnet.blockfrost.io/api/v0'
},
kupmios: {
kind: 'kupmios',
kupoUrl: '',
ogmiosUrl: ''
},
}
}

const settingsSlice = createSlice({
name: 'tooltip',
initialState,
reducers: {
toggleSettings: (state) => {
state.open = !state.open
},
saveUpdatedSettings: (state) => {
state.open = false
state.network = state.form.network === 'Emulator' ? 'Custom' : state.form.network

if (state.form.providerKind === 'blockfrost') {
state.providerConfig = state.form.blockfrost
} else if (state.form.providerKind === 'kupmios') {
state.providerConfig = state.form.kupmios
} else if (state.form.providerKind === 'emulator') {
state.providerConfig = { kind: 'emulator' }
} else {
throw Error('not implemented')
}
},
setFormProviderKind: (state, action: PayloadAction<ProviderKind>) => {
state.form.providerKind = action.payload
},
setFormNetwork: (state, action: PayloadAction<Network | 'Emulator'>) => {
state.form.network = action.payload

if (state.form.network === 'Emulator' && state.form.providerKind !== 'emulator') {
state.form.providerKind = 'emulator'
}

if (state.form.network !== 'Emulator' && state.form.providerKind === 'emulator') {
state.form.providerKind = 'blockfrost'
}
},
setBlockfrostConfig: (state, action: PayloadAction<BlockfrostProviderConfig>) => {
state.form.blockfrost = action.payload
},
setKupmiosConfig: (state, action: PayloadAction<BlockfrostProviderConfig>) => {
state.form.blockfrost = action.payload
}
}
})

export const {
toggleSettings,
saveUpdatedSettings,
setFormProviderKind,
setBlockfrostConfig,
setKupmiosConfig,
setFormNetwork
} = settingsSlice.actions
export default settingsSlice.reducer
16 changes: 13 additions & 3 deletions src/panels/management/ManagementTopBar.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { useSelector } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import '../TopBar.css';
import { RootState } from '../../app/store';
import { toggleSettings } from '../../features/settings/settingsSlice';

function ManagementTopBar() {
const network = useSelector((state: RootState) => state.lucid.network)
const network = useSelector((state: RootState) => state.settings.network)
const dispatch = useDispatch()

const displayNetwork = network === 'Custom' ? 'Emulator' : network
return (
<div className='top-bar management-top-bar'>
<div className='top-bar-item'><strong>Network</strong>: {network}</div>
<div className='top-bar-item'><strong>Network</strong>: {displayNetwork}</div>
<div
className='top-bar-item'
onClick={() => dispatch(toggleSettings())}
><strong>Settings</strong>
</div>

{/* <div className='top-bar-item'><strong>Status</strong>: Not Connected</div> */}
</div>
)
Expand Down
2 changes: 0 additions & 2 deletions src/panels/management/transact/Submit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,6 @@ async function buildTransaction(lucid: Lucid, transactState: TransactState, file
try {
const redeemerJson = JSON.parse(redeemerFile.content)
redeemer = constructObject(redeemerJson)

console.log(redeemer)
} catch (e: any) {
if (e.message && e.message.includes('JSON.parse')) {
throw Error(`Invalid JSON in ${redeemerFile.name}`)
Expand Down
Loading

0 comments on commit b38e9b5

Please sign in to comment.