Skip to content

Commit

Permalink
Merge branch 'main' into feat/transfer-navigation
Browse files Browse the repository at this point in the history
  • Loading branch information
euharrison committed Oct 1, 2024
2 parents 5e18828 + acb7c0b commit c8001aa
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 36 deletions.
61 changes: 41 additions & 20 deletions apps/namadillo/src/hooks/useSdk.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import initSdk from "@heliax/namada-sdk/inline-init";
import { getSdk, Sdk } from "@heliax/namada-sdk/web";
import { QueryStatus, useQuery } from "@tanstack/react-query";
import { nativeTokenAddressAtom } from "atoms/chain";
import { rpcUrlAtom } from "atoms/settings";
import { getDefaultStore, useAtomValue } from "jotai";
Expand All @@ -13,7 +14,15 @@ import {
} from "react";
import Proxies from "../../scripts/proxies.json";

export const SdkContext = createContext<Sdk | undefined>(undefined);
type SdkContext = {
sdk?: Sdk;
maspParamsStatus: QueryStatus;
};

export const SdkContext = createContext<SdkContext>({
sdk: undefined,
maspParamsStatus: "pending",
});

const { VITE_PROXY: isProxied } = import.meta.env;

Expand Down Expand Up @@ -51,37 +60,49 @@ export const SdkProvider: FunctionComponent<PropsWithChildren> = ({
const [sdk, setSdk] = useState<Sdk>();
const nativeToken = useAtomValue(nativeTokenAddressAtom);

// fetchAndStoreMaspParams() returns nothing,
// so we return boolean on success for the query to succeed:
const fetchMaspParams = async (): Promise<boolean | void> => {
const { masp } = sdk!;

return masp.hasMaspParams().then(async (hasMaspParams) => {
if (hasMaspParams) {
await masp.loadMaspParams("").catch((e) => Promise.reject(e));
return true;
}
return masp
.fetchAndStoreMaspParams(paramsUrl)
.then(() => masp.loadMaspParams("").then(() => true))
.catch((e) => {
throw new Error(e);
});
});
};

const { status: maspParamsStatus } = useQuery({
queryKey: ["sdk"],
queryFn: fetchMaspParams,
retry: 3,
retryDelay: 3000,
});

useEffect(() => {
if (nativeToken.data) {
getSdkInstance().then((sdk) => {
setSdk(sdk);
const { masp } = sdk;
masp.hasMaspParams().then((hasMaspParams) => {
if (hasMaspParams) {
return masp.loadMaspParams("").catch((e) => console.error(`${e}`));
}
masp
.fetchAndStoreMaspParams(paramsUrl)
.then(() => masp.loadMaspParams(""))
.catch((e) => console.error(`${e}`));
});
});
}
}, [nativeToken.data]);

return (
<>
<SdkContext.Provider value={sdk}> {children} </SdkContext.Provider>
<SdkContext.Provider value={{ sdk, maspParamsStatus }}>
{children}
</SdkContext.Provider>
</>
);
};

export const useSdk = (): Sdk => {
const sdkContext = useContext(SdkContext);

if (!sdkContext) {
throw new Error("sdkContext has to be used within <SdkContext.Provider>");
}

return sdkContext;
export const useSdk = (): SdkContext => {
return useContext(SdkContext);
};
133 changes: 117 additions & 16 deletions packages/shared/lib/src/sdk/mod.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,147 @@
const PREFIX = "Namada::SDK";
const MASP_MPC_RELEASE_URL =
"https://github.com/anoma/masp-mpc/releases/download/namada-trusted-setup/";

const sha256Hash = async (msg: Uint8Array): Promise<string> => {
const hashBuffer = await crypto.subtle.digest("SHA-256", msg);
const hashArray = Array.from(new Uint8Array(hashBuffer));
// Return hash as hex
return hashArray.map((byte) => byte.toString(16).padStart(2, "0")).join("");
};

enum MaspParam {
Output = "masp-output.params",
Convert = "masp-convert.params",
Spend = "masp-spend.params",
}

type MaspParamBytes = {
param: MaspParam;
bytes: Uint8Array;
};

/**
* The following sha256 digests where produced by downloading the following:
* https://github.com/anoma/masp-mpc/releases/download/namada-trusted-setup/masp-convert.params
* https://github.com/anoma/masp-mpc/releases/download/namada-trusted-setup/masp-spend.params
* https://github.com/anoma/masp-mpc/releases/download/namada-trusted-setup/masp-output.params
*
* And running "sha256sum" against each file:
*
* > sha256sum masp-convert.params
* 8e049c905e0e46f27662c7577a4e3480c0047ee1171f7f6d9c5b0de757bf71f1 masp-convert.params
*
* > sha256sum masp-spend.params
* 62b3c60ca54bd99eb390198e949660624612f7db7942db84595fa9f1b4a29fd8 masp-spend.params
*
* > sha256sum masp-output.params
* ed8b5d354017d808cfaf7b31eca5c511936e65ef6d276770251f5234ec5328b8 masp-output.params
*
* Length is specified in bytes, and can be retrieved with:
*
* > wc -c < masp-convert.params
* 22570940
* > wc -c < masp-spend.params
* 49848572
* > wc -c < masp-output.params
* 16398620
*/
const MASP_PARAM_ATTR: Record<
MaspParam,
{ length: number; sha256sum: string }
> = {
[MaspParam.Output]: {
length: 16398620,
sha256sum:
"ed8b5d354017d808cfaf7b31eca5c511936e65ef6d276770251f5234ec5328b8",
},
[MaspParam.Spend]: {
length: 49848572,
sha256sum:
"62b3c60ca54bd99eb390198e949660624612f7db7942db84595fa9f1b4a29fd8",
},
[MaspParam.Convert]: {
length: 22570940,
sha256sum:
"8e049c905e0e46f27662c7577a4e3480c0047ee1171f7f6d9c5b0de757bf71f1",
},
};

const validateMaspParamBytes = async ({
param,
bytes,
}: MaspParamBytes): Promise<Uint8Array> => {
const { length, sha256sum } = MASP_PARAM_ATTR[param];

// Reject if invalid length (incomplete download or invalid)
console.info(`Validating data length for ${param}, expecting ${length}...`);

if (length !== bytes.length) {
return Promise.reject(
`[${param}]: Invalid data length! Expected ${length}, received ${bytes.length}!`
);
}

// Reject if invalid hash (otherwise invalid data)
console.info(`Validating sha256sum for ${param}, expecting ${sha256sum}...`);
const hash = await sha256Hash(bytes);

if (hash !== sha256sum) {
return Promise.reject(
`[${param}]: Invalid sha256sum! Expected ${sha256sum}, received ${hash}!`
);
}

return bytes;
};

export async function hasMaspParams(): Promise<boolean> {
return (
(await has("masp-spend.params")) &&
(await has("masp-output.params")) &&
(await has("masp-convert.params"))
(await has(MaspParam.Spend)) &&
(await has(MaspParam.Output)) &&
(await has(MaspParam.Convert))
);
}

export async function fetchAndStoreMaspParams(
url?: string
): Promise<[void, void, void]> {
return Promise.all([
fetchAndStore("masp-spend.params", url),
fetchAndStore("masp-output.params", url),
fetchAndStore("masp-convert.params", url),
fetchAndStore(MaspParam.Spend, url),
fetchAndStore(MaspParam.Output, url),
fetchAndStore(MaspParam.Convert, url),
]);
}

export async function getMaspParams(): Promise<[unknown, unknown, unknown]> {
return Promise.all([
get("masp-spend.params"),
get("masp-output.params"),
get("masp-convert.params"),
get(MaspParam.Spend),
get(MaspParam.Output),
get(MaspParam.Convert),
]);
}

export async function fetchAndStore(
params: string,
param: MaspParam,
url?: string
): Promise<void> {
const data = await fetchParams(params, url);
await set(params, data);
return await fetchParams(param, url)
.then((data) => set(param, data))
.catch((e) => {
return Promise.reject(`Encountered errors fetching ${param}: ${e}`);
});
}

export async function fetchParams(
params: string,
url: string = "https://github.com/anoma/masp-mpc/releases/download/namada-trusted-setup/"
param: MaspParam,
url: string = MASP_MPC_RELEASE_URL
): Promise<Uint8Array> {
return fetch(`${url}${params}`)
return fetch(`${url}${param}`)
.then((response) => response.arrayBuffer())
.then((ab) => new Uint8Array(ab));
.then((ab) => {
const bytes = new Uint8Array(ab);
return validateMaspParamBytes({ param, bytes });
});
}

function getDB(): Promise<IDBDatabase> {
Expand Down

0 comments on commit c8001aa

Please sign in to comment.