-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(frontend): add sol wallet worker #4037
Changes from all commits
ed78f4b
4b10789
97b495b
3dac81d
d5c6110
e9017b7
33c7ffd
49559fa
b2a5733
977e23c
a9e1e4d
e4871e1
8655b42
7aef276
816f789
936e8bb
8f6c1f9
88ebcf2
458726a
1aaaf05
1dbf131
4fbc4a4
3d47454
6e6fe73
b9a73b0
b1d5d8a
d9ac699
3e3bf34
360cbf0
e41a702
cdd9042
0b3cae6
bf91bec
93c3f2b
e696cf7
e1a7793
f68d290
baeeb48
73bd616
9125eba
8bc76a6
c16477a
535fb6e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) { | ||
peterpeterparker marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 | ||
} | ||
}); | ||
} | ||
} |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, we wanted to revisit the approach at some point (e.g. by avoiding requests (or launching wallets in this case) that we know in advance are not gonna succeed, rather than making a call + failing silently). |
||
}); | ||
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 | ||
}); | ||
} | ||
}; | ||
}; |
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; | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: while we do not have an eslint rule for it, maybe something we can setup (?), we generally use
// TODO:
. Maybe easier to retrieve if you use the same pattern?