diff --git a/apps/namadillo/src/App/App.tsx b/apps/namadillo/src/App/App.tsx index f6b68e975..38f586c00 100644 --- a/apps/namadillo/src/App/App.tsx +++ b/apps/namadillo/src/App/App.tsx @@ -1,11 +1,21 @@ +import { ShieldingTransferMsgValue } from "@namada/types"; import { Toasts } from "App/Common/Toast"; import { AppLayout } from "App/Layout/AppLayout"; +import { defaultAccountAtom } from "atoms/accounts"; +import { chainAtom, nativeTokenAddressAtom } from "atoms/chain"; +import { rpcUrlAtom } from "atoms/settings"; +import BigNumber from "bignumber.js"; import { createBrowserHistory } from "history"; +import { useSdk } from "hooks"; import { useExtensionEvents } from "hooks/useExtensionEvents"; import { useOnNamadaExtensionAttached } from "hooks/useOnNamadaExtensionAttached"; import { useTransactionCallback } from "hooks/useTransactionCallbacks"; import { useTransactionNotifications } from "hooks/useTransactionNotifications"; +import { useAtomValue } from "jotai"; +import { broadcastTx, EncodedTxData, signTx } from "lib/query"; import { Outlet } from "react-router-dom"; +import { ShieldMessageType } from "workers/ShieldWorker"; +import ShieldWorker from "workers/ShieldWorker?worker"; import { ChainLoader } from "./Setup/ChainLoader"; export const history = createBrowserHistory({ window }); @@ -15,6 +25,87 @@ export function App(): JSX.Element { useOnNamadaExtensionAttached(); useTransactionNotifications(); useTransactionCallback(); + + const rpcUrl = useAtomValue(rpcUrlAtom); + + // const worker = new ShieldedSyncWorker(); + // const maspIndexerUrl = useAtomValue(maspIndexerUrlAtom); + + // // eslint-disable-next-line @typescript-eslint/no-explicit-any + // (window as any).shieldedSync = () => { + // console.log("maspIndexerUrl", maspIndexerUrl); + // const msg: ShieldedSyncMessageType = { + // type: "shielded-sync-multicore", + // payload: { + // rpcUrl, + // maspIndexerUrl, + // vks: [ + // "zvknam1qvgwgy79qqqqpq88yru6n3f3ugfme002t7272a0ke8zdr2kt80jhnjwmgxkwm7yc6ydp8tfh8lmd28n8hrmcvqszjm3tnytryaf4qhwu645xks4nnx64m3fnpm8yr6hrpd8jtsupyzz4knqleuy7jdjz32jcz9ual56vrf3estg0e6kew0g9aqs4vg2d6n569c78ttqw4zw6mvjkhwfprcc804qt3yewsrxf8l67p87ltnqjtjkr35pfnnxavs9c5wqpr2t2lf3husqn4zvux", + // ], + // }, + // }; + + // worker.postMessage(msg); + // }; + // + // + const { sdk } = useSdk(); + + const shieldWorker = new ShieldWorker(); + const { data: account } = useAtomValue(defaultAccountAtom); + const { data: chain } = useAtomValue(chainAtom); + const { data: token } = useAtomValue(nativeTokenAddressAtom); + const shiedlingMsgValue = { + target: + "znam1vue386rsee5c65qsvlm9tgj5lqetz6ejkln3j57utc44w2n8upty57z7lh07myrj3clfxyl9lvn", + data: [ + { + source: account?.address || "", + token: token!, + amount: 100 as unknown as BigNumber, + }, + ], + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (window as any).buildShieldlingTx = () => { + const msg: ShieldMessageType = { + type: "shield", + payload: { + account: account!, + gasConfig: { + gasLimit: 250000, + gasPrice: 0.000001, + }, + shieldingProps: [shiedlingMsgValue], + rpcUrl, + token: token!, + chain: chain!, + }, + }; + + shieldWorker.postMessage(msg); + }; + + shieldWorker.onmessage = async (e) => { + const encodedTx = e.data + .payload as EncodedTxData; + console.log("encodedTx2", encodedTx); + const signedTxs = await signTx("namada", encodedTx, account?.address || ""); + console.log("signedTx", signedTxs); + + signedTxs.forEach((tx) => { + signedTxs.forEach((signedTx) => { + broadcastTx( + encodedTx, + signedTx, + encodedTx.meta?.props + // TODO: event type + ); + }); + }); + }; + return ( <> diff --git a/apps/namadillo/src/atoms/api.ts b/apps/namadillo/src/atoms/api.ts index 24257cbad..6975352ee 100644 --- a/apps/namadillo/src/atoms/api.ts +++ b/apps/namadillo/src/atoms/api.ts @@ -14,6 +14,8 @@ export const getIndexerApi = (): DefaultApi => { // Helper function to use outside of hooks const getApi = (get: (atom: Atom) => Value): DefaultApi => { const indexerUrl = get(indexerUrlAtom); + console.log("indexerUrl", indexerUrl, typeof indexerUrl); + //TODO: this returns "" sometimes const configuration = new Configuration({ basePath: indexerUrl }); return new DefaultApi(configuration); diff --git a/apps/namadillo/src/atoms/staking/atoms.ts b/apps/namadillo/src/atoms/staking/atoms.ts index 8e9e5796b..fd1209aa9 100644 --- a/apps/namadillo/src/atoms/staking/atoms.ts +++ b/apps/namadillo/src/atoms/staking/atoms.ts @@ -3,6 +3,7 @@ import { BondMsgValue, ClaimRewardsMsgValue, RedelegateMsgValue, + ShieldingTransferMsgValue, UnbondMsgValue, WithdrawMsgValue, } from "@namada/types"; @@ -21,6 +22,7 @@ import { createClaimAndStakeTx, createClaimTx, createReDelegateTx, + createShieldingTx, createUnbondTx, createWithdrawTx, fetchClaimableRewards, @@ -38,6 +40,20 @@ export const getStakingTotalAtom = atomWithQuery((get) => { }; }); +export const createShieldingTxAtom = atomWithMutation((get) => { + const chain = get(chainAtom); + return { + mutationKey: ["create-shielding-tx"], + enabled: chain.isSuccess, + mutationFn: async ({ + params, + gasConfig, + account, + }: BuildTxAtomParams) => + createShieldingTx(chain.data!, account, params, gasConfig), + }; +}); + export const createBondTxAtom = atomWithMutation((get) => { const chain = get(chainAtom); return { diff --git a/apps/namadillo/src/atoms/staking/services.ts b/apps/namadillo/src/atoms/staking/services.ts index d8523b6cd..9e4af6b3a 100644 --- a/apps/namadillo/src/atoms/staking/services.ts +++ b/apps/namadillo/src/atoms/staking/services.ts @@ -6,6 +6,8 @@ import { ClaimRewardsMsgValue, ClaimRewardsProps, RedelegateMsgValue, + ShieldingTransferMsgValue, + ShieldingTransferProps, TxMsgValue, UnbondMsgValue, WithdrawMsgValue, @@ -43,6 +45,24 @@ export const createBondTx = async ( return transactionPairs; }; +export const createShieldingTx = async ( + chain: ChainSettings, + account: Account, + shiedlingProps: ShieldingTransferMsgValue[], + gasConfig: GasConfig +): Promise | undefined> => { + const { tx } = await getSdkInstance(); + const transactionPairs = await buildTxPair( + account, + gasConfig, + chain, + shiedlingProps, + tx.buildShieldingTransfer, + shiedlingProps[0].data[0].source + ); + return transactionPairs; +}; + export const createUnbondTx = async ( chain: ChainSettings, account: Account, diff --git a/apps/namadillo/src/lib/build.ts b/apps/namadillo/src/lib/build.ts new file mode 100644 index 000000000..303b89a12 --- /dev/null +++ b/apps/namadillo/src/lib/build.ts @@ -0,0 +1,113 @@ +import { Sdk } from "@heliax/namada-sdk/web"; +import { + Account, + AccountType, + TxMsgValue, + TxProps, + WrapperTxProps, +} from "@namada/types"; +import { getIndexerApi } from "atoms/api"; +import invariant from "invariant"; +import { Address, ChainSettings, GasConfig } from "types"; + +export type EncodedTxData = { + type: string; + txs: TxProps[] & + { + innerTxHashes: string[]; + }[]; + wrapperTxProps: WrapperTxProps; + meta?: { + props: T[]; + }; +}; + +export const isPublicKeyRevealed = async ( + address: Address +): Promise => { + const api = getIndexerApi(); + console.log("address", address); + let publicKey: string | undefined; + try { + publicKey = (await api.apiV1RevealedPublicKeyAddressGet(address)).data + ?.publicKey; + console.log("publicKey", publicKey); + } catch {} + return Boolean(publicKey); +}; + +const getTxProps = ( + account: Account, + gasConfig: GasConfig, + chain: ChainSettings +): WrapperTxProps => { + invariant( + !!account.publicKey, + "Account doesn't contain a publicKey attached to it" + ); + + return { + token: chain.nativeTokenAddress, + feeAmount: gasConfig.gasPrice, + gasLimit: gasConfig.gasLimit, + chainId: chain.chainId, + publicKey: account.publicKey!, + memo: "", + }; +}; + +export const buildTx2 = async ( + sdk: Sdk, + account: Account, + gasConfig: GasConfig, + chain: ChainSettings, + queryProps: T[], + txFn: (wrapperTxProps: WrapperTxProps, props: T) => Promise +): Promise> => { + const { tx } = sdk; + const wrapperTxProps = getTxProps(account, gasConfig, chain); + const txs: TxMsgValue[] = []; + const txProps: TxProps[] = []; + console.log("wrapperTxProps", wrapperTxProps); + + // Determine if RevealPK is needed: + // const publicKeyRevealed = await isPublicKeyRevealed(account.address); + // console.log("publicKeyRevealed", publicKeyRevealed, account.address); + // if (!publicKeyRevealed) { + // const revealPkTx = await tx.buildRevealPk(wrapperTxProps); + // txs.push(revealPkTx); + // } + + console.log("queryProps", queryProps); + + const encodedTxs = await Promise.all( + queryProps.map((props) => txFn.apply(tx, [wrapperTxProps, props])) + ); + console.log("encodedTxs", encodedTxs); + + txs.push(...encodedTxs); + + if (account.type === AccountType.Ledger) { + txProps.push(...txs); + } else { + txProps.push(tx.buildBatch(txs)); + } + + return { + txs: txProps.map(({ args, hash, bytes, signingData }) => { + const innerTxHashes = tx.getInnerTxHashes(bytes); + return { + args, + hash, + bytes, + signingData, + innerTxHashes, + }; + }), + wrapperTxProps, + type: txFn.name, + meta: { + props: queryProps, + }, + }; +}; diff --git a/apps/namadillo/src/lib/query.ts b/apps/namadillo/src/lib/query.ts index b7b26cd93..240f67b9c 100644 --- a/apps/namadillo/src/lib/query.ts +++ b/apps/namadillo/src/lib/query.ts @@ -10,11 +10,11 @@ import { } from "@namada/types"; import { getIndexerApi } from "atoms/api"; import { chainParametersAtom } from "atoms/chain"; -import { getSdkInstance } from "hooks"; import invariant from "invariant"; import { getDefaultStore } from "jotai"; import { Address, ChainSettings, GasConfig } from "types"; import { TransactionEventsClasses } from "types/events"; +import { getSdkInstance } from "utils/sdk"; export type TransactionPair = { encodedTxData: EncodedTxData; diff --git a/apps/namadillo/src/utils/sdk.ts b/apps/namadillo/src/utils/sdk.ts new file mode 100644 index 000000000..28dc03599 --- /dev/null +++ b/apps/namadillo/src/utils/sdk.ts @@ -0,0 +1,38 @@ +import initSdk from "@heliax/namada-sdk/inline-init"; +import { getSdk, Sdk } from "@heliax/namada-sdk/web"; +import { nativeTokenAddressAtom } from "atoms/chain"; +import { maspIndexerUrlAtom, rpcUrlAtom } from "atoms/settings"; +import { getDefaultStore } from "jotai"; + +// TODO: temp import fix +const initializeSdk = async (): Promise => { + const { cryptoMemory } = await initSdk(); + const store = getDefaultStore(); + const rpcUrl = store.get(rpcUrlAtom); + const maspIndexerUrl = store.get(maspIndexerUrlAtom); + const nativeToken = store.get(nativeTokenAddressAtom); + + if (!nativeToken.isSuccess) { + throw "Native token not loaded"; + } + + const sdk = getSdk( + cryptoMemory, + rpcUrl, + maspIndexerUrl, + "", + nativeToken.data + ); + return sdk; +}; + +// Global instance of initialized SDK +let sdkInstance: Promise; + +// Helper to access SDK instance +export const getSdkInstance = async (): Promise => { + if (!sdkInstance) { + sdkInstance = initializeSdk(); + } + return sdkInstance; +}; diff --git a/apps/namadillo/src/workers/ShieldWorker.ts b/apps/namadillo/src/workers/ShieldWorker.ts new file mode 100644 index 000000000..3875601cd --- /dev/null +++ b/apps/namadillo/src/workers/ShieldWorker.ts @@ -0,0 +1,96 @@ +import { initMulticore } from "@heliax/namada-sdk/inline-init"; + +import { getSdk } from "@heliax/namada-sdk/web"; +import { Account, ShieldingTransferMsgValue } from "@namada/types"; +import BigNumber from "bignumber.js"; +import { buildTx2, EncodedTxData } from "lib/build"; +import { ChainSettings } from "types"; + +export type ShieldPayload = { + account: Account; + gasConfig: { + gasLimit: number; + gasPrice: number; + }; + shieldingProps: ShieldingTransferMsgValue[]; + rpcUrl: string; + token: string; + chain: ChainSettings; +}; + +export type Shield = { + type: "shield"; + payload: ShieldPayload; +}; + +export type ShieldMessageType = Shield; + +export type EncodedShieldTransferEvent = { + type: "encoded-shield-transfer"; + payload: EncodedTxData; +}; + +export type ShieldEvent = EncodedShieldTransferEvent; + +self.onmessage = async (e: MessageEvent) => { + const { type, payload } = e.data; + + switch (type) { + case "shield": { + const { cryptoMemory } = await initMulticore(); + await shield(cryptoMemory, payload); + break; + } + + default: + throw new Error(`Unknown message type: ${type}`); + } +}; + +async function shield( + cryptoMemory: WebAssembly.Memory, + payload: ShieldPayload +): Promise { + console.log("payload", payload); + const { rpcUrl, token, account, gasConfig, chain, shieldingProps } = payload; + const www = { + target: shieldingProps[0].target, + data: [ + { + ...shieldingProps[0].data[0], + amount: new BigNumber(shieldingProps[0].data[0].amount), + }, + ], + }; + + const sdk = getSdk( + cryptoMemory, + rpcUrl, + "", + "", + // Not really used, but required by the SDK, as long as it's valid address it's fine + token + ); + console.log("sdk", sdk); + + await sdk.masp.loadMaspParams(""); + const encodedTxData = await buildTx2( + sdk, + account, + // TODO: focking prototype xddd + { + gasLimit: BigNumber(gasConfig.gasLimit), + gasPrice: BigNumber(gasConfig.gasPrice), + }, + chain, + [www], + sdk.tx.buildShieldingTransfer + ); + + const event: EncodedShieldTransferEvent = { + type: "encoded-shield-transfer", + payload: encodedTxData, + }; + + postMessage(event); +} diff --git a/packages/sdk/src/rpc/rpc.ts b/packages/sdk/src/rpc/rpc.ts index 0be729db2..0a4d10289 100644 --- a/packages/sdk/src/rpc/rpc.ts +++ b/packages/sdk/src/rpc/rpc.ts @@ -228,6 +228,7 @@ export class Rpc { const msg = new Message(); const encodedArgs = msg.encode(wrapperTxMsgValue); + await this.sdk.load_masp_params(""); const response = await this.sdk.process_tx(signedTxBytes, encodedArgs); return deserialize(Buffer.from(response), TxResponseMsgValue); } diff --git a/packages/sdk/src/tx/tx.ts b/packages/sdk/src/tx/tx.ts index 913f70849..2ad09fac0 100644 --- a/packages/sdk/src/tx/tx.ts +++ b/packages/sdk/src/tx/tx.ts @@ -115,9 +115,12 @@ export class Tx { wrapperTxProps: WrapperTxProps, shieldingTransferProps: ShieldingTransferProps ): Promise { + console.log("Shielding Transfer Props: ", shieldingTransferProps); const shieldingTransferMsg = new Message(); + console.log("Shielding Transfer Msg: ", shieldingTransferMsg); const encodedWrapperArgs = this.encodeTxArgs(wrapperTxProps); + const encodedTransfer = shieldingTransferMsg.encode( new ShieldingTransferMsgValue(shieldingTransferProps) ); @@ -162,7 +165,9 @@ export class Tx { */ async buildRevealPk(wrapperTxProps: WrapperTxProps): Promise { const encodedWrapperArgs = this.encodeTxArgs(wrapperTxProps); + console.log("Encoded Wrapper Args: ", encodedWrapperArgs); const serializedTx = await this.sdk.build_reveal_pk(encodedWrapperArgs); + console.log("Serialized Tx: ", serializedTx); return deserialize(Buffer.from(serializedTx), TxMsgValue); } @@ -412,6 +417,7 @@ export class Tx { encodeTxArgs(wrapperTxProps: WrapperTxProps): Uint8Array { const wrapperTxMsgValue = new WrapperTxMsgValue(wrapperTxProps); const msg = new Message(); + console.log("msg: ", msg); return msg.encode(wrapperTxMsgValue); } diff --git a/packages/shared/lib/Cargo.toml b/packages/shared/lib/Cargo.toml index b86c8dd7d..2b6e8df92 100644 --- a/packages/shared/lib/Cargo.toml +++ b/packages/shared/lib/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" crate-type = ["cdylib", "rlib"] [features] -default = [] +default = ["web", "multicore"] dev = [] multicore = ["rayon", "wasm-bindgen-rayon", "namada_sdk/multicore"] nodejs = [] diff --git a/packages/shared/lib/src/sdk/args.rs b/packages/shared/lib/src/sdk/args.rs index f6fe04816..abf871d0b 100644 --- a/packages/shared/lib/src/sdk/args.rs +++ b/packages/shared/lib/src/sdk/args.rs @@ -862,6 +862,7 @@ fn tx_msg_into_args(tx_msg: &[u8]) -> Result { } = tx_msg; let token = Address::from_str(&token)?; + web_sys::console::log_1(&format!("Fee amount: {}", fee_amount).into()); let fee_amount = DenominatedAmount::from_str(&fee_amount) .expect(format!("Fee amount has to be valid. Received {}", fee_amount).as_str()); diff --git a/packages/shared/lib/src/sdk/masp/masp_web.rs b/packages/shared/lib/src/sdk/masp/masp_web.rs index 3fb80bc34..8b2dee3bd 100644 --- a/packages/shared/lib/src/sdk/masp/masp_web.rs +++ b/packages/shared/lib/src/sdk/masp/masp_web.rs @@ -144,6 +144,7 @@ impl ShieldedUtils for WebShieldedUtils { ctx: &mut ShieldedWallet, force_confirmed: bool, ) -> std::io::Result<()> { + web_sys::console::log_1(&JsValue::from_str("Loading shielded context")); let db = Self::build_database().await.map_err(Self::to_io_err)?; let confirmed = force_confirmed || get_confirmed(&ctx.sync_status); diff --git a/packages/shared/lib/src/sdk/mod.rs b/packages/shared/lib/src/sdk/mod.rs index 787c9fcec..bb2c1519f 100644 --- a/packages/shared/lib/src/sdk/mod.rs +++ b/packages/shared/lib/src/sdk/mod.rs @@ -79,6 +79,7 @@ impl Sdk { #[cfg(feature = "web")] pub async fn load_masp_params(&self, _db_name: JsValue) -> Result<(), JsValue> { + web_sys::console::log_1(&JsValue::from_str("Loading masp params")); // _dn_name is not used in the web version for a time being let params = get_masp_params().await?; let params_iter = js_sys::try_iter(¶ms)?.ok_or("Can't iterate over JsValue")?; diff --git a/packages/types/src/tx/schema/transfer.ts b/packages/types/src/tx/schema/transfer.ts index ae7a30bdb..165fc4d95 100644 --- a/packages/types/src/tx/schema/transfer.ts +++ b/packages/types/src/tx/schema/transfer.ts @@ -105,11 +105,15 @@ export class ShieldingTransferDataMsgValue { } export class ShieldingTransferMsgValue { + @field({ type: "string" }) + target!: string; + @field({ type: vec(ShieldingTransferDataMsgValue) }) data!: ShieldingTransferDataMsgValue[]; - constructor({ data }: ShieldingTransferProps) { + constructor({ data, target }: ShieldingTransferProps) { Object.assign(this, { + target, data: data.map( (shieldingTransferDataProps) => new ShieldingTransferDataMsgValue(shieldingTransferDataProps) diff --git a/packages/types/src/tx/schema/utils.ts b/packages/types/src/tx/schema/utils.ts index df9c6dcba..3089fe733 100644 --- a/packages/types/src/tx/schema/utils.ts +++ b/packages/types/src/tx/schema/utils.ts @@ -1,9 +1,12 @@ +import { BinaryReader, BinaryWriter } from "@dao-xyz/borsh"; import BigNumber from "bignumber.js"; -import { BinaryWriter, BinaryReader } from "@dao-xyz/borsh"; export const BigNumberSerializer = { serialize: (value: BigNumber, writer: BinaryWriter) => { - writer.string(value.toString()); + console.log("BN", value); + const asd = Object.setPrototypeOf(value, BigNumber.prototype); + console.log("BN2", asd); + writer.string(asd.toString()); }, deserialize: (reader: BinaryReader): BigNumber => { const valueString = reader.string();