Skip to content
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

Merged
merged 43 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
ed78f4b
feat(frontend): add sol dependency
loki344 Dec 19, 2024
4b10789
feat(frontend): implement sol api
loki344 Dec 19, 2024
97b495b
feat(frontend): clarify naming
loki344 Dec 19, 2024
3dac81d
Merge branch 'main' into feat(frontend)/add-sol-api-for-balance
loki344 Dec 19, 2024
d5c6110
🤖 Apply formatting changes
github-actions[bot] Dec 19, 2024
e9017b7
feat(frontend): rm todo
loki344 Dec 19, 2024
33c7ffd
Merge remote-tracking branch 'origin/feat(frontend)/add-sol-api-for-b…
loki344 Dec 19, 2024
49559fa
feat(frontend): adjust typing
loki344 Dec 19, 2024
b2a5733
feat(frontend): adjust typing
loki344 Dec 19, 2024
977e23c
feat(frontend): lint
loki344 Dec 19, 2024
a9e1e4d
feat(frontend): lint
loki344 Dec 19, 2024
e4871e1
feat(frontend): lint
loki344 Dec 19, 2024
8655b42
feat(frontend): lint
loki344 Dec 19, 2024
7aef276
Merge branch 'refs/heads/feat(frontend)/adjust-sol-typing' into feat(…
loki344 Dec 19, 2024
816f789
feat(frontend): add types for worker
loki344 Dec 19, 2024
936e8bb
🤖 Apply formatting changes
github-actions[bot] Dec 19, 2024
8f6c1f9
feat(frontend): implement sol-listener
loki344 Dec 19, 2024
88ebcf2
🤖 Apply formatting changes
github-actions[bot] Dec 19, 2024
458726a
Merge branch 'refs/heads/feat(frontend)/add-sol-api-for-balance' into…
loki344 Dec 19, 2024
1aaaf05
feat(frontend): implement sol-wallet.scheduler.ts
loki344 Dec 19, 2024
1dbf131
Merge branch 'refs/heads/feat(frontend)/add-sol-listener' into feat(f…
loki344 Dec 19, 2024
4fbc4a4
feat(frontend): implement worker for sol wallet, add unit tests
loki344 Dec 19, 2024
3d47454
feat(frontend): implement worker for sol wallet, add unit tests
loki344 Dec 19, 2024
6e6fe73
Merge remote-tracking branch 'origin/feat(frontend)/add-sol-worker' i…
loki344 Dec 19, 2024
b9a73b0
feat(frontend): rm import
loki344 Dec 19, 2024
b1d5d8a
🤖 Apply formatting changes
github-actions[bot] Dec 19, 2024
d9ac699
Merge remote-tracking branch 'origin/main' into feat(frontend)/add-so…
loki344 Dec 20, 2024
3e3bf34
style(frontend): rm unwanted change
loki344 Dec 20, 2024
360cbf0
style(frontend): fix check
loki344 Dec 20, 2024
e41a702
🤖 Apply formatting changes
github-actions[bot] Dec 20, 2024
cdd9042
add todo
loki344 Jan 2, 2025
0b3cae6
implement MR feedback
loki344 Jan 2, 2025
bf91bec
feat(frontend): rm loaders from mr
loki344 Jan 2, 2025
93c3f2b
🤖 Apply formatting changes
github-actions[bot] Jan 2, 2025
e696cf7
Merge remote-tracking branch 'origin/feat(frontend)/add-sol-worker' i…
loki344 Jan 2, 2025
e1a7793
🤖 Apply formatting changes
github-actions[bot] Jan 2, 2025
f68d290
feat(frontend): rm txns
loki344 Jan 6, 2025
baeeb48
feat(frontend): fix tests
loki344 Jan 6, 2025
73bd616
feat(frontend): fix tests
loki344 Jan 6, 2025
9125eba
Merge remote-tracking branch 'refs/remotes/origin/main' into feat(fro…
loki344 Jan 6, 2025
8bc76a6
🤖 Apply formatting changes
github-actions[bot] Jan 6, 2025
c16477a
Merge remote-tracking branch 'origin/feat(frontend)/add-sol-worker' i…
loki344 Jan 6, 2025
535fb6e
Merge branch 'main' into feat(frontend)/add-sol-worker
loki344 Jan 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/frontend/src/btc/schedulers/btc-wallet.scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ export class BtcWalletScheduler implements Scheduler<PostMessageDataRequestBtc>
const newBalance =
isNullish(this.store.balance) ||
this.store.balance.data !== balance.data ||
// TODO, align with sol-wallet.scheduler.ts, crash if certified changes
(!this.store.balance.certified && balance.certified);
const newTransactions = uncertifiedTransactions.length > 0;

Expand Down
116 changes: 116 additions & 0 deletions src/frontend/src/sol/schedulers/sol-wallet.scheduler.ts
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
Copy link
Member

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?


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
}
});
}
}
8 changes: 2 additions & 6 deletions src/frontend/src/sol/schema/sol-post-message.schema.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import {
JsonTransactionsTextSchema,
PostMessageDataResponseSchema
} from '$lib/schema/post-message.schema';
import { PostMessageDataResponseSchema } from '$lib/schema/post-message.schema';
import type { CertifiedData } from '$lib/types/store';
import { z } from 'zod';

const SolPostMessageWalletDataSchema = z.object({
balance: z.custom<CertifiedData<bigint | null>>(),
newTransactions: JsonTransactionsTextSchema
balance: z.custom<CertifiedData<bigint | null>>()
});

export const SolPostMessageDataResponseWalletSchema = PostMessageDataResponseSchema.extend({
Expand Down
95 changes: 95 additions & 0 deletions src/frontend/src/sol/services/worker.sol-wallet.services.ts
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a TODO to review this at some point. As far as I remember neither @DenysKarmazynDFINITY nor myself were super happy with this pattern hideToast. Right Denys?

Copy link
Contributor

Choose a reason for hiding this comment

The 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
});
}
};
};
20 changes: 20 additions & 0 deletions src/frontend/src/sol/workers/sol-wallet.worker.ts
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;
}
};
Loading
Loading