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

WIP: feat/1206 - SDK: Adding events to shielded sync #1212

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
50 changes: 47 additions & 3 deletions apps/namadillo/src/App/Masp/ShieldedBalanceChart.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
import { Heading, PieChart, SkeletonLoading } from "@namada/components";
import { ProgressBarNames } from "@namada/shared";
import { AtomErrorBoundary } from "App/Common/AtomErrorBoundary";
import { FiatCurrency } from "App/Common/FiatCurrency";
import { shieldedTokensAtom } from "atoms/balance/atoms";
import {
shieldedSyncProgressAtom,
shieldedTokensAtom,
} from "atoms/balance/atoms";
import { getTotalDollar } from "atoms/balance/functions";
import { useAtomValue } from "jotai";
import { colors } from "theme";

export const ShieldedBalanceChart = (): JSX.Element => {
const shieldedTokensQuery = useAtomValue(shieldedTokensAtom);

const shieledSyncProgressScanned = useAtomValue(
shieldedSyncProgressAtom(ProgressBarNames.Scanned)
);
const shieledSyncProgressFetched = useAtomValue(
shieldedSyncProgressAtom(ProgressBarNames.Fetched)
);
const shieledSyncProgressApplied = useAtomValue(
shieldedSyncProgressAtom(ProgressBarNames.Applied)
);
const shieldedDollars = getTotalDollar(shieldedTokensQuery.data);

return (
Expand Down Expand Up @@ -48,12 +60,44 @@ export const ShieldedBalanceChart = (): JSX.Element => {
</div>
</PieChart>
<div className="absolute -bottom-4 -left-2 text-[10px]">
*Balances exclude NAM until phase 5
*Balances exclude NAM until phase 5{" "}
</div>
</>
}
<div className="absolute top-0 right-0 text-right">
Shieled sync progress: <br />
<Progress name="Scanned" {...shieledSyncProgressScanned} />
<Progress name="Fetched" {...shieledSyncProgressFetched} />
<Progress name="Applied" {...shieledSyncProgressApplied} />
</div>
</AtomErrorBoundary>
</div>
</div>
);
};

const Progress = ({
name,
status,
current,
total,
}: {
name: string;
status: string;
current: number;
total: number;
}): JSX.Element => {
const perc = current === 0 && total === 0 ? 0 : (current / total) * 100;
return (
<div>
{name}:
{status === "pending" ?
<div>-</div>
: status === "loading" ?
<div>{Math.floor(perc)}%</div>
: status === "success" ?
<div>100%</div>
: <div>error</div>}
</div>
);
};
56 changes: 45 additions & 11 deletions apps/namadillo/src/atoms/balance/atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,60 +11,94 @@ import {
} from "atoms/chain";
import { shouldUpdateBalanceAtom } from "atoms/etc";
import { availableAssetsAtom } from "atoms/integrations";
import { maspIndexerUrlAtom, rpcUrlAtom } from "atoms/settings";
import { queryDependentFn } from "atoms/utils";
import BigNumber from "bignumber.js";
import { atom } from "jotai";
import { atomWithQuery } from "jotai-tanstack-query";
import { atomFamily } from "jotai/utils";
import { namadaAsset } from "registry/namadaAsset";
import { AddressWithAsset } from "types";
import {
findAssetByToken,
mapNamadaAddressesToAssets,
mapNamadaAssetsToTokenBalances,
} from "./functions";
import { fetchCoinPrices, fetchShieldedBalance } from "./services";
import {
fetchCoinPrices,
fetchShieldedBalance,
shieldedSync,
} from "./services";

export type TokenBalance = AddressWithAsset & {
balance: BigNumber;
dollar?: BigNumber;
};

export const viewingKeyAtom = atomWithQuery<string>((get) => {
export const viewingKeysAtom = atomWithQuery<[string, string[]]>((get) => {
const accountsQuery = get(accountsAtom);
const defaultAccountQuery = get(defaultAccountAtom);

return {
queryKey: ["viewing-key", accountsQuery.data, defaultAccountQuery.data],
queryKey: ["viewing-keys", accountsQuery.data, defaultAccountQuery.data],
...queryDependentFn(async () => {
const shieldedAccount = accountsQuery.data?.find(
(a) => a.isShielded && a.alias === defaultAccountQuery.data?.alias
const shieldedAccounts = accountsQuery.data?.filter((a) => a.isShielded);
const defaultShieldedAccount = shieldedAccounts?.find(
(a) => a.alias === defaultAccountQuery.data?.alias
);
return shieldedAccount?.viewingKey ?? "";
const defaultViewingKey = defaultShieldedAccount?.viewingKey ?? "";
const viewingKeys =
shieldedAccounts?.map((a) => a.viewingKey ?? "") ?? [];

return [defaultViewingKey, viewingKeys];
}, [accountsQuery, defaultAccountQuery]),
};
});

export const shieldedSyncProgressAtom = atomFamily((_name: string) =>
atom<{
status: "pending" | "loading" | "success";
current: number;
total: number;
}>({ status: "pending", current: 0, total: 0 })
);

export const shieldedBalanceAtom = atomWithQuery<
{ address: string; amount: BigNumber }[]
>((get) => {
const enablePolling = get(shouldUpdateBalanceAtom);
const viewingKeyQuery = get(viewingKeyAtom);
const viewingKeysQuery = get(viewingKeysAtom);
const tokenAddressesQuery = get(tokenAddressesAtom);
const namTokenAddressQuery = get(nativeTokenAddressAtom);
const rpcUrl = get(rpcUrlAtom);
const maspIndexerUrl = get(maspIndexerUrlAtom);

return {
refetchInterval: enablePolling ? 1000 : false,
queryKey: [
"shielded-balance",
viewingKeyQuery.data,
viewingKeysQuery.data,
tokenAddressesQuery.data,
namTokenAddressQuery.data,
rpcUrl,
maspIndexerUrl,
],
...queryDependentFn(async () => {
const viewingKey = viewingKeyQuery.data;
const viewingKeys = viewingKeysQuery.data;
const tokenAddresses = tokenAddressesQuery.data;
if (!viewingKey || !tokenAddresses) {
if (!viewingKeys || !tokenAddresses) {
return [];
}
const namTokenAddress = namTokenAddressQuery.data;
const [viewingKey, allViewingKeys] = viewingKeys;

await shieldedSync(
rpcUrl,
maspIndexerUrl,
namTokenAddress!,
allViewingKeys
);

const response = await fetchShieldedBalance(
viewingKey,
tokenAddresses.map((t) => t.address)
Expand All @@ -78,7 +112,7 @@ export const shieldedBalanceAtom = atomWithQuery<
: new BigNumber(amount),
}));
return shieldedBalance;
}, [viewingKeyQuery, tokenAddressesQuery, namTokenAddressQuery]),
}, [viewingKeysQuery, tokenAddressesQuery, namTokenAddressQuery]),
};
});

Expand Down
84 changes: 82 additions & 2 deletions apps/namadillo/src/atoms/balance/services.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import { Balance } from "@namada/sdk/web";
import * as Comlink from "comlink";

import { Balance, SdkEvents } from "@namada/sdk/web";
import { getDefaultStore } from "jotai";
import { getSdkInstance } from "utils/sdk";
import {
Events,
ProgressBarIncremented,
ProgressBarStarted,
Worker as ShieldedSyncWorkerApi,
} from "workers/ShieldedSyncWorker";
import ShieldedSyncWorker from "workers/ShieldedSyncWorker?worker";
// TODO: circular dependency
import { shieldedSyncProgressAtom } from "./atoms";

const sqsOsmosisApi = "https://sqs.osmosis.zone/";

Expand All @@ -11,6 +23,75 @@ export const fetchCoinPrices = async (
`${sqsOsmosisApi}/tokens/prices?base=${assetBaseList.sort((a, b) => a.localeCompare(b)).join(",")}`
).then((res) => res.json());

const isStarted = (event: Events): event is ProgressBarStarted => {
return event.type === SdkEvents.ProgressBarStarted;
};

const isIncremented = (event: Events): event is ProgressBarIncremented => {
return event.type === SdkEvents.ProgressBarIncremented;
};

const isFinished = (event: Events): event is ProgressBarStarted => {
return event.type === SdkEvents.ProgressBarFinished;
};

export const shieldedSync = async (
rpcUrl: string,
maspIndexerUrl: string,
token: string,
viewingKeys: string[]
): Promise<void> => {
const worker = new ShieldedSyncWorker();
const shieldedSyncWorker = Comlink.wrap<ShieldedSyncWorkerApi>(worker);

worker.onmessage = (event: MessageEvent<Events>) => {
const store = getDefaultStore();
const { data } = event;

if (isStarted(data)) {
store.set(shieldedSyncProgressAtom(data.payload.name), {
status: "loading",
current: 0,
total: 0,
});
}
if (isIncremented(data)) {
const { name, current, total } = data.payload;

store.set(shieldedSyncProgressAtom(name), {
status: "loading",
current,
total,
});
}
if (isFinished(data)) {
store.set(shieldedSyncProgressAtom(data.payload.name), {
status: "success",
current: 0,
total: 0,
});
}
};

await shieldedSyncWorker.init({
type: "init",
payload: {
rpcUrl,
maspIndexerUrl,
token,
},
});

await shieldedSyncWorker.sync({
type: "sync",
payload: {
vks: viewingKeys,
},
});

worker.terminate();
};

export const fetchShieldedBalance = async (
viewingKey: string,
addresses: string[]
Expand All @@ -19,7 +100,6 @@ export const fetchShieldedBalance = async (
// return await mockShieldedBalance(viewingKey);

const sdk = await getSdkInstance();
await sdk.rpc.shieldedSync([viewingKey]);
return await sdk.rpc.queryBalance(viewingKey, addresses);
};

Expand Down
32 changes: 31 additions & 1 deletion apps/namadillo/src/workers/ShieldedSyncWorker.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,44 @@
import { initMulticore } from "@namada/sdk/inline-init";
import { getSdk, Sdk } from "@namada/sdk/web";
import { getSdk, Sdk, SdkEvents } from "@namada/sdk/web";
import * as Comlink from "comlink";
import { Init, InitDone, Sync, SyncDone } from "./ShieldedSyncMessages";

export type ProgressBarStarted = { type: string; payload: { name: string } };
export type ProgressBarIncremented = {
type: string;
payload: { name: string; current: number; total: number };
};
export type ProgressBarFinished = { type: string; payload: { name: string } };
export type Events =
| ProgressBarStarted
| ProgressBarIncremented
| ProgressBarFinished;

export class Worker {
private sdk: Sdk | undefined;

async init(m: Init): Promise<InitDone> {
const { cryptoMemory } = await initMulticore();
this.sdk = newSdk(cryptoMemory, m.payload);

addEventListener(SdkEvents.ProgressBarStarted, (data) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const payload = JSON.parse((data as any).detail);
postMessage({ type: SdkEvents.ProgressBarStarted, payload });
});

addEventListener(SdkEvents.ProgressBarIncremented, (data) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const payload = JSON.parse((data as any).detail);
postMessage({ type: SdkEvents.ProgressBarIncremented, payload });
});

addEventListener(SdkEvents.ProgressBarFinished, (data) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const payload = JSON.parse((data as any).detail);
postMessage({ type: SdkEvents.ProgressBarFinished, payload });
});

return { type: "init-done", payload: null };
}

Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export type {
export { TxType, TxTypeLabel } from "./tx";
export type { SupportedTx } from "./tx";

export { Sdk } from "./sdk";
export { Sdk, SdkEvents } from "./sdk";

export { publicKeyToBech32 } from "./keys";

Expand Down
3 changes: 3 additions & 0 deletions packages/sdk/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { Rpc } from "./rpc";
import { Signing } from "./signing";
import { Tx } from "./tx";

export { SdkEvents } from "@namada/shared";

/**
* API for interacting with Namada SDK
*/
Expand Down Expand Up @@ -114,6 +116,7 @@ export class Sdk {

/**
* Return SDK Package version
* @returns SDK version
*/
getVersion(): string {
return packageJson.version;
Expand Down
5 changes: 5 additions & 0 deletions packages/shared/lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ subtle-encoding = "0.5.1"
version = "0.3.4"
features = [
'console',
'Document',
'Event',
'EventTarget',
'CustomEvent',
'CustomEventInit',
'Headers',
'Request',
'RequestInit',
Expand Down
Loading
Loading