-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(frontend): add sol wallet worker (#4037)
# Motivation We integrate SOL with the same worker approach as btc and ic. # Changes Implement worker and scheduler. # Tests Unit tests provided, same as for btc worker. Additionally: https://github.com/user-attachments/assets/7eca0ee7-dc69-4bd5-8597-c2aafc1b6c33 # Note During the development there were some error messages for mainnet (even though we use alchemy now). ![image](https://github.com/user-attachments/assets/e60af10e-211b-45ee-ad0e-e6e5367d71ef) ![image](https://github.com/user-attachments/assets/f5f26f22-9a8d-4ab5-a1c3-2c3362526fcc) Not reproducable at the moment. Will address this with @StefanBerger-DFINITY to check if the alchemy dashboard shows something. --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
- Loading branch information
1 parent
ce3d851
commit a3eda58
Showing
7 changed files
with
412 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
116 changes: 116 additions & 0 deletions
116
src/frontend/src/sol/schedulers/sol-wallet.scheduler.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import { WALLET_TIMER_INTERVAL_MILLIS } from '$lib/constants/app.constants'; | ||
import { SchedulerTimer, type Scheduler, type SchedulerJobData } from '$lib/schedulers/scheduler'; | ||
import type { SolAddress } from '$lib/types/address'; | ||
import type { | ||
PostMessageDataRequestSol, | ||
PostMessageDataResponseError | ||
} from '$lib/types/post-message'; | ||
import type { CertifiedData } from '$lib/types/store'; | ||
import { loadSolLamportsBalance } from '$sol/api/solana.api'; | ||
import type { SolanaNetworkType } from '$sol/types/network'; | ||
import type { SolPostMessageDataResponseWallet } from '$sol/types/sol-post-message'; | ||
import { assertNonNullish, isNullish } from '@dfinity/utils'; | ||
|
||
interface LoadSolWalletParams { | ||
solanaNetwork: SolanaNetworkType; | ||
address: SolAddress; | ||
} | ||
|
||
interface SolWalletStore { | ||
balance: CertifiedData<bigint | null> | undefined; | ||
} | ||
|
||
interface SolWalletData { | ||
balance: CertifiedData<bigint | null>; | ||
} | ||
|
||
export class SolWalletScheduler implements Scheduler<PostMessageDataRequestSol> { | ||
private timer = new SchedulerTimer('syncSolWalletStatus'); | ||
|
||
private store: SolWalletStore = { | ||
balance: undefined | ||
}; | ||
|
||
stop() { | ||
this.timer.stop(); | ||
} | ||
|
||
async start(data: PostMessageDataRequestSol | undefined) { | ||
await this.timer.start<PostMessageDataRequestSol>({ | ||
interval: WALLET_TIMER_INTERVAL_MILLIS, | ||
job: this.syncWallet, | ||
data | ||
}); | ||
} | ||
|
||
async trigger(data: PostMessageDataRequestSol | undefined) { | ||
await this.timer.trigger<PostMessageDataRequestSol>({ | ||
job: this.syncWallet, | ||
data | ||
}); | ||
} | ||
|
||
private loadBalance = async ({ | ||
address, | ||
solanaNetwork | ||
}: LoadSolWalletParams): Promise<CertifiedData<bigint | null>> => ({ | ||
data: await loadSolLamportsBalance({ network: solanaNetwork, address }), | ||
certified: false | ||
}); | ||
|
||
private syncWallet = async ({ data }: SchedulerJobData<PostMessageDataRequestSol>) => { | ||
assertNonNullish(data, 'No data provided to get Solana balance.'); | ||
|
||
try { | ||
const balance = await this.loadBalance({ | ||
address: data.address.data, | ||
solanaNetwork: data.solanaNetwork | ||
}); | ||
|
||
//todo implement loading transactions | ||
|
||
this.syncWalletData({ response: { balance } }); | ||
} catch (error: unknown) { | ||
this.postMessageWalletError({ error }); | ||
} | ||
}; | ||
|
||
private syncWalletData = ({ response: { balance } }: { response: SolWalletData }) => { | ||
if (!this.store.balance?.certified && balance.certified) { | ||
throw new Error('Balance certification status cannot change from uncertified to certified'); | ||
} | ||
|
||
const newBalance = isNullish(this.store.balance) || this.store.balance.data !== balance.data; | ||
|
||
if (!newBalance) { | ||
return; | ||
} | ||
|
||
this.store = { | ||
...this.store, | ||
balance | ||
}; | ||
|
||
this.postMessageWallet({ | ||
wallet: { | ||
balance | ||
} | ||
}); | ||
}; | ||
|
||
private postMessageWallet(data: SolPostMessageDataResponseWallet) { | ||
this.timer.postMsg<SolPostMessageDataResponseWallet>({ | ||
msg: 'syncSolWallet', | ||
data | ||
}); | ||
} | ||
|
||
protected postMessageWalletError({ error }: { error: unknown }) { | ||
this.timer.postMsg<PostMessageDataResponseError>({ | ||
msg: 'syncSolWalletError', | ||
data: { | ||
error | ||
} | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
95 changes: 95 additions & 0 deletions
95
src/frontend/src/sol/services/worker.sol-wallet.services.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import { | ||
solAddressDevnetStore, | ||
solAddressLocalnetStore, | ||
solAddressMainnetStore, | ||
solAddressTestnetStore | ||
} from '$lib/stores/address.store'; | ||
import type { WalletWorker } from '$lib/types/listener'; | ||
import type { | ||
PostMessage, | ||
PostMessageDataRequestSol, | ||
PostMessageDataResponseError | ||
} from '$lib/types/post-message'; | ||
import type { Token } from '$lib/types/token'; | ||
import { | ||
isNetworkIdSOLDevnet, | ||
isNetworkIdSOLLocal, | ||
isNetworkIdSOLTestnet | ||
} from '$lib/utils/network.utils'; | ||
import { syncWallet, syncWalletError } from '$sol/services/sol-listener.services'; | ||
import type { SolPostMessageDataResponseWallet } from '$sol/types/sol-post-message'; | ||
import { mapNetworkIdToNetwork } from '$sol/utils/network.utils'; | ||
import { assertNonNullish } from '@dfinity/utils'; | ||
import { get } from 'svelte/store'; | ||
|
||
export const initSolWalletWorker = async ({ token }: { token: Token }): Promise<WalletWorker> => { | ||
const { | ||
id: tokenId, | ||
network: { id: networkId } | ||
} = token; | ||
|
||
const WalletWorker = await import('$sol/workers/sol-wallet.worker?worker'); | ||
const worker: Worker = new WalletWorker.default(); | ||
|
||
const isTestnetNetwork = isNetworkIdSOLTestnet(networkId); | ||
const isDevnetNetwork = isNetworkIdSOLDevnet(networkId); | ||
const isLocalNetwork = isNetworkIdSOLLocal(networkId); | ||
|
||
worker.onmessage = ({ data }: MessageEvent<PostMessage<SolPostMessageDataResponseWallet>>) => { | ||
const { msg } = data; | ||
|
||
switch (msg) { | ||
case 'syncSolWallet': | ||
syncWallet({ | ||
tokenId, | ||
data: data.data as SolPostMessageDataResponseWallet | ||
}); | ||
return; | ||
|
||
case 'syncSolWalletError': | ||
syncWalletError({ | ||
tokenId, | ||
error: (data.data as PostMessageDataResponseError).error, | ||
hideToast: isTestnetNetwork || isDevnetNetwork || isLocalNetwork | ||
}); | ||
return; | ||
} | ||
}; | ||
|
||
// TODO: stop/start the worker on address change (same as for worker.btc-wallet.services.ts) | ||
const address = get( | ||
isTestnetNetwork | ||
? solAddressTestnetStore | ||
: isDevnetNetwork | ||
? solAddressDevnetStore | ||
: isLocalNetwork | ||
? solAddressLocalnetStore | ||
: solAddressMainnetStore | ||
); | ||
assertNonNullish(address, 'No Solana address provided to start Solana wallet worker.'); | ||
|
||
const network = mapNetworkIdToNetwork(token.network.id); | ||
assertNonNullish(network, 'No Solana network provided to start Solana wallet worker.'); | ||
|
||
const data: PostMessageDataRequestSol = { address, solanaNetwork: network }; | ||
|
||
return { | ||
start: () => { | ||
worker.postMessage({ | ||
msg: 'startSolWalletTimer', | ||
data | ||
}); | ||
}, | ||
stop: () => { | ||
worker.postMessage({ | ||
msg: 'stopSolWalletTimer' | ||
}); | ||
}, | ||
trigger: () => { | ||
worker.postMessage({ | ||
msg: 'triggerSolWalletTimer', | ||
data | ||
}); | ||
} | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import type { PostMessage, PostMessageDataRequestSol } from '$lib/types/post-message'; | ||
import { SolWalletScheduler } from '$sol/schedulers/sol-wallet.scheduler'; | ||
|
||
const scheduler: SolWalletScheduler = new SolWalletScheduler(); | ||
|
||
onmessage = async ({ data: dataMsg }: MessageEvent<PostMessage<PostMessageDataRequestSol>>) => { | ||
const { msg, data } = dataMsg; | ||
|
||
switch (msg) { | ||
case 'stopSolWalletTimer': | ||
scheduler.stop(); | ||
return; | ||
case 'startSolWalletTimer': | ||
await scheduler.start(data); | ||
return; | ||
case 'triggerSolWalletTimer': | ||
await scheduler.trigger(data); | ||
return; | ||
} | ||
}; |
Oops, something went wrong.