diff --git a/README.md b/README.md index cb5cfcd..f83f1fb 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,20 @@ npm i npm run dev ``` +### Local Development + +To develop against a local Miden node (default port `57291`), set the RPC endpoint +to the Vite dev server's origin in your `.env`: + +```bash +VITE_RPC_ENDPOINT=http://localhost:5173 +VITE_NETWORK_ID=localhost +``` + +This keeps gRPC-web requests same-origin, avoiding CORS issues. A dev-server +middleware in `vite.config.ts` automatically proxies these requests to +`localhost:57291`. + ## Resetting the MidenClientDB The Miden webclient stores account and note data in the browser. To clear the account and node data in the browser, paste this code snippet into the browser console: diff --git a/package-lock.json b/package-lock.json index 1da4b9a..ae74818 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,12 @@ "version": "0.3.0", "hasInstallScript": true, "dependencies": { - "@demox-labs/miden-sdk": "^0.12.5", "@demox-labs/miden-wallet-adapter": "0.10.0", "@getpara/react-sdk-lite": "^2.2.0", - "@miden-sdk/miden-para": "^0.10.10", - "@miden-sdk/use-miden-para-react": "^0.10.10", + "@miden-sdk/miden-para": "^0.13.0", + "@miden-sdk/miden-sdk": "^0.13.0", + "@miden-sdk/react": "^0.13.2", + "@miden-sdk/use-miden-para-react": "^0.13.0", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-slot": "^1.2.4", "@tanstack/react-query": "^5.90.12", @@ -47,6 +48,7 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", + "http-proxy-middleware": "^3.0.5", "postcss": "^8.5.6", "puppeteer": "^24.32.1", "tailwindcss": "^3.4.18", @@ -577,16 +579,6 @@ "readonly-date": "^1.0.0" } }, - "node_modules/@demox-labs/miden-sdk": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/@demox-labs/miden-sdk/-/miden-sdk-0.12.5.tgz", - "integrity": "sha512-Oq+MKObtOEY3aro+q1coVSc2RKrIycWbz9hBvnxqIlUkHzn6iL8X4+TG8t14co03cyuVt1dz1F13w0ScQSBMuA==", - "dependencies": { - "@rollup/plugin-typescript": "^12.3.0", - "dexie": "^4.0.1", - "glob": "^11.0.0" - } - }, "node_modules/@demox-labs/miden-wallet-adapter": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/@demox-labs/miden-wallet-adapter/-/miden-wallet-adapter-0.10.0.tgz", @@ -1913,6 +1905,34 @@ "react-dom": "*" } }, + "node_modules/@getpara/react-sdk-lite/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/@getpara/shared": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@getpara/shared/-/shared-1.9.0.tgz", @@ -2101,16 +2121,16 @@ } }, "node_modules/@miden-sdk/miden-para": { - "version": "0.10.10", - "resolved": "https://registry.npmjs.org/@miden-sdk/miden-para/-/miden-para-0.10.10.tgz", - "integrity": "sha512-3GIif8/OlRRpgJ2laLyIRhJrDyH5dMJ9Kpiqldpj0J6J7bIHEya962av961VuSL0bpHvWO+7YSIYeITgxSPf7g==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@miden-sdk/miden-para/-/miden-para-0.13.1.tgz", + "integrity": "sha512-rvFKIy/tmbd5+8KfiZLXBxcF7/P4SPA3rgQYTqVwyeDbkJG3+mysaTfXNNABdc9v03wsCnYkaTa0lE718GOcRA==", "license": "MIT", "dependencies": { "@noble/hashes": "^2.0.1" }, "peerDependencies": { - "@demox-labs/miden-sdk": "^0.12.5", - "@getpara/web-sdk": "2.0.0-alpha.73" + "@getpara/web-sdk": "2.0.0-alpha.73", + "@miden-sdk/miden-sdk": "^0.13.0" } }, "node_modules/@miden-sdk/miden-para/node_modules/@noble/hashes": { @@ -2125,18 +2145,44 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@miden-sdk/miden-sdk": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@miden-sdk/miden-sdk/-/miden-sdk-0.13.0.tgz", + "integrity": "sha512-N0qUCZW9Dvk3Oqj37IrGmm0b0v3Nq5qHsX3BtQIzZIwDXKXKPBxy/0lO40oCwDtwI8AfriZQyMLbJR81Fo4Vpg==", + "dependencies": { + "@rollup/plugin-typescript": "^12.3.0", + "dexie": "^4.0.1", + "glob": "^11.0.0" + } + }, + "node_modules/@miden-sdk/react": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@miden-sdk/react/-/react-0.13.2.tgz", + "integrity": "sha512-78i3/5YUUwitqvJA02HVXZ61RXEOyaiRsm1agVUtOblOmazuIgMEW4P3gdlX9YUiGr06l9O+Rw1ol5FZCOJTXQ==", + "license": "MIT", + "dependencies": { + "zustand": "^5.0.0" + }, + "peerDependencies": { + "@miden-sdk/miden-sdk": "^0.13.0", + "react": ">=18.0.0" + } + }, "node_modules/@miden-sdk/use-miden-para-react": { - "version": "0.10.10", - "resolved": "https://registry.npmjs.org/@miden-sdk/use-miden-para-react/-/use-miden-para-react-0.10.10.tgz", - "integrity": "sha512-GQS45cwvcsrCRP4u8mI5p4IZvWOeKRUqPesWoBlFO4AdIaB8oY5cGb6nx3FvMNKMFbXf+lZrX5AVG3jVIaGGCQ==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@miden-sdk/use-miden-para-react/-/use-miden-para-react-0.13.1.tgz", + "integrity": "sha512-b/sbG8CXOwedYFUCL/zr2fSugzEdt9mTYSl/2Cf+EUAOwp2JMxDLV5BfdZU9/soV7BIyBdaYXOlNkr+/fxshcw==", "license": "MIT", "engines": { "node": ">=18" }, "peerDependencies": { - "@demox-labs/miden-sdk": "^0.12.5", "@getpara/react-sdk-lite": "^2.2.0", - "@miden-sdk/miden-para": "^0.10.10", + "@getpara/web-sdk": "2.0.0-alpha.73", + "@miden-sdk/miden-para": "^0.13.0", + "@miden-sdk/miden-sdk": "^0.13.0", + "@miden-sdk/react": "^0.13.1", + "@tanstack/react-query": "^5.0.0", "react": "^18.0.0 || ^19.0.0" } }, @@ -3509,6 +3555,16 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, + "node_modules/@types/http-proxy": { + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -7069,6 +7125,7 @@ "version": "11.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "BlueOak-1.0.0", "dependencies": { "foreground-child": "^3.3.1", @@ -7307,6 +7364,21 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -7321,6 +7393,31 @@ "node": ">= 14" } }, + "node_modules/http-proxy-middleware": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz", + "integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.15", + "debug": "^4.3.6", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.3", + "is-plain-object": "^5.0.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/http-proxy/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, "node_modules/https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", @@ -7764,6 +7861,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -10909,6 +11016,13 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "license": "ISC" }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -13291,20 +13405,18 @@ } }, "node_modules/zustand": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", - "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.11.tgz", + "integrity": "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==", "license": "MIT", - "dependencies": { - "use-sync-external-store": "^1.2.2" - }, "engines": { - "node": ">=12.7.0" + "node": ">=12.20.0" }, "peerDependencies": { - "@types/react": ">=16.8", + "@types/react": ">=18.0.0", "immer": ">=9.0.6", - "react": ">=16.8" + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" }, "peerDependenciesMeta": { "@types/react": { @@ -13315,6 +13427,9 @@ }, "react": { "optional": true + }, + "use-sync-external-store": { + "optional": true } } }, diff --git a/package.json b/package.json index aaef6b6..4a033fe 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,12 @@ "postinstall": "setup-para" }, "dependencies": { - "@demox-labs/miden-sdk": "^0.12.5", "@demox-labs/miden-wallet-adapter": "0.10.0", "@getpara/react-sdk-lite": "^2.2.0", - "@miden-sdk/miden-para": "^0.10.10", - "@miden-sdk/use-miden-para-react": "^0.10.10", + "@miden-sdk/miden-para": "^0.13.0", + "@miden-sdk/miden-sdk": "^0.13.0", + "@miden-sdk/react": "^0.13.2", + "@miden-sdk/use-miden-para-react": "^0.13.0", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-slot": "^1.2.4", "@tanstack/react-query": "^5.90.12", @@ -52,6 +53,7 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", + "http-proxy-middleware": "^3.0.5", "postcss": "^8.5.6", "puppeteer": "^24.32.1", "tailwindcss": "^3.4.18", diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 44bcfa9..a26ab14 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -81,9 +81,18 @@ export function Footer() { > About us + + Developers +
- testnet v.12 + testnet 0.13
diff --git a/src/components/PoolModal.tsx b/src/components/PoolModal.tsx index 7747928..b571632 100644 --- a/src/components/PoolModal.tsx +++ b/src/components/PoolModal.tsx @@ -2,7 +2,7 @@ import { useDeposit } from '@/hooks/useDeposit'; import { useWithdraw } from '@/hooks/useWithdraw'; import { ZoroContext } from '@/providers/ZoroContext'; import type { TokenConfig } from '@/providers/ZoroProvider'; -import { NoteType } from '@demox-labs/miden-sdk'; +import { NoteType } from '@miden-sdk/miden-sdk'; import { Loader, X } from 'lucide-react'; import { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { parseUnits } from 'viem'; @@ -123,7 +123,11 @@ const PoolModal = ( if (token == null) return; await deposit({ amount: rawValue, - minAmountOut: BigInt(0), // rawValue, + + // TODO: This needs to be the correct LP amount, not token amount + // BigInt(rawValue * BigInt(1e8 - 1e6 * slippage) / BigInt(1e8)) + // find a way to simulate it properly + minAmountOut: BigInt(1), token, noteType: NoteType.Public, }); @@ -131,9 +135,13 @@ const PoolModal = ( const writeWithdraw = useCallback(async () => { if (token == null) return; + await withdraw({ amount: rawValue, - minAmountOut: rawValue, + // TODO: This needs to be the correct LP amount, not token amount + // BigInt(rawValue * BigInt(1e8 - 1e6 * slippage) / BigInt(1e8)) + // find a way to simulate it properly + minAmountOut: BigInt(1), token, noteType: NoteType.Public, }); diff --git a/src/hooks/useClaimNotes.ts b/src/hooks/useClaimNotes.ts index 4e52496..2b6e2af 100644 --- a/src/hooks/useClaimNotes.ts +++ b/src/hooks/useClaimNotes.ts @@ -36,13 +36,13 @@ export function useClaimNotes() { return { claimed: 0 }; } - // Get note IDs as strings - const noteIds = notes.map((n) => n.inputNoteRecord().id().toString()); + // Convert consumable note records to Note objects + const noteObjects = notes.map((n) => n.inputNoteRecord().toNote()); - console.log('useClaimNotes: consuming notes', noteIds); + console.log('useClaimNotes: consuming', noteObjects.length, 'notes'); // Consume the notes (locking handled internally) - const txHash = await consumeNotes(accountId, noteIds); + const txHash = await consumeNotes(accountId, noteObjects); console.log('useClaimNotes: transaction submitted', txHash); diff --git a/src/hooks/useDeposit.tsx b/src/hooks/useDeposit.tsx index 1d3c204..f303bd3 100644 --- a/src/hooks/useDeposit.tsx +++ b/src/hooks/useDeposit.tsx @@ -1,9 +1,10 @@ import { useUnifiedWallet } from '@/hooks/useUnifiedWallet'; +import { clientMutex } from '@/lib/clientMutex'; import { API } from '@/lib/config'; import { compileDepositTransaction } from '@/lib/ZoroDepositNote'; import { ZoroContext } from '@/providers/ZoroContext'; import { type TokenConfig } from '@/providers/ZoroProvider'; -import { NoteType } from '@demox-labs/miden-sdk'; +import { NoteType } from '@miden-sdk/miden-sdk'; import { useCallback, useContext, useMemo, useState } from 'react'; import { toast } from 'react-toastify'; @@ -32,17 +33,20 @@ export const useDeposit = () => { setError(''); setIsLoading(true); try { - const { tx, noteId, note } = await compileDepositTransaction({ - amount, - poolAccountId, - token, - minAmountOut: minAmountOut, - userAccountId: accountId, - client, - syncState, - noteType, - }); - const txId = await requestTransaction(tx); + await syncState(); + + const { tx, noteId, note } = await clientMutex.runExclusive(() => + compileDepositTransaction({ + amount, + poolAccountId, + token, + minAmountOut: minAmountOut, + userAccountId: accountId, + client, + noteType, + }), + ); + const txId = await requestTransaction({ type: 'Custom', payload: tx }); await syncState(); if (noteType === NoteType.Private) { const serialized = btoa( diff --git a/src/hooks/useLPBalances.ts b/src/hooks/useLPBalances.ts index 719d380..0e072bd 100644 --- a/src/hooks/useLPBalances.ts +++ b/src/hooks/useLPBalances.ts @@ -1,20 +1,23 @@ +import { bech32ToAccountId, accountIdToBech32 } from '@/lib/utils'; import { ZoroContext } from '@/providers/ZoroContext'; import type { TokenConfig } from '@/providers/ZoroProvider'; -import { Felt, Word } from '@demox-labs/miden-sdk'; +import { Felt, Word } from '@miden-sdk/miden-sdk'; import { useCallback, useContext, useEffect, useMemo, useState } from 'react'; export const useLPBalances = ({ tokens }: { tokens?: TokenConfig[] }) => { - const { client, poolAccountId, accountId, getAccount } = useContext(ZoroContext); + const { rpcClient, poolAccountId, accountId } = useContext(ZoroContext); const [balances, setBalances] = useState>({}); const refetch = useCallback(async () => { - if (!poolAccountId || !client || !accountId || !tokens) return; + if (!poolAccountId || !rpcClient || !accountId || !tokens) return; const balances: Record = {}; - const account = await getAccount(poolAccountId); - const storage = account?.storage(); + // Clone poolAccountId since getAccountDetails() consumes the AccountId argument + const poolIdClone = bech32ToAccountId(accountIdToBech32(poolAccountId))!; + const fetched = await rpcClient.getAccountDetails(poolIdClone); + const storage = fetched.account()?.storage(); for (const token of tokens) { const lp = storage?.getMapItem( - 5, + "zoroswap::user_deposits", Word.newFromFelts([ new Felt(accountId.suffix().asInt()), new Felt(accountId.prefix().asInt()), @@ -26,7 +29,7 @@ export const useLPBalances = ({ tokens }: { tokens?: TokenConfig[] }) => { balances[token.faucetIdBech32] = balance; } setBalances(balances); - }, [poolAccountId, client, accountId, tokens, getAccount]); + }, [poolAccountId, rpcClient, accountId, tokens]); useEffect(() => { // eslint-disable-next-line diff --git a/src/hooks/usePoolsBalances.tsx b/src/hooks/usePoolsBalances.tsx index a8bffe4..13ec543 100644 --- a/src/hooks/usePoolsBalances.tsx +++ b/src/hooks/usePoolsBalances.tsx @@ -1,6 +1,6 @@ import { API } from '@/lib/config'; import { bech32ToAccountId } from '@/lib/utils'; -import type { AccountId } from '@demox-labs/miden-sdk'; +import type { AccountId } from '@miden-sdk/miden-sdk'; import { useQuery } from '@tanstack/react-query'; import { useMemo } from 'react'; diff --git a/src/hooks/usePoolsInfo.tsx b/src/hooks/usePoolsInfo.tsx index 8809f81..7f2a6cf 100644 --- a/src/hooks/usePoolsInfo.tsx +++ b/src/hooks/usePoolsInfo.tsx @@ -1,6 +1,6 @@ import { API } from '@/lib/config'; import { bech32ToAccountId } from '@/lib/utils'; -import type { AccountId } from '@demox-labs/miden-sdk'; +import type { AccountId } from '@miden-sdk/miden-sdk'; import { useQuery } from '@tanstack/react-query'; import { useMemo } from 'react'; diff --git a/src/hooks/usePoolsSettings.tsx b/src/hooks/usePoolsSettings.tsx index 3ba2d1e..0bf8a1d 100644 --- a/src/hooks/usePoolsSettings.tsx +++ b/src/hooks/usePoolsSettings.tsx @@ -1,6 +1,6 @@ import { API } from '@/lib/config'; import { bech32ToAccountId } from '@/lib/utils'; -import type { AccountId } from '@demox-labs/miden-sdk'; +import type { AccountId } from '@miden-sdk/miden-sdk'; import { useQuery } from '@tanstack/react-query'; import { useMemo } from 'react'; diff --git a/src/hooks/useSwap.tsx b/src/hooks/useSwap.tsx index 234b003..12e98de 100644 --- a/src/hooks/useSwap.tsx +++ b/src/hooks/useSwap.tsx @@ -35,7 +35,7 @@ export const useSwap = () => { // Sync state before compilation (locking handled internally) await syncState(); - // Compilation uses client.createScriptBuilder(), hence we must + // Compilation uses client.createCodeBuilder(), hence we must // use our mutex. const { tx, noteId: newNoteId } = await clientMutex.runExclusive(() => compileSwapTransaction({ diff --git a/src/hooks/useWithdraw.tsx b/src/hooks/useWithdraw.tsx index 2441959..d88ec1b 100644 --- a/src/hooks/useWithdraw.tsx +++ b/src/hooks/useWithdraw.tsx @@ -1,9 +1,10 @@ import { useUnifiedWallet } from '@/hooks/useUnifiedWallet'; +import { clientMutex } from '@/lib/clientMutex'; import { API } from '@/lib/config'; import { compileWithdrawTransaction } from '@/lib/ZoroWithdrawNote'; import { ZoroContext } from '@/providers/ZoroContext'; import { type TokenConfig } from '@/providers/ZoroProvider'; -import { NoteType } from '@demox-labs/miden-sdk'; +import { NoteType } from '@miden-sdk/miden-sdk'; import { useCallback, useContext, useMemo, useState } from 'react'; import { toast } from 'react-toastify'; @@ -32,17 +33,20 @@ export const useWithdraw = () => { setError(''); setIsLoading(true); try { - const { tx, noteId, note } = await compileWithdrawTransaction({ - amount, - poolAccountId, - token, - minAmountOut: minAmountOut, - userAccountId: accountId, - client, - syncState, - noteType, - }); - const txId = await requestTransaction(tx); + await syncState(); + + const { tx, noteId, note } = await clientMutex.runExclusive(() => + compileWithdrawTransaction({ + amount, + poolAccountId, + token, + minAmountOut: minAmountOut, + userAccountId: accountId, + client, + noteType, + }), + ); + const txId = await requestTransaction({ type: 'Custom', payload: tx }); await syncState(); if (noteType === NoteType.Private) { diff --git a/src/lib/DEPOSIT.masm b/src/lib/DEPOSIT.masm index 51e16ac..b07c715 100644 --- a/src/lib/DEPOSIT.masm +++ b/src/lib/DEPOSIT.masm @@ -1,24 +1,23 @@ -use.miden::active_note -use.miden::output_note -use.std::crypto::hashes::rpo -use.std::sys +use miden::protocol::active_note +use miden::protocol::output_note +use miden::core::crypto::hashes::rpo256 +use miden::core::sys -use.zoro::zoropool +use zoroswap::zoropool # ERRORS # ================================================================================================= # DEPOSIT script expects exactly 9 note inputs -const.ERR_DEPOSIT_WRONG_NUMBER_OF_INPUTS="DEPOSIT wrong number of inputs" +const ERR_DEPOSIT_WRONG_NUMBER_OF_INPUTS="DEPOSIT wrong number of inputs" # DEPOSIT script requires exactly one note asset -const.ERR_DEPOSIT_WRONG_NUMBER_OF_ASSETS="DEPOSIT wrong number of assets" +const ERR_DEPOSIT_WRONG_NUMBER_OF_ASSETS="DEPOSIT wrong number of assets" # CONSTANTS # ================================================================================================= -const.PUBLIC_NOTE=1 -const.EXECUTION_HINT_ALWAYS=1 +const PUBLIC_NOTE=1 # MEMORY # ================================================================================================= @@ -30,39 +29,39 @@ const.EXECUTION_HINT_ALWAYS=1 # next 4 WORDS reserved for arguments # ==================================================== -const.INPUTS_POINTER = 0x0010 +const INPUTS_POINTER = 0x0010 -const.INPUTS_WORD_0 = INPUTS_POINTER # [min_lp_amount_out, deadline, p2id_tag, empty] -const.INPUTS_WORD_1 = INPUTS_POINTER + 4 # [CREATOR_ID_WORD] = [empty, empty, creator_id_suffix, creator_id_prefix] +const INPUTS_WORD_0 = INPUTS_POINTER # [min_lp_amount_out, deadline, p2id_tag, empty] +const INPUTS_WORD_1 = INPUTS_POINTER + 4 # [CREATOR_ID_WORD] = [empty, empty, creator_id_suffix, creator_id_prefix] # first 2 WORDS of input space used for inputs # ==================================================== -const.NUMBER_OF_INPUTS = 0x0008 # 8 +const NUMBER_OF_INPUTS = 0x0008 # 8 # semantic names for inputs # ==================================================== # [min_lp_amount_outdeadline, p2id_tag, empty, empty] -const.INPUT_PARAMS = INPUTS_WORD_0 -const.CREATOR_ID_WORD = INPUTS_WORD_1 +const INPUT_PARAMS = INPUTS_WORD_0 +const CREATOR_ID_WORD = INPUTS_WORD_1 # DEPOSIT Note Inputs (16 to 24) # [min_amount_out, empty, out_asset_id_suffix, out_asset_id_prefix] -const.MIN_LP_AMOUNT_OUT = INPUT_PARAMS # 0x0010 -const.DEADLINE = INPUT_PARAMS + 1 -const.P2ID_TAG = INPUT_PARAMS + 2 +const MIN_LP_AMOUNT_OUT = INPUT_PARAMS # 0x0010 +const DEADLINE = INPUT_PARAMS + 1 +const P2ID_TAG = INPUT_PARAMS + 2 # [empty, empty, creator_id_suffix, creator_id_prefix] -const.EMPTY_INPUT_4 = CREATOR_ID_WORD # 0x0018 -const.EMPTY_INPUT_5 = CREATOR_ID_WORD + 1 -const.DEPOSIT_CREATOR_ID_SUFFIX = CREATOR_ID_WORD + 2 -const.DEPOSIT_CREATOR_ID_PREFIX = CREATOR_ID_WORD + 3 # 0x001B +const EMPTY_INPUT_4 = CREATOR_ID_WORD # 0x0018 +const EMPTY_INPUT_5 = CREATOR_ID_WORD + 1 +const DEPOSIT_CREATOR_ID_SUFFIX = CREATOR_ID_WORD + 2 +const DEPOSIT_CREATOR_ID_PREFIX = CREATOR_ID_WORD + 3 # 0x001B # EMPTY 5WORDS 0X001C- 0x00 @@ -70,45 +69,45 @@ const.DEPOSIT_CREATOR_ID_PREFIX = CREATOR_ID_WORD + 3 # 0x001B # 0x0040 - 0x004F # ==================================================== -const.ARGS_POINTER = 0x0040 +const ARGS_POINTER = 0x0040 -const.ARGS_WORD_0 = ARGS_POINTER # [] +const ARGS_WORD_0 = ARGS_POINTER # [] # first 3 WORDS of ARGUMENTS space used for args # ==================================================== -const.NUMBER_OF_ARGS = 0x000C # 4 +const NUMBER_OF_ARGS = 0x000C # 4 # semantic names for inputs # ==================================================== -const.LP_AMOUNT_OUT = ARGS_WORD_0 # 0x0040 -const.LIABILITIES = ARGS_WORD_0 + 1 # 0x0041 -const.RESERVE = ARGS_WORD_0 + 2 # 0x0042 -const.RESERVE_WITH_SLIPPAGE = ARGS_WORD_0 + 3 # 0x0043 +const LP_AMOUNT_OUT = ARGS_WORD_0 # 0x0040 +const LIABILITIES = ARGS_WORD_0 + 1 # 0x0041 +const RESERVE = ARGS_WORD_0 + 2 # 0x0042 +const RESERVE_WITH_SLIPPAGE = ARGS_WORD_0 + 3 # 0x0043 # memory addresses of the assets -const.DEPOSIT_ASSET_WORD = 0x0060 +const DEPOSIT_ASSET_WORD = 0x0060 # memory locations based on IN_ASSET_WORD -const.DEPOSIT_AMOUNT = DEPOSIT_ASSET_WORD -const.DEPOSIT_ASSET_EMPTY_FELT = DEPOSIT_ASSET_WORD + 1 -const.DEPOSIT_ASSET_ID_PREFIX = DEPOSIT_ASSET_WORD + 3 -const.DEPOSIT_ASSET_ID_SUFFIX = DEPOSIT_ASSET_WORD + 2 +const DEPOSIT_AMOUNT = DEPOSIT_ASSET_WORD +const DEPOSIT_ASSET_EMPTY_FELT = DEPOSIT_ASSET_WORD + 1 +const DEPOSIT_ASSET_ID_PREFIX = DEPOSIT_ASSET_WORD + 3 +const DEPOSIT_ASSET_ID_SUFFIX = DEPOSIT_ASSET_WORD + 2 # "constant" MEMORY # ================================================================================================= -const.P2ID_SCRIPT_ROOT_WORD = 0x0080 +const P2ID_SCRIPT_ROOT_WORD = 0x0080 -proc.store_asset_in_to_memory +proc store_asset_in_to_memory push.DEPOSIT_ASSET_WORD exec.active_note::get_assets assert.err=ERR_DEPOSIT_WRONG_NUMBER_OF_ASSETS drop end -proc.store_inputs_to_memory +proc store_inputs_to_memory # store note inputs into memory starting at address 0 push.INPUTS_POINTER exec.active_note::get_inputs # => [num_inputs, inputs_ptr] @@ -119,7 +118,7 @@ proc.store_inputs_to_memory end # @note TODO: pool stats will likely need to go through the advice stack -proc.store_args_to_memory +proc store_args_to_memory mem_store.LIABILITIES mem_store.RESERVE mem_store.RESERVE_WITH_SLIPPAGE @@ -132,30 +131,41 @@ end #! Inputs: [SERIAL_NUM, SCRIPT_HASH] #! Outputs: [P2ID_RECIPIENT] #! -proc.build_p2id_recipient_hash +proc build_p2id_recipient_hash padw hmerge # => [SERIAL_NUM_HASH, SCRIPT_HASH] swapw hmerge # => [SERIAL_SCRIPT_HASH] - padw - mem_load.DEPOSIT_CREATOR_ID_SUFFIX mem_load.DEPOSIT_CREATOR_ID_PREFIX + + # Compute inputs commitment: hash_elements([suffix, prefix]) + # Must match build_p2id_recipient() from miden_standards which internally + # computes NoteInputs::new(vec![suffix, prefix]).commitment() via + # Rpo256::hash_elements(&[suffix, prefix]) + # + # mem_storew_le stores in little-endian: stack[0]→pos0, stack[1]→pos1, etc. + # hash_elements (via mem_stream) reads pos0 first → rate[0], pos1 → rate[1] + # So we need suffix on top (it goes to pos0) and prefix below (pos1) + # padw push.0.0 - push.4000 mem_storew_be dropw - push.4004 mem_storew_be dropw + mem_load.DEPOSIT_CREATOR_ID_PREFIX mem_load.DEPOSIT_CREATOR_ID_SUFFIX + + push.4000 mem_storew_le dropw + # => mem[4000] pos0=suffix, pos1=prefix (LE), stack=[SERIAL_SCRIPT_HASH] - push.8.4000 - # => [ptr, elements] - exec.rpo::hash_memory - # => [INPUTS_HASH, SERIAL_SCRIPT_HASH] + push.2.4000 + # => [ptr=4000, num_elements=2, SERIAL_SCRIPT_HASH] + exec.rpo256::hash_elements + # => [INPUTS_COMMITMENT, SERIAL_SCRIPT_HASH] hmerge # => [P2ID_RECIPIENT] + end # input: [SERIAL_NUM,...] # ouput: [P2ID_SERIAL_NUM, ...] -proc.get_p2id_serial_num +proc get_p2id_serial_num add.1 end @@ -164,11 +174,12 @@ end #! Inputs: [ASSET_TO_BE_SENT] #! Outputs: [] #! -proc.create_p2id_reverse_note - # @dev prepad to not to destroy ASSET_TO_BE_SENT by note creation - # @dev if doesnt work, use memory for ASSET_TO_BE_SENT - padw padw - # => [pad(8), ASSET_TO_BE_SENT] +proc create_p2id_reverse_note + # prepad to keep ASSET_TO_BE_SENT below the 16-element call frame + # We need 16 elements above ASSET before call.output_note::create: + # tag(1) + note_type(1) + RECIPIENT(4) + pad(10) = 16 + padw padw push.0.0 + # => [pad(10), ASSET_TO_BE_SENT] padw mem_loadw_be.P2ID_SCRIPT_ROOT_WORD # => [P2ID_SCRIPT_HASH, pad(8), ASSET_TO_BE_SENT] exec.active_note::get_serial_number @@ -178,16 +189,10 @@ proc.create_p2id_reverse_note exec.build_p2id_recipient_hash # => [P2ID_RECIPIENT, pad(8), ASSET_TO_BE_SENT] - - push.EXECUTION_HINT_ALWAYS - # => [execution_hint_always, P2ID_RECIPIENT, pad(8), ASSET_TO_BE_SENT] push.PUBLIC_NOTE - # => [public_note, execution_hint_always, P2ID_RECIPIENT, pad(8), ASSET_TO_BE_SENT] - push.0 # @dev aux for p2id output note - # => [aux, public_note, execution_hint_always, P2ID_RECIPIENT, pad(8), ASSET_TO_BE_SENT] + # => [note_type, P2ID_RECIPIENT, pad(8), ASSET_TO_BE_SENT] mem_load.P2ID_TAG - # => [tag, aux, public_note, execution_hint_always, P2ID_RECIPIENT, pad(8), ASSET_TO_BE_SENT] - # => [tag, aux, note_type, execution_hint, RECIPIENT, pad(8), ASSET_TO_BE_SENT] + # => [tag, note_type, P2ID_RECIPIENT, pad(8), ASSET_TO_BE_SENT] call.output_note::create # => [note_idx, pad(15), ASSET_TO_BE_SENT] swapw.2 dropw swapw.3 @@ -196,26 +201,26 @@ proc.create_p2id_reverse_note swapw.2 swapw - call.zoropool::bounce_asset # => [ASSET, note_idx, pad(3), NEW_OUT_POOL_STATE, pad(4)] - dropw dropw drop + # dropw dropw drop # => [] end -proc.store_p2id_script_hash - push.15783632360113277539.7403765918285273520.15691985194755641846.10399643920503194563 +proc store_p2id_script_hash + # P2ID script root from miden-standards v0.13 WellKnownNote::P2ID.script_root() + push.13362761878458161062.15090726097241769395.444910447169617901.3558201871398422326 mem_storew_be.P2ID_SCRIPT_ROOT_WORD dropw end -proc.execute_DEPOSIT +proc execute_DEPOSIT mem_load.LP_AMOUNT_OUT mem_load.MIN_LP_AMOUNT_OUT lt # min amount out is not met - if.true mem_loadw_be.DEPOSIT_ASSET_WORD exec.create_p2id_reverse_note + call.zoropool::bounce_asset else mem_load.LP_AMOUNT_OUT mem_load.DEPOSIT_CREATOR_ID_SUFFIX mem_load.DEPOSIT_CREATOR_ID_PREFIX # => [creator_id_prefix, creator_id_suffix] @@ -224,27 +229,18 @@ proc.execute_DEPOSIT push.0.0.0.0 mem_loadw_be.DEPOSIT_ASSET_WORD # => [ASSET, liabilities,reserve, reserve_with_slippage, 0, creator_id_prefix, creator_id_suffix] - call.zoropool::deposit - end - - #mem_load.POOL0_RESERVE_WITH_SLIPPAGE mem_load.POOL0_RESERVE mem_load.POOL0_LIABILITIES #call.zoropool::set_pool0_state end begin exec.store_args_to_memory - exec.store_inputs_to_memory - exec.store_asset_in_to_memory - exec.store_p2id_script_hash - exec.execute_DEPOSIT - exec.sys::truncate_stack end diff --git a/src/lib/WITHDRAW.masm b/src/lib/WITHDRAW.masm index 55ae5f0..2d249c0 100644 --- a/src/lib/WITHDRAW.masm +++ b/src/lib/WITHDRAW.masm @@ -1,26 +1,25 @@ -use.miden::active_note -use.miden::output_note -use.std::crypto::hashes::rpo -use.std::sys +use miden::protocol::active_note +use miden::protocol::output_note +use miden::core::crypto::hashes::rpo256 +use miden::core::sys -use.zoro::zoropool +use zoroswap::zoropool # ERRORS # ================================================================================================= # WITHDRAW script expects exactly 9 note inputs -const.ERR_WITHDRAW_WRONG_NUMBER_OF_INPUTS="WITHDRAW wrong number of inputs" +const ERR_WITHDRAW_WRONG_NUMBER_OF_INPUTS="WITHDRAW wrong number of inputs" # WITHDRAW script requires exactly one note asset -const.ERR_WITHDRAW_WRONG_NUMBER_OF_ASSETS="WITHDRAW wrong number of assets" -const.ERR_WITHDRAW_MIN_ASSET_OUT_NOT_MET="WITHDRAW min asset out not met" +const ERR_WITHDRAW_WRONG_NUMBER_OF_ASSETS="WITHDRAW wrong number of assets" +const ERR_WITHDRAW_MIN_ASSET_OUT_NOT_MET="WITHDRAW min asset out not met" # CONSTANTS # ================================================================================================= -const.PUBLIC_NOTE=1 -const.EXECUTION_HINT_ALWAYS=1 +const PUBLIC_NOTE=1 # MEMORY # ================================================================================================= @@ -32,45 +31,45 @@ const.EXECUTION_HINT_ALWAYS=1 # next 4 WORDS reserved for arguments # ==================================================== -const.INPUTS_POINTER = 0x0010 +const INPUTS_POINTER = 0x0010 -const.INPUTS_WORD_0 = INPUTS_POINTER # [MIN_ASSET_OUT] -const.INPUTS_WORD_1 = INPUTS_POINTER + 4 # [PARAMS] = [lp_withdraw_amount, deadline, p2id_tag, empty] -const.INPUTS_WORD_2 = INPUTS_POINTER + 8 # [CREATOR_ID_WORD] = [empty, empty, creator_id_suffix, creator_id_prefix] +const INPUTS_WORD_0 = INPUTS_POINTER # [MIN_ASSET_OUT] +const INPUTS_WORD_1 = INPUTS_POINTER + 4 # [PARAMS] = [lp_withdraw_amount, deadline, p2id_tag, empty] +const INPUTS_WORD_2 = INPUTS_POINTER + 8 # [CREATOR_ID_WORD] = [empty, empty, creator_id_suffix, creator_id_prefix] # first 2 WORDS of input space used for inputs # ==================================================== -const.NUMBER_OF_INPUTS = 0x000C # 12 +const NUMBER_OF_INPUTS = 0x000C # 12 # semantic names for inputs # ==================================================== # [min_lp_amount_outdeadline, p2id_tag, empty, empty] -const.MIN_ASSET_OUT= INPUTS_WORD_0 -const.INPUT_PARAMS = INPUTS_WORD_1 -const.CREATOR_ID_WORD = INPUTS_WORD_2 +const MIN_ASSET_OUT= INPUTS_WORD_0 +const INPUT_PARAMS = INPUTS_WORD_1 +const CREATOR_ID_WORD = INPUTS_WORD_2 # WITHDRAW Note Inputs (10 to 22) # [asset_id_prefix, asset_id_suffix, empty, min_asset_amount_out] -const.ASSET_ID_PREFIX = MIN_ASSET_OUT + 3 -const.ASSET_ID_SUFFIX = MIN_ASSET_OUT + 2 -const.MIN_ASSET_AMOUNT_OUT = MIN_ASSET_OUT +const ASSET_ID_PREFIX = MIN_ASSET_OUT + 3 +const ASSET_ID_SUFFIX = MIN_ASSET_OUT + 2 +const MIN_ASSET_AMOUNT_OUT = MIN_ASSET_OUT # [lp_withdraw_amount, deadline, p2id_tag, 0] -const.P2ID_TAG = INPUT_PARAMS + 3 -const.DEADLINE = INPUT_PARAMS + 2 -const.LP_WITHDRAW_AMOUNT = INPUT_PARAMS + 1# 0x0010 +const P2ID_TAG = INPUT_PARAMS + 3 +const DEADLINE = INPUT_PARAMS + 2 +const LP_WITHDRAW_AMOUNT = INPUT_PARAMS + 1# 0x0010 # [empty, empty, creator_id_suffix, creator_id_prefix] -const.CREATOR_ID_PREFIX = CREATOR_ID_WORD + 3 -const.CREATOR_ID_SUFFIX = CREATOR_ID_WORD + 2 +const CREATOR_ID_PREFIX = CREATOR_ID_WORD + 3 +const CREATOR_ID_SUFFIX = CREATOR_ID_WORD + 2 # EMPTY 5WORDS 0X001C- 0x00 @@ -79,31 +78,31 @@ const.CREATOR_ID_SUFFIX = CREATOR_ID_WORD + 2 # 0x0040 - 0x004F # ==================================================== -const.ARGS_POINTER = 0x0040 +const ARGS_POINTER = 0x0040 -const.ARGS_WORD_0 = ARGS_POINTER # [] +const ARGS_WORD_0 = ARGS_POINTER # [] # first 3 WORDS of ARGUMENTS space used for args # ==================================================== -const.NUMBER_OF_ARGS = 0x000C # 4 +const NUMBER_OF_ARGS = 0x000C # 4 # semantic names for inputs # ==================================================== -const.WITHDRAW_AMOUNT_OUT = ARGS_WORD_0 # 0x0040 -const.LIABILITIES = ARGS_WORD_0 + 1 # 0x0041 -const.RESERVE = ARGS_WORD_0 + 2 # 0x0042 -const.RESERVE_WITH_SLIPPAGE = ARGS_WORD_0 + 3 # 0x0043 +const WITHDRAW_AMOUNT_OUT = ARGS_WORD_0 # 0x0040 +const LIABILITIES = ARGS_WORD_0 + 1 # 0x0041 +const RESERVE = ARGS_WORD_0 + 2 # 0x0042 +const RESERVE_WITH_SLIPPAGE = ARGS_WORD_0 + 3 # 0x0043 # "constant" MEMORY # ================================================================================================= -const.P2ID_SCRIPT_ROOT_WORD = 0x0080 +const P2ID_SCRIPT_ROOT_WORD = 0x0080 -proc.store_inputs_to_memory +proc store_inputs_to_memory # store note inputs into memory starting at address 0 push.INPUTS_POINTER exec.active_note::get_inputs # => [num_inputs, inputs_ptr] @@ -113,7 +112,7 @@ proc.store_inputs_to_memory drop end # @note TODO: pool stats will likely need to go through the advice stack -proc.store_args_to_memory +proc store_args_to_memory mem_store.LIABILITIES mem_store.RESERVE mem_store.RESERVE_WITH_SLIPPAGE @@ -126,30 +125,33 @@ end #! Inputs: [SERIAL_NUM, SCRIPT_HASH] #! Outputs: [P2ID_RECIPIENT] #! -proc.build_p2id_recipient_hash +proc build_p2id_recipient_hash padw hmerge # => [SERIAL_NUM_HASH, SCRIPT_HASH] swapw hmerge # => [SERIAL_SCRIPT_HASH] - padw - mem_load.CREATOR_ID_SUFFIX mem_load.CREATOR_ID_PREFIX + + # Compute inputs commitment: hash_elements([suffix, prefix]) + # Must match build_p2id_recipient() from miden_standards which internally + # computes NoteInputs::new(vec![suffix, prefix]).commitment() push.0.0 + mem_load.CREATOR_ID_PREFIX mem_load.CREATOR_ID_SUFFIX + # => [suffix, prefix, 0, 0, SERIAL_SCRIPT_HASH] - push.4000 mem_storew_be dropw - push.4004 mem_storew_be dropw + push.4000 mem_storew_le dropw + # => mem[4000] pos0=suffix, pos1=prefix (LE), stack=[SERIAL_SCRIPT_HASH] - push.8.4000 - # => [ptr, elements] - exec.rpo::hash_memory - # => [INPUTS_HASH, SERIAL_SCRIPT_HASH] + push.2.4000 + exec.rpo256::hash_elements + # => [INPUTS_COMMITMENT, SERIAL_SCRIPT_HASH] hmerge # => [P2ID_RECIPIENT] end # input: [SERIAL_NUM,...] # ouput: [P2ID_SERIAL_NUM, ...] -proc.get_p2id_serial_num +proc get_p2id_serial_num add.1 end @@ -158,7 +160,7 @@ end #! Inputs: [0, 0, 0,liabilities,reserve, reserve,reserve_with_slippage, 0]] #! Outputs: [] #! -proc.create_p2id_reverse_note +proc create_p2id_reverse_note padw padw padw padw # => [pad(8)] padw mem_loadw_be.P2ID_SCRIPT_ROOT_WORD @@ -171,21 +173,13 @@ proc.create_p2id_reverse_note # => [P2ID_RECIPIENT, pad(8)] - push.EXECUTION_HINT_ALWAYS - # => [execution_hint_always, P2ID_RECIPIENT, pad(8)] push.PUBLIC_NOTE - # => [public_note, execution_hint_always, P2ID_RECIPIENT, pad(8)] - push.0 # @dev aux for p2id output note - # => [aux, public_note, execution_hint_always, P2ID_RECIPIENT, pad(8)] + # => [note_type, P2ID_RECIPIENT, pad(8)] mem_load.P2ID_TAG - # => [tag, aux, public_note, execution_hint_always, P2ID_RECIPIENT, pad(8)] - # => [tag, aux, note_type, execution_hint, RECIPIENT, pad(8)] + # => [tag, note_type, P2ID_RECIPIENT, pad(8)] call.output_note::create - debug.stack.16 mem_load.RESERVE_WITH_SLIPPAGE mem_load.RESERVE mem_load.LIABILITIES - debug.stack.16 movup.4 push.0.0.0 swap.3 - debug.stack.16 # => [note_idx, 0, 0, 0, liabilities,reserve, reserve,reserve_with_slippage] mem_load.WITHDRAW_AMOUNT_OUT push.0 @@ -194,18 +188,18 @@ proc.create_p2id_reverse_note # => [ASSET, note_idx, 0, 0, 0, liabilities,reserve, reserve,reserve_with_slippage] push.0 mem_load.LP_WITHDRAW_AMOUNT mem_load.CREATOR_ID_SUFFIX mem_load.CREATOR_ID_PREFIX # => [user_id_prefix, user_id_suffix, lp_withdraw_amount, 0, ASSET, noteid, 0,0,0, liabilities, reserve, reserve_with_slippage] - debug.stack.32 call.zoropool::withdraw # => [] end -proc.store_p2id_script_hash - push.15783632360113277539.7403765918285273520.15691985194755641846.10399643920503194563 +proc store_p2id_script_hash + # P2ID script root from miden-standards v0.13 WellKnownNote::P2ID.script_root() + push.13362761878458161062.15090726097241769395.444910447169617901.3558201871398422326 mem_storew_be.P2ID_SCRIPT_ROOT_WORD dropw end -proc.execute_WITHDRAW +proc execute_WITHDRAW mem_load.MIN_ASSET_AMOUNT_OUT mem_load.WITHDRAW_AMOUNT_OUT drop drop #assert.err=ERR_WITHDRAW_MIN_ASSET_OUT_NOT_MET @@ -219,13 +213,11 @@ proc.execute_WITHDRAW push.0.0.0 # => [0, 0, 0,liabilities,reserve, reserve,reserve_with_slippage, 0] - debug.stack.16 exec.create_p2id_reverse_note end begin -debug.stack.9 exec.store_args_to_memory exec.store_inputs_to_memory diff --git a/src/lib/ZOROSWAP.masm b/src/lib/ZOROSWAP.masm index 1fdb2e7..7b20a10 100644 --- a/src/lib/ZOROSWAP.masm +++ b/src/lib/ZOROSWAP.masm @@ -1,24 +1,23 @@ -use.miden::active_note -use.miden::output_note -use.std::crypto::hashes::rpo -use.std::sys +use miden::protocol::active_note +use miden::protocol::output_note +use miden::core::crypto::hashes::rpo256 +use miden::core::sys -use.zoro::zoropool +use zoroswap::zoropool # ERRORS # ================================================================================================= # ZOROSWAP script expects exactly 9 note inputs -const.ERR_ZOROSWAP_WRONG_NUMBER_OF_INPUTS="ZOROSWAP wrong number of inputs" +const ERR_ZOROSWAP_WRONG_NUMBER_OF_INPUTS="ZOROSWAP wrong number of inputs" # ZOROSWAP script requires exactly one note asset -const.ERR_ZOROSWAP_WRONG_NUMBER_OF_ASSETS="ZOROSWAP wrong number of assets" +const ERR_ZOROSWAP_WRONG_NUMBER_OF_ASSETS="ZOROSWAP wrong number of assets" # CONSTANTS # ================================================================================================= -const.PUBLIC_NOTE=1 -const.EXECUTION_HINT_ALWAYS=1 +const PUBLIC_NOTE=1 # MEMORY # ================================================================================================= @@ -30,32 +29,32 @@ const.EXECUTION_HINT_ALWAYS=1 # next 4 WORDS reserved for arguments # ==================================================== -const.INPUTS_POINTER = 0x0010 +const INPUTS_POINTER = 0x0010 -const.INPUTS_WORD_0 = INPUTS_POINTER # [REQUESTED_ASSET_WORD] -const.INPUTS_WORD_1 = INPUTS_POINTER + 4 # [deadline, p2id_tag, empty, empty] -const.INPUTS_WORD_2 = INPUTS_POINTER + 8 # [CREATOR_ID_WORD] = [empty, empty, creator_id_suffix, creator_id_prefix] -const.INPUTS_WORD_3 = INPUTS_POINTER + 12 # [EMPTY] -const.INPUTS_WORD_4 = INPUTS_POINTER + 16 # [EMPTY] -const.INPUTS_WORD_5 = INPUTS_POINTER + 20 # [EMPTY] -const.INPUTS_WORD_6 = INPUTS_POINTER + 24 # [EMPTY] -const.INPUTS_WORD_7 = INPUTS_POINTER + 28 # [EMPTY] -const.INPUTS_WORD_8 = INPUTS_POINTER + 32 # [EMPTY] +const INPUTS_WORD_0 = INPUTS_POINTER # [REQUESTED_ASSET_WORD] +const INPUTS_WORD_1 = INPUTS_POINTER + 4 # [deadline, p2id_tag, empty, empty] +const INPUTS_WORD_2 = INPUTS_POINTER + 8 # [CREATOR_ID_WORD] = [empty, empty, creator_id_suffix, creator_id_prefix] +const INPUTS_WORD_3 = INPUTS_POINTER + 12 # [EMPTY] +const INPUTS_WORD_4 = INPUTS_POINTER + 16 # [EMPTY] +const INPUTS_WORD_5 = INPUTS_POINTER + 20 # [EMPTY] +const INPUTS_WORD_6 = INPUTS_POINTER + 24 # [EMPTY] +const INPUTS_WORD_7 = INPUTS_POINTER + 28 # [EMPTY] +const INPUTS_WORD_8 = INPUTS_POINTER + 32 # [EMPTY] # first 3 WORDS of input space used for inputs # ==================================================== -const.NUMBER_OF_INPUTS = 0x000C # 12 +const NUMBER_OF_INPUTS = 0x000C # 12 # semantic names for inputs # ==================================================== -const.REQUESTED_ASSET_WORD = INPUTS_WORD_0 +const REQUESTED_ASSET_WORD = INPUTS_WORD_0 # [deadline, p2id_tag, empty, empty] -const.CREATOR_ID_WORD = INPUTS_WORD_2 -const.INPUTS_EMPTY_WORD_0 = INPUTS_WORD_3 +const CREATOR_ID_WORD = INPUTS_WORD_2 +const INPUTS_EMPTY_WORD_0 = INPUTS_WORD_3 # [EMPTY] # [EMPTY] # [EMPTY] @@ -63,22 +62,22 @@ const.INPUTS_EMPTY_WORD_0 = INPUTS_WORD_3 # ZOROSWAP Note Inputs (16 to 28) # [min_amount_out, empty, out_asset_id_suffix, out_asset_id_prefix] -const.MIN_AMOUNT_OUT = REQUESTED_ASSET_WORD # 0x0010 -const.OUT_TOKEN_EMPTY_FELT = REQUESTED_ASSET_WORD + 1 -const.OUT_TOKEN_ID_SUFFIX = REQUESTED_ASSET_WORD + 2 -const.OUT_TOKEN_ID_PREFIX = REQUESTED_ASSET_WORD + 3 # 0x0013 +const MIN_AMOUNT_OUT = REQUESTED_ASSET_WORD # 0x0010 +const OUT_TOKEN_EMPTY_FELT = REQUESTED_ASSET_WORD + 1 +const OUT_TOKEN_ID_SUFFIX = REQUESTED_ASSET_WORD + 2 +const OUT_TOKEN_ID_PREFIX = REQUESTED_ASSET_WORD + 3 # 0x0013 # [deadline, p2id_tag, empty, empty] -const.DEADLINE = INPUTS_WORD_1 # 0x0014 -const.P2ID_TAG = INPUTS_WORD_1 + 1 -const.EMPTY_INPUT_6 = INPUTS_WORD_1 + 2 -const.EMPTY_INPUT_7 = INPUTS_WORD_1 + 3 # 0x0017 +const DEADLINE = INPUTS_WORD_1 # 0x0014 +const P2ID_TAG = INPUTS_WORD_1 + 1 +const EMPTY_INPUT_6 = INPUTS_WORD_1 + 2 +const EMPTY_INPUT_7 = INPUTS_WORD_1 + 3 # 0x0017 -# [empty, empty, creator_id_suffix, creator_id_prefix] -const.EMPTY_INPUT_8 = CREATOR_ID_WORD # 0x0018 -const.EMPTY_INPUT_9 = CREATOR_ID_WORD + 1 -const.ZOROSWAP_CREATOR_ID_SUFFIX = CREATOR_ID_WORD + 2 -const.ZOROSWAP_CREATOR_ID_PREFIX = CREATOR_ID_WORD + 3 # 0x001B +# [beneficiary_id_suffix, beneficiary_id_prefix, creator_id_suffix, creator_id_prefix] +const BENEFICIARY_ID_SUFFIX = CREATOR_ID_WORD # 0x0018 +const BENEFICIARY_ID_PREFIX = CREATOR_ID_WORD + 1 +const ZOROSWAP_CREATOR_ID_SUFFIX = CREATOR_ID_WORD + 2 +const ZOROSWAP_CREATOR_ID_PREFIX = CREATOR_ID_WORD + 3 # 0x001B # EMPTY 5WORDS 0X001C- 0x00 @@ -86,42 +85,42 @@ const.ZOROSWAP_CREATOR_ID_PREFIX = CREATOR_ID_WORD + 3 # 0x001B # 0x0040 - 0x004F # ==================================================== -const.ARGS_POINTER = 0x0040 +const ARGS_POINTER = 0x0040 -const.ARGS_WORD_0 = ARGS_POINTER # [total_liabilities_in, reserve_in, reserve_with_slippage_in, amount_out] -const.ARGS_WORD_1 = ARGS_POINTER + 4 # [total_liabilities_out, reserve_out, reserve_with_slippage_out, 0] -const.ARGS_WORD_2 = ARGS_POINTER + 8 # [EMPTY] -const.ARGS_WORD_3 = ARGS_POINTER + 12 # [EMPTY] +const ARGS_WORD_0 = ARGS_POINTER # [total_liabilities_in, reserve_in, reserve_with_slippage_in, amount_out] +const ARGS_WORD_1 = ARGS_POINTER + 4 # [total_liabilities_out, reserve_out, reserve_with_slippage_out, 0] +const ARGS_WORD_2 = ARGS_POINTER + 8 # [EMPTY] +const ARGS_WORD_3 = ARGS_POINTER + 12 # [EMPTY] # first 3 WORDS of ARGUMENTS space used for args # ==================================================== -const.NUMBER_OF_ARGS = 0x0008 # 8 +const NUMBER_OF_ARGS = 0x0008 # 8 # semantic names for inputs # ==================================================== -const.NEW_IN_POOL_STATE_WORD = ARGS_WORD_0 # 0x0044 -const.NEW_OUT_POOL_STATE_WORD = ARGS_WORD_1 # 0x0048 +const NEW_IN_POOL_STATE_WORD = ARGS_WORD_0 # 0x0044 +const NEW_OUT_POOL_STATE_WORD = ARGS_WORD_1 # 0x0048 # [amount_out] -const.AMOUNT_OUT = ARGS_WORD_0 + 3 +const AMOUNT_OUT = ARGS_WORD_0 + 3 # 3 empty felts # [pool0_liabilities, pool0_reserve, pool0_reserve_with_slippage, empty] -const.IN_POOL_LIABILITIES = NEW_IN_POOL_STATE_WORD # 0x0044 -const.IN_POOL_RESERVE = NEW_IN_POOL_STATE_WORD + 1 -const.IN_POOL_RESERVE_WITH_SLIPPAGE = NEW_IN_POOL_STATE_WORD + 2 +const IN_POOL_LIABILITIES = NEW_IN_POOL_STATE_WORD # 0x0044 +const IN_POOL_RESERVE = NEW_IN_POOL_STATE_WORD + 1 +const IN_POOL_RESERVE_WITH_SLIPPAGE = NEW_IN_POOL_STATE_WORD + 2 # amount_out # [pool1_liabilities, pool1_reserve, pool1_reserve_with_slippage, empty] -const.OUT_POOL_LIABILITIES = NEW_OUT_POOL_STATE_WORD # 0x0048 -const.OUT_POOL_RESERVE = NEW_OUT_POOL_STATE_WORD + 1 -const.OUT_POOL_RESERVE_WITH_SLIPPAGE = NEW_OUT_POOL_STATE_WORD + 2 -const.OUT_POOL_EMPTY_FELT = NEW_OUT_POOL_STATE_WORD + 3 # 0x0051 +const OUT_POOL_LIABILITIES = NEW_OUT_POOL_STATE_WORD # 0x0048 +const OUT_POOL_RESERVE = NEW_OUT_POOL_STATE_WORD + 1 +const OUT_POOL_RESERVE_WITH_SLIPPAGE = NEW_OUT_POOL_STATE_WORD + 2 +const OUT_POOL_EMPTY_FELT = NEW_OUT_POOL_STATE_WORD + 3 # 0x0051 @@ -133,18 +132,22 @@ const.OUT_POOL_EMPTY_FELT = NEW_OUT_POOL_STATE_WORD + 3 # 0x0051 # memory addresses of the assets -const.IN_ASSET_WORD = 0x0060 +const IN_ASSET_WORD = 0x0060 # memory locations based on IN_ASSET_WORD -const.AMOUNT_IN = IN_ASSET_WORD -const.IN_TOKEN_EMPTY_FELT = IN_ASSET_WORD + 1 -const.IN_TOKEN_ID_PREFIX = IN_ASSET_WORD + 3 -const.IN_TOKEN_ID_SUFFIX = IN_ASSET_WORD + 2 +const AMOUNT_IN = IN_ASSET_WORD +const IN_TOKEN_EMPTY_FELT = IN_ASSET_WORD + 1 +const IN_TOKEN_ID_PREFIX = IN_ASSET_WORD + 3 +const IN_TOKEN_ID_SUFFIX = IN_ASSET_WORD + 2 + +# p2id related memory +const P2ID_TARGET_ID_PREFIX = 0x0068 +const P2ID_TARGET_ID_SUFFIX = 0x0069 # "constant" MEMORY # ================================================================================================= -const.P2ID_SCRIPT_ROOT_WORD = 0x0080 +const P2ID_SCRIPT_ROOT_WORD = 0x0080 @@ -153,17 +156,17 @@ const.P2ID_SCRIPT_ROOT_WORD = 0x0080 #! Inputs: [] #! Outputs: [] #! -proc.add_first_asset_to_account +proc add_first_asset_to_account mem_loadw_be.IN_ASSET_WORD #call.wallet::receive_asset end -proc.store_asset_in_to_memory +proc store_asset_in_to_memory push.IN_ASSET_WORD exec.active_note::get_assets assert.err=ERR_ZOROSWAP_WRONG_NUMBER_OF_ASSETS drop end -proc.store_inputs_to_memory +proc store_inputs_to_memory # store note inputs into memory starting at address 0 push.INPUTS_POINTER exec.active_note::get_inputs # => [num_inputs, inputs_ptr] @@ -174,7 +177,7 @@ proc.store_inputs_to_memory end # @note TODO: pool stats will likely need to go through the advice stack -proc.store_args_to_memory +proc store_args_to_memory #push.6000.0.0.0 exec.active_note::get_serial_number adv.push_mapval @@ -191,30 +194,39 @@ end #! Inputs: [SERIAL_NUM, SCRIPT_HASH] #! Outputs: [P2ID_RECIPIENT] #! -proc.build_p2id_recipient_hash +proc build_p2id_recipient_hash padw hmerge # => [SERIAL_NUM_HASH, SCRIPT_HASH] swapw hmerge # => [SERIAL_SCRIPT_HASH] - padw - mem_load.ZOROSWAP_CREATOR_ID_SUFFIX mem_load.ZOROSWAP_CREATOR_ID_PREFIX + + # Compute inputs commitment: hash_elements([suffix, prefix]) + # Must match build_p2id_recipient() from miden_standards which internally + # computes NoteInputs::new(vec![suffix, prefix]).commitment() via + # Rpo256::hash_elements(&[suffix, prefix]) + # + # mem_storew_le stores in little-endian: stack[0]→pos0, stack[1]→pos1, etc. + # hash_elements (via mem_stream) reads pos0 first → rate[0], pos1 → rate[1] + # So we need suffix on top (it goes to pos0) and prefix below (pos1) push.0.0 + mem_load.P2ID_TARGET_ID_PREFIX mem_load.P2ID_TARGET_ID_SUFFIX + # => [suffix, prefix, 0, 0, SERIAL_SCRIPT_HASH] - push.4000 mem_storew_be dropw - push.4004 mem_storew_be dropw + push.4000 mem_storew_le dropw + # => mem[4000] pos0=suffix, pos1=prefix (LE), stack=[SERIAL_SCRIPT_HASH] - push.8.4000 - # => [ptr, elements] - exec.rpo::hash_memory - # => [INPUTS_HASH, SERIAL_SCRIPT_HASH] + push.2.4000 + # => [ptr=4000, num_elements=2, SERIAL_SCRIPT_HASH] + exec.rpo256::hash_elements + # => [INPUTS_COMMITMENT, SERIAL_SCRIPT_HASH] hmerge # => [P2ID_RECIPIENT] end # input: [SERIAL_NUM,...] # ouput: [P2ID_SERIAL_NUM, ...] -proc.get_p2id_serial_num +proc get_p2id_serial_num add.1 end @@ -223,11 +235,12 @@ end #! Inputs: [ASSET_TO_BE_SENT] #! Outputs: [] #! -proc.create_p2id_reverse_note - # @dev prepad to not to destroy ASSET_TO_BE_SENT by note creation - # @dev if doesnt work, use memory for ASSET_TO_BE_SENT - padw padw - # => [pad(8), ASSET_TO_BE_SENT] +proc create_p2id_reverse_note + # prepad to keep ASSET_TO_BE_SENT below the 16-element call frame + # We need 16 elements above ASSET before call.output_note::create: + # tag(1) + note_type(1) + RECIPIENT(4) + pad(10) = 16 + padw padw push.0.0 + # => [pad(10), ASSET_TO_BE_SENT] padw mem_loadw_be.P2ID_SCRIPT_ROOT_WORD # => [P2ID_SCRIPT_HASH, pad(8), ASSET_TO_BE_SENT] exec.active_note::get_serial_number @@ -238,15 +251,10 @@ proc.create_p2id_reverse_note # => [P2ID_RECIPIENT, pad(8), ASSET_TO_BE_SENT] - push.EXECUTION_HINT_ALWAYS - # => [execution_hint_always, P2ID_RECIPIENT, pad(8), ASSET_TO_BE_SENT] push.PUBLIC_NOTE - # => [public_note, execution_hint_always, P2ID_RECIPIENT, pad(8), ASSET_TO_BE_SENT] - push.0 # @dev aux for p2id output note - # => [aux, public_note, execution_hint_always, P2ID_RECIPIENT, pad(8), ASSET_TO_BE_SENT] + # => [note_type, P2ID_RECIPIENT, pad(8), ASSET_TO_BE_SENT] mem_load.P2ID_TAG - # => [tag, aux, public_note, execution_hint_always, P2ID_RECIPIENT, pad(8), ASSET_TO_BE_SENT] - # => [tag, aux, note_type, execution_hint, RECIPIENT, pad(8), ASSET_TO_BE_SENT] + # => [tag, note_type, P2ID_RECIPIENT, pad(8), ASSET_TO_BE_SENT] call.output_note::create # => [note_idx, pad(15), ASSET_TO_BE_SENT] swapw.2 dropw swapw.3 @@ -260,18 +268,21 @@ proc.create_p2id_reverse_note # => [] end -proc.store_p2id_script_hash - push.15783632360113277539.7403765918285273520.15691985194755641846.10399643920503194563 +proc store_p2id_script_hash + # P2ID script root from miden-standards v0.13 WellKnownNote::P2ID.script_root() + push.13362761878458161062.15090726097241769395.444910447169617901.3558201871398422326 mem_storew_be.P2ID_SCRIPT_ROOT_WORD dropw end -proc.execute_ZOROSWAP +proc execute_ZOROSWAP mem_load.AMOUNT_OUT mem_load.MIN_AMOUNT_OUT lt # min amount out is not met if.true + mem_load.ZOROSWAP_CREATOR_ID_SUFFIX mem_store.P2ID_TARGET_ID_SUFFIX + mem_load.ZOROSWAP_CREATOR_ID_PREFIX mem_store.P2ID_TARGET_ID_PREFIX mem_loadw_be.IN_ASSET_WORD exec.create_p2id_reverse_note call.zoropool::bounce_asset @@ -281,6 +292,8 @@ proc.execute_ZOROSWAP mem_loadw_be.IN_ASSET_WORD call.zoropool::receive_asset + mem_load.BENEFICIARY_ID_SUFFIX mem_store.P2ID_TARGET_ID_SUFFIX + mem_load.BENEFICIARY_ID_PREFIX mem_store.P2ID_TARGET_ID_PREFIX mem_load.AMOUNT_OUT push.0 mem_load.OUT_TOKEN_ID_SUFFIX mem_load.OUT_TOKEN_ID_PREFIX exec.create_p2id_reverse_note diff --git a/src/lib/ZoroDepositNote.ts b/src/lib/ZoroDepositNote.ts index 1a35610..e58c8b0 100644 --- a/src/lib/ZoroDepositNote.ts +++ b/src/lib/ZoroDepositNote.ts @@ -1,3 +1,4 @@ +import { CustomTransaction } from '@demox-labs/miden-wallet-adapter'; import { AccountId, Felt, @@ -6,7 +7,6 @@ import { MidenArrays, Note, NoteAssets, - NoteExecutionHint, NoteInputs, NoteMetadata, NoteRecipient, @@ -15,13 +15,12 @@ import { OutputNote, TransactionRequestBuilder, WebClient, -} from '@demox-labs/miden-sdk'; -import { Transaction } from '@demox-labs/miden-wallet-adapter'; +} from '@miden-sdk/miden-sdk'; import type { TokenConfig } from '@/providers/ZoroProvider'; import DEPOSIT_SCRIPT from './DEPOSIT.masm?raw'; -import zoropool from './zoropool.masm?raw'; import { accountIdToBech32, generateRandomSerialNumber } from './utils'; +import zoropool from './zoropool.masm?raw'; export interface DepositParams { poolAccountId: AccountId; @@ -30,7 +29,6 @@ export interface DepositParams { minAmountOut: bigint; userAccountId: AccountId; client: WebClient; - syncState: () => Promise; noteType: NoteType; } @@ -46,12 +44,10 @@ export async function compileDepositTransaction({ minAmountOut, userAccountId, client, - syncState, noteType, }: DepositParams) { - await syncState(); - const builder = client.createScriptBuilder(); - const pool_script = builder.buildLibrary('zoro::zoropool', zoropool); + const builder = client.createCodeBuilder(); + const pool_script = builder.buildLibrary('zoroswap::zoropool', zoropool); builder.linkDynamicLibrary(pool_script); const script = builder.compileNoteScript( DEPOSIT_SCRIPT, @@ -61,31 +57,28 @@ export async function compileDepositTransaction({ // Note should only contain the offered asset const noteAssets = new NoteAssets([offeredAsset]); const noteTag = noteType === NoteType.Private - ? NoteTag.forLocalUseCase(0, 0) - : NoteTag.fromAccountId(poolAccountId); + ? new NoteTag(0) + : NoteTag.withAccountTarget(poolAccountId); const metadata = new NoteMetadata( userAccountId, noteType, noteTag, - NoteExecutionHint.always(), - new Felt(BigInt(0)), // aux ); const deadline = Date.now() + 120_000; // 2 min from now // Use the AccountId for p2id tag - const p2idTag = NoteTag.fromAccountId(userAccountId).asU32(); + const p2idTag = NoteTag.withAccountTarget(userAccountId).asU32(); - // Following the pattern: [asset_id_prefix, asset_id_suffix, 0, min_amount_out] const inputs = new NoteInputs( new FeltArray([ - new Felt(BigInt(0)), new Felt(minAmountOut), new Felt(BigInt(deadline)), new Felt(BigInt(p2idTag)), new Felt(BigInt(0)), new Felt(BigInt(0)), + new Felt(BigInt(0)), userAccountId.suffix(), userAccountId.prefix(), ]), @@ -103,7 +96,7 @@ export async function compileDepositTransaction({ .withOwnOutputNotes(new MidenArrays.OutputNoteArray([OutputNote.full(note)])) .build(); - const tx = Transaction.createCustomTransaction( + const tx = new CustomTransaction( accountIdToBech32(userAccountId), accountIdToBech32(poolAccountId), transactionRequest, diff --git a/src/lib/ZoroSwapNote.ts b/src/lib/ZoroSwapNote.ts index 873945a..3bf57e8 100644 --- a/src/lib/ZoroSwapNote.ts +++ b/src/lib/ZoroSwapNote.ts @@ -6,7 +6,6 @@ import { MidenArrays, Note, NoteAssets, - NoteExecutionHint, NoteInputs, NoteMetadata, NoteRecipient, @@ -15,7 +14,7 @@ import { OutputNote, TransactionRequestBuilder, WebClient, -} from '@demox-labs/miden-sdk'; +} from '@miden-sdk/miden-sdk'; import { CustomTransaction } from '@demox-labs/miden-wallet-adapter'; import type { TokenConfig } from '@/providers/ZoroProvider'; @@ -49,8 +48,8 @@ export async function compileSwapTransaction({ client, }: Omit) { // Note: syncState should be called by the caller before invoking this function - const builder = client.createScriptBuilder(); - const pool_script = builder.buildLibrary('zoro::zoropool', zoropool); + const builder = client.createCodeBuilder(); + const pool_script = builder.buildLibrary('zoroswap::zoropool', zoropool); builder.linkDynamicLibrary(pool_script); const script = builder.compileNoteScript(ZOROSWAP_SCRIPT); const noteType = NoteType.Public; @@ -58,20 +57,18 @@ export async function compileSwapTransaction({ // Note should only contain the offered asset const noteAssets = new NoteAssets([offeredAsset]); - const noteTag = NoteTag.fromAccountId(poolAccountId); + const noteTag = NoteTag.withAccountTarget(poolAccountId); const metadata = new NoteMetadata( userAccountId, noteType, noteTag, - NoteExecutionHint.always(), - new Felt(BigInt(0)), // aux ); const deadline = Date.now() + 120_000; // 2 min from now // Use the AccountId for p2id tag - const p2idTag = NoteTag.fromAccountId(userAccountId).asU32(); + const p2idTag = NoteTag.withAccountTarget(userAccountId).asU32(); // Following the pattern: [asset_id_prefix, asset_id_suffix, 0, min_amount_out] const inputs = new NoteInputs( @@ -84,9 +81,9 @@ export async function compileSwapTransaction({ new Felt(BigInt(p2idTag)), new Felt(BigInt(0)), new Felt(BigInt(0)), - new Felt(BigInt(0)), - new Felt(BigInt(0)), - userAccountId.suffix(), + userAccountId.suffix(), // beneficiary + userAccountId.prefix(), + userAccountId.suffix(), // creator userAccountId.prefix(), ]), ); diff --git a/src/lib/ZoroWithdrawNote.ts b/src/lib/ZoroWithdrawNote.ts index c9a11db..6418517 100644 --- a/src/lib/ZoroWithdrawNote.ts +++ b/src/lib/ZoroWithdrawNote.ts @@ -1,3 +1,4 @@ +import { CustomTransaction } from '@demox-labs/miden-wallet-adapter'; import { AccountId, Felt, @@ -6,7 +7,6 @@ import { MidenArrays, Note, NoteAssets, - NoteExecutionHint, NoteInputs, NoteMetadata, NoteRecipient, @@ -15,13 +15,12 @@ import { OutputNote, TransactionRequestBuilder, WebClient, -} from '@demox-labs/miden-sdk'; -import { Transaction } from '@demox-labs/miden-wallet-adapter'; +} from '@miden-sdk/miden-sdk'; import type { TokenConfig } from '@/providers/ZoroProvider'; -import zoropool from './zoropool.masm?raw'; import { accountIdToBech32, generateRandomSerialNumber } from './utils'; import WITHDRAW_SCRIPT from './WITHDRAW.masm?raw'; +import zoropool from './zoropool.masm?raw'; export interface WithdrawParams { poolAccountId: AccountId; @@ -30,7 +29,6 @@ export interface WithdrawParams { minAmountOut: bigint; userAccountId: AccountId; client: WebClient; - syncState: () => Promise; noteType: NoteType; } @@ -46,43 +44,40 @@ export async function compileWithdrawTransaction({ minAmountOut, userAccountId, client, - syncState, noteType, }: WithdrawParams) { - await syncState(); - const builder = client.createScriptBuilder(); - const pool_script = builder.buildLibrary('zoro::zoropool', zoropool); + const builder = client.createCodeBuilder(); + const pool_script = builder.buildLibrary('zoroswap::zoropool', zoropool); builder.linkDynamicLibrary(pool_script); const script = builder.compileNoteScript( WITHDRAW_SCRIPT, ); - const requestedAsset = new FungibleAsset(token.faucetId, amount).intoWord().toFelts(); + const requestedAsset = new FungibleAsset(token.faucetId, minAmountOut).intoWord() + .toFelts(); // Note should only contain the offered asset const noteAssets = new NoteAssets([]); const noteTag = noteType === NoteType.Private - ? NoteTag.forLocalUseCase(0, 0) - : NoteTag.fromAccountId(poolAccountId); + ? new NoteTag(0) + : NoteTag.withAccountTarget(poolAccountId); const metadata = new NoteMetadata( userAccountId, noteType, noteTag, - NoteExecutionHint.always(), - new Felt(BigInt(0)), // aux ); const deadline = Date.now() + 120_000; // 2 min from now // Use the AccountId for p2id tag - const p2idTag = NoteTag.fromAccountId(userAccountId).asU32(); + const p2idTag = NoteTag.withAccountTarget(userAccountId).asU32(); // Following the pattern: [asset_id_prefix, asset_id_suffix, 0, min_amount_out] const inputs = new NoteInputs( new FeltArray([ ...requestedAsset, new Felt(BigInt(0)), - new Felt(minAmountOut), + new Felt(amount), new Felt(BigInt(deadline)), new Felt(BigInt(p2idTag)), new Felt(BigInt(0)), @@ -104,7 +99,7 @@ export async function compileWithdrawTransaction({ .withOwnOutputNotes(new MidenArrays.OutputNoteArray([OutputNote.full(note)])) .build(); - const tx = Transaction.createCustomTransaction( + const tx = new CustomTransaction( accountIdToBech32(userAccountId), accountIdToBech32(poolAccountId), transactionRequest, diff --git a/src/lib/config.ts b/src/lib/config.ts index 90b8892..91ee4bd 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -1,4 +1,4 @@ -import { NetworkId } from '@demox-labs/miden-sdk'; +import { NetworkId } from '@miden-sdk/miden-sdk'; export interface NetworkConfig { readonly rpcEndpoint: string; @@ -70,9 +70,20 @@ export const API: ApiConfig = { // UI Configuration export const DEFAULT_SLIPPAGE = getNumericEnvVar('VITE_DEFAULT_SLIPPAGE', 0.5); -export const NETWORK_ID = import.meta.env.VITE_NETWORK_ID === 'mainnet' - ? NetworkId.Mainnet - : NetworkId.Testnet; +/** Create a fresh NetworkId matching the configured network (toBech32 consumes the NetworkId) */ +export const createNetworkId = (): NetworkId => { + switch (import.meta.env.VITE_NETWORK_ID) { + case 'mainnet': + return NetworkId.mainnet(); + case 'devnet': + return NetworkId.devnet(); + case 'localhost': + // No NetworkId.localhost() exists, we use the next best one + return NetworkId.testnet(); + default: + return NetworkId.testnet(); + } +}; /** * Validate all configurations on module load diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 783d6f3..07f1ce8 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -5,10 +5,10 @@ import { Felt, WebClient, Word, -} from '@demox-labs/miden-sdk'; +} from '@miden-sdk/miden-sdk'; import { type ClassValue, clsx } from 'clsx'; import { twMerge } from 'tailwind-merge'; -import { NETWORK, NETWORK_ID } from './config'; +import { createNetworkId, NETWORK } from './config'; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); @@ -18,14 +18,14 @@ export const instantiateClient = async ( { accountsToImport }: { accountsToImport: (AccountId | undefined)[] }, ) => { const { Address: DynamicAddress, WebClient } = await import( - '@demox-labs/miden-sdk' + '@miden-sdk/miden-sdk' ); const client = await WebClient.createClient(NETWORK.rpcEndpoint); for (const acc of accountsToImport) { if (!acc) continue; try { // Convert to bech32 and back to ensure we have an AccountId from the same module instance - const bech32 = acc.toBech32(NETWORK_ID, AccountInterface.BasicWallet); + const bech32 = acc.toBech32(createNetworkId(), AccountInterface.BasicWallet); const accountId = DynamicAddress.fromBech32(bech32).accountId(); await safeAccountImport(client, accountId); } catch (e) { @@ -50,7 +50,7 @@ export const safeAccountImport = async (client: WebClient, accountId: AccountId) export const accountIdToBech32 = ( accountId: AccountId, ) => { - return accountId.toBech32(NETWORK_ID, AccountInterface.BasicWallet).split('_')[0]; + return accountId.toBech32(createNetworkId(), AccountInterface.BasicWallet).split('_')[0]; }; export const bech32ToAccountId = (bech32str?: string) => { diff --git a/src/lib/zoropool.masm b/src/lib/zoropool.masm index cd8086b..8bd75c2 100644 --- a/src/lib/zoropool.masm +++ b/src/lib/zoropool.masm @@ -1,36 +1,35 @@ -use.miden::active_account -use.miden::native_account -use.miden::output_note - - -#use.std::sys +use miden::protocol::active_account +use miden::protocol::native_account +use miden::protocol::output_note #! CONSTANTS -const.MAX_COVERAGE_RATIO = 0x0002 # 200% +const MAX_COVERAGE_RATIO = 0x0002 # 200% #! ERRORS -const.ERR_MAX_COVERAGE_RATIO_EXCEEDED ="Max coverage ratio exceeded" -const.ERR_RESERVE_WITH_SLIPPAGE_EXCEEDS_ASSET_BALANCE ="Reserve with slippage exceeds asset balance" -const.ERR_RESERVE_EXCEEDS_RESERVE_WITH_SLIPPAGE ="Reserve exceeds reserve with slippage" -const.ERR_LP_WITHDRAW_AMOUNT_EXCEEDS_USER_LP_SHARES ="LP withdrawal amount exceeds user LP shares" +const ERR_MAX_COVERAGE_RATIO_EXCEEDED ="Max coverage ratio exceeded" +const ERR_RESERVE_WITH_SLIPPAGE_EXCEEDS_ASSET_BALANCE ="Reserve with slippage exceeds asset balance" +const ERR_RESERVE_EXCEEDS_RESERVE_WITH_SLIPPAGE ="Reserve exceeds reserve with slippage" +const ERR_LP_WITHDRAW_AMOUNT_EXCEEDS_USER_LP_SHARES ="LP withdrawal amount exceeds user LP shares" #! MEM STRUCTURE #! DYNAMIC PROC ADDRESS -const.DYNAMIC_PROC_ADDR = 0x0004 +const DYNAMIC_PROC_ADDR = 0x0004 -const.DEPOSIT_ASSET_LOC = 0x00A0 -const.DEPOSIT_KEY_LOC = 0x00A4 -const.WITHDRAW_KEY_LOC = 0x00A4 +const DEPOSIT_ASSET_LOC = 0x00A0 +const DEPOSIT_KEY_LOC = 0x00A4 +const WITHDRAW_KEY_LOC = 0x00A4 -const.BOUNCE_ASSET_LOC = 0x00A8 +const BOUNCE_ASSET_LOC = 0x00A8 -const.LP_SHARES_LOC = 0x00AC -const.LP_SHARES_AMOUNT_ADDR = LP_SHARES_LOC + 3 +const LP_SHARES_LOC = 0x00AC +const LP_SHARES_AMOUNT_ADDR = LP_SHARES_LOC + 3 +const HELPER_SLOT_ID_PREFIX_LOC = 0x00B0 +const HELPER_SLOT_ID_SUFFIX_LOC = 0x00B1 #! STORAGE STRUCTURE @@ -38,27 +37,27 @@ const.LP_SHARES_AMOUNT_ADDR = LP_SHARES_LOC + 3 #! pool state: [liabilities, reserve, reserve_with_slippage, 0] #! fees: [swap_fee, backstop_fee, protocol_fee, 0 ] #! curve: c, beta -const.ASSETS_MAPPING_SLOT = 0x0002 -const.POOL_STATE_MAPPING_SLOT = 0x0003 +const ASSETS_MAPPING_SLOT = word("zoroswap::assets") +const POOL_STATE_MAPPING_SLOT = word("zoroswap::pool_state") -const.USER_DEPOSITS_MAPPING_SLOT = 0x0004 +const USER_DEPOSITS_MAPPING_SLOT = word("zoroswap::user_deposits") -const.POOL_CURVE_MAPPING_SLOT = 0x0005 -const.POOL_FEES_MAPPING_SLOT = 0x0006 +const POOL_CURVE_MAPPING_SLOT = word("zoroswap::pool_curve") +const POOL_FEES_MAPPING_SLOT = word("zoroswap::fees") #! INDEXES -const.LIABILITIES_INDEX = 0x0000 -const.RESERVE_INDEX = 0x0001 -const.RESERVE_WITH_SLIPPAGE_INDEX = 0x0002 +const LIABILITIES_INDEX = 0x0000 +const RESERVE_INDEX = 0x0001 +const RESERVE_WITH_SLIPPAGE_INDEX = 0x0002 -const.SWAP_FEE_INDEX = 0x0000 -const.BACKSTOP_FEE_INDEX = 0x0001 -const.PROTOCOL_FEE_INDEX = 0x0002 +const SWAP_FEE_INDEX = 0x0000 +const BACKSTOP_FEE_INDEX = 0x0001 +const PROTOCOL_FEE_INDEX = 0x0002 -const.C_INDEX = 0x0000 -const.BETA_INDEX = 0x0001 +const C_INDEX = 0x0000 +const BETA_INDEX = 0x0001 @@ -68,29 +67,29 @@ const.BETA_INDEX = 0x0001 #! Inputs: [ASSET, liabilities, reserve, reserve_with_slippage] #! Outputs: [] #! -#! @dev TODO: compare reserve with slippage with actual asset balances -export.set_pool_state +#! TODO: compare reserve with slippage with actual asset balances +pub proc set_pool_state dupw movdnw.2 # =>[ASSET, liabilities, reserve, reserve_with_slippage, 0,ASSET] - # @dev: ensure reserve never > reserve_with_slippage - dup.5 + # ensure reserve never > reserve_with_slippage + dup.5 # =>[reserve, ASSET, liabilities, reserve, reserve_with_slippage, 0, ASSET] dup.7 # => [reserve_with_slippage, reserve, ASSET, liabilities, reserve, reserve_with_slippage, 0, ASSET] lte assert.err=ERR_RESERVE_EXCEEDS_RESERVE_WITH_SLIPPAGE ## => [ASSET, liabilities, reserve, reserve_with_slippage, 0, ASSET] - # @dev:ensure reserves < max_coverage_ratio * liabilities + # ensure reserves < max_coverage_ratio * liabilities dup.6 dup.5 mul.MAX_COVERAGE_RATIO # => [liablities * max_coverage_ratio, reserve_with_slippage, ASSET, liabilities, reserve, reserve_with_slippage, 0, ASSET] lt assert.err=ERR_MAX_COVERAGE_RATIO_EXCEEDED ## => [ASSET, liabilities, reserve, reserve_with_slippage, 0, ASSET] # => [ASSET, liabilities, reserve, reserve_with_slippage, 0, ASSET] - - exec.address_from_asset + + exec.address_from_asset exec.active_account::get_balance # => [asset_balance, liabilities, reserve, reserve_with_slippage, 0, ASSET] @@ -99,40 +98,40 @@ export.set_pool_state lte assert.err=ERR_RESERVE_WITH_SLIPPAGE_EXCEEDS_ASSET_BALANCE # => [liabilities, reserve, reserve_with_slippage, 0, ASSET] # => [ POOL_STATE, ASSET] - swapw + swapw # => [ ASSET, POOL_STATE] #reversew drop drop push.0.0 exec.address_word_from_asset - + # => [asset_faucet_id_prefix, asset_faucet_id_suffix, 0, 0, POOL_STATE] # => [POOL_KEY, POOL_STATE] - push.POOL_STATE_MAPPING_SLOT + push.POOL_STATE_MAPPING_SLOT[0..2] exec.native_account::set_map_item - - dropw dropw - # => [] + + dropw + # => [] end -export.set_pool0_state +pub proc set_pool0_state #push.POOL0_STATE_ADDR push.0.0.0.0 # asset index # => [ASSET_0_INDEX, liabilities, reserve, reserve_with_slippage, 0] # => [ 0,0,0,0, liabilities, reserve, reserve_with_slippage, 0] - push.ASSETS_MAPPING_SLOT + push.ASSETS_MAPPING_SLOT[0..2] exec.active_account::get_map_item - + exec.set_pool_state - + end -export.set_pool1_state +pub proc set_pool1_state push.1.0.0.0 - push.ASSETS_MAPPING_SLOT + push.ASSETS_MAPPING_SLOT[0..2] exec.active_account::get_map_item exec.set_pool_state - + end @@ -141,24 +140,21 @@ end #! Inputs: [0, 0, 0, 0] #! Outputs: [liabilities, reserve, reserve_with_slippage, 0] #! -export.get_pool0_state +pub proc get_pool0_state push.0 # pool index call.get_pool_asset - # => [pool0_state_addr] - push.POOL_STATE_MAPPING_SLOT - exec.active_account::get_item + # => [faucet_prefix, faucet_suffix, 0, 0, ...] (call frame, KEY = first 4 felts after slot) + push.POOL_STATE_MAPPING_SLOT[0..2] + exec.active_account::get_map_item # => [liabilities, reserve, reserve_with_slippage, 0] - swapw dropw - # => [liabilities, reserve, reserve_with_slippage] end -export.get_pool1_state +pub proc get_pool1_state push.1 # pool index call.get_pool_asset - # => [POOL_ASSET_ID] - push.POOL_STATE_MAPPING_SLOT - exec.active_account::get_item - - swapw dropw + # => [faucet_prefix, faucet_suffix, 0, 0, ...] (call frame) + push.POOL_STATE_MAPPING_SLOT[0..2] + exec.active_account::get_map_item + # => [liabilities, reserve, reserve_with_slippage, 0] end #! Returns POOL ASSET ID @@ -166,11 +162,11 @@ end #! Inputs: [pool_index] #! Outputs: [asset_id_prefix, asset_id_suffix] #! -export.get_pool_asset +pub proc get_pool_asset # fill in the index to a word to server as the mapping key - push.0.0.0 + push.0.0.0 # => [POOL_IMDEX] - push.ASSETS_MAPPING_SLOT + push.ASSETS_MAPPING_SLOT[0..2] exec.active_account::get_map_item swap.2 drop swap.2 drop @@ -184,7 +180,7 @@ end #export.get_pool0_fees # push.POOL0_FEES_ADDR # # => [pool0_fees_addr] -# +# # exec.active_account::get_item # # => [swap_fee, backstop_fee, protocol_fee, 0] # swapw dropw @@ -192,14 +188,14 @@ end #export.get_pool1_fees # push.POOL1_FEES_ADDR # # => [pool1_fees_addr] -# +# # exec.active_account::get_item # # => [swap_fee, backstop_fee, protocol_fee, 0] # swapw dropw #end -export.get_pool_fees +pub proc get_pool_fees push.0.0.0 - push.POOL_FEES_MAPPING_SLOT + push.POOL_FEES_MAPPING_SLOT[0..2] exec.active_account::get_map_item end @@ -209,9 +205,9 @@ end #! Inputs: [0, 0, 0, 0] #! Outputs: [c, beta, 0, 0] #! -export.get_pool_curve +pub proc get_pool_curve push.0.0.0 - push.POOL_CURVE_MAPPING_SLOT + push.POOL_CURVE_MAPPING_SLOT[0..2] exec.active_account::get_map_item swap.2 drop swap.2 drop @@ -235,7 +231,7 @@ end #! the total amount would be greater than 2^63. #! #! Invocation: call -export.receive_asset +pub proc receive_asset exec.native_account::add_asset # => [ASSET', reserve, reserve_with_slippage, liabilities, 0, pad(8)] @@ -245,11 +241,11 @@ export.receive_asset # => [pad(16)] end -#proc.validate_and_update_state +#proc validate_and_update_state # push.0 drop #end -proc.validate_and_update_state +proc validate_and_update_state exec.set_pool_state end @@ -266,7 +262,7 @@ end #! - adding a fungible asset would result in amount overflow, i.e., #! the total amount would be greater than 2^63. #! -export.move_asset_to_note +pub proc move_asset_to_note exec.native_account::remove_asset # => [ASSET, note_idx, 0,0,0, liabilities, reserve, reserve_with_slippage, 0, pad(4)] @@ -274,11 +270,11 @@ export.move_asset_to_note # => [ASSET, note_idx, ASSET, note_idx,0,0,0, liabilities, reserve, reserve_with_slippage, 0, pad(4)] exec.output_note::add_asset - + # => [ASSET, note_idx, 0,0,0, liabilities, reserve, reserve_with_slippage, 0, pad(4)] - swapw.1 dropw - + swapw.1 dropw + exec.set_pool_state end @@ -295,13 +291,13 @@ end #! - adding a fungible asset would result in amount overflow, i.e., #! the total amount would be greater than 2^63. #! -export.bounce_asset +pub proc bounce_asset mem_storew_be.BOUNCE_ASSET_LOC exec.native_account::add_asset dropw mem_loadw_be.BOUNCE_ASSET_LOC exec.native_account::remove_asset - + exec.output_note::add_asset end @@ -311,7 +307,7 @@ end #! Inputs: [ASSET, POOL_STATE,user_id_suffix, user_id_prefix, lp_amount_out] #! Outputs: [share_amount] #! -export.deposit +pub proc deposit mem_storew_be.DEPOSIT_ASSET_LOC exec.receive_asset @@ -327,8 +323,8 @@ export.deposit # => [SHARE_AMOUNT, KEY] swapw # => [KEY, SHARE_AMOUNT] - push.USER_DEPOSITS_MAPPING_SLOT - # => [slot, KEY, SHARE_AMOUNT] + push.USER_DEPOSITS_MAPPING_SLOT[0..2] + # => [slot_id_prefix, slot_id_suffix, KEY, SHARE_AMOUNT] exec.add_to_map_item # update total supply @@ -337,14 +333,14 @@ export.deposit # => [KEY, SHARE_AMOUNT] exec.get_lp_shares_total_supply_key # => [TOTAL_SUPPLY_KEY, SHARE_AMOUNT] - push.USER_DEPOSITS_MAPPING_SLOT - # => [slot, KEY, SHARE_AMOUNT] + push.USER_DEPOSITS_MAPPING_SLOT[0..2] + # => [slot_id_prefix, slot_id_suffix, KEY, SHARE_AMOUNT] exec.add_to_map_item # => [] end #! Inputs: [user_id_prefix, user_id_suffix, lp_withdraw_amount, 0, ASSET_OUT, noteid, 0,0,0, liabilities, reserve, reserve_with_slippage] -export.withdraw +pub proc withdraw dupw # => [user_id_prefix, user_id_suffix, lp_withdraw_amount, 0, user_id_prefix, user_id_suffix, lp_withdraw_amount, 0, ASSET_OUT, noteid, 0,0,0, liabilities, reserve, reserve_with_slippage, 0] dupw.2 @@ -356,33 +352,33 @@ export.withdraw # => [user_lp_shares, lp_withdraw_amount, 0, user_id_prefix, user_id_suffix, lp_withdraw_amount, 0, ASSET, noteid, 0,0,0, liabilities, reserve, reserve_with_slippage, 0] swap sub # => [new_user_lp_shares, 0, user_id_prefix, user_id_suffix, lp_withdraw_amount, 0, ASSET, noteid, 0,0,0, liabilities, reserve, reserve_with_slippage, 0 ] - dup + dup # => [new_user_lp_shares, new_user_lp_shares, 0, user_id_prefix, user_id_suffix, lp_withdraw_amount, 0, ASSET, noteid, 0,0,0, liabilities, reserve, reserve_with_slippage, 0] padw mem_loadw_be.WITHDRAW_KEY_LOC exec.get_user_deposit # => [user_lp_shares, new_user_lp_shares, new_user_lp_shares, 0, user_id_prefix, user_id_suffix, lp_withdraw_amount, 0, ASSET, noteid, 0,0,0, liabilities, reserve, reserve_with_slippage, 0] lt assert.err=ERR_LP_WITHDRAW_AMOUNT_EXCEEDS_USER_LP_SHARES # => [new_user_lp_shares, 0, user_id_prefix, user_id_suffix, lp_withdraw_amount, 0, ASSET, noteid, 0,0,0, liabilities, reserve, reserve_with_slippage, 0] - + # store new user lp shares push.0.0.0 # => [NEW_USER_LP_SHARES, 0, user_id_prefix, user_id_suffix, lp_withdraw_amount, 0, ASSET, noteid, 0,0,0, liabilities, reserve, reserve_with_slippage, 0] padw mem_loadw_be.WITHDRAW_KEY_LOC - push.USER_DEPOSITS_MAPPING_SLOT - # => [slot, KEY, NEW_USER_LP_SHARES] + push.USER_DEPOSITS_MAPPING_SLOT[0..2] + # => [slot_id_prefix, slot_id_suffix, KEY, NEW_USER_LP_SHARES] exec.native_account::set_map_item - dropw dropw drop + dropw drop # => [user_id_prefix, user_id_suffix, lp_withdraw_amount, 0, ASSET, noteid, 0,0,0, liabilities, reserve, reserve_with_slippage, 0] - drop drop swap drop + drop drop swap drop mem_store.LP_SHARES_AMOUNT_ADDR - + mem_load.LP_SHARES_AMOUNT_ADDR push.0.0.0 padw mem_loadw_be.WITHDRAW_KEY_LOC exec.get_lp_shares_total_supply_key # => [KEY, SHARE_AMOUNT] - push.USER_DEPOSITS_MAPPING_SLOT - # => [slot, KEY, SHARE_AMOUNT] + push.USER_DEPOSITS_MAPPING_SLOT[0..2] + # => [slot_id_prefix, slot_id_suffix, KEY, SHARE_AMOUNT] exec.sub_from_map_item # => [ ASSET_OUT, noteid, 0,0,0, liabilities, reserve, reserve_with_slippage, 0] exec.move_asset_to_note @@ -390,35 +386,79 @@ export.withdraw end -#! Inputs: [slot, KEY, VALUE] -proc.add_to_map_item - movdn.4 dupw movup.8 dup movdn.5 - # => [slot, KEY, slot, KEY, VALUE] - exec.active_account::get_map_item drop drop drop - # => [old_value, slot, KEY, VALUE] - movup.9 add - # => [new_value, slot, KEY, 0,0,0] - movdn.8 - # => [ slot, KEY, 0,0,0, new_value] - # => [ slot, KEY, NEW_VALUE] - exec.native_account::set_map_item dropw dropw +#! Atomically increments a storage map entry: map[KEY] += VALUE. +#! Reads the current value via get_map_item, adds VALUE, writes back via set_map_item. +#! +#! Inputs: [slot_id_prefix, slot_id_suffix, KEY, VALUE] +#! Outputs: [] +proc add_to_map_item + # Save slot ID to memory + mem_store.HELPER_SLOT_ID_PREFIX_LOC + mem_store.HELPER_SLOT_ID_SUFFIX_LOC + # => [KEY, VALUE] + # Duplicate KEY for second call + dupw + # => [KEY, KEY, VALUE] + + # Load slot ID and get current map value + mem_load.HELPER_SLOT_ID_SUFFIX_LOC + mem_load.HELPER_SLOT_ID_PREFIX_LOC + # => [slot_id_prefix, slot_id_suffix, KEY, KEY, VALUE] + exec.active_account::get_map_item drop drop drop + # => [old_amount, KEY, VALUE] + + # Add new amount to old amount + movup.8 add + # => [new_amount, KEY, 0, 0, 0] + movdn.7 + # => [KEY, 0, 0, 0, new_amount] + # => [KEY, NEW_VALUE] + + # Load slot ID and set map item + mem_load.HELPER_SLOT_ID_SUFFIX_LOC + mem_load.HELPER_SLOT_ID_PREFIX_LOC + # => [slot_id_prefix, slot_id_suffix, KEY, NEW_VALUE] + exec.native_account::set_map_item dropw + # => [] end -#! Inputs: [slot, KEY, VALUE] +#! Atomically decrements a storage map entry: map[KEY] -= VALUE. +#! Reads the current value via get_map_item, subtracts VALUE, writes back via set_map_item. +#! +#! Inputs: [slot_id_prefix, slot_id_suffix, KEY, VALUE] #! Outputs: [] -proc.sub_from_map_item - movdn.4 dupw movup.8 dup movdn.5 - # => [slot, KEY, slot, KEY, VALUE] +proc sub_from_map_item + # Save slot ID to memory + mem_store.HELPER_SLOT_ID_PREFIX_LOC + mem_store.HELPER_SLOT_ID_SUFFIX_LOC + # => [KEY, VALUE] + + # Duplicate KEY for second call + dupw + # => [KEY, KEY, VALUE] + + # Load slot ID and get current map value + mem_load.HELPER_SLOT_ID_SUFFIX_LOC + mem_load.HELPER_SLOT_ID_PREFIX_LOC + # => [slot_id_prefix, slot_id_suffix, KEY, KEY, VALUE] exec.active_account::get_map_item drop drop drop - # => [old_value, slot, KEY, VALUE] - movup.9 + # => [old_amount, KEY, VALUE] + + # Subtract new amount from old amount + movup.8 sub - # => [new_value, slot, KEY, 0,0,0] - movdn.8 - # => [ slot, KEY, 0,0,0, new_value] - # => [ slot, KEY, NEW_VALUE] - exec.native_account::set_map_item dropw dropw + # => [new_amount, KEY, 0, 0, 0] + movdn.7 + # => [KEY, 0, 0, 0, new_amount] + # => [KEY, NEW_VALUE] + + # Load slot ID and set map item + mem_load.HELPER_SLOT_ID_SUFFIX_LOC + mem_load.HELPER_SLOT_ID_PREFIX_LOC + # => [slot_id_prefix, slot_id_suffix, KEY, NEW_VALUE] + exec.native_account::set_map_item dropw + # => [] end #! Returns user LP shares amount @@ -426,10 +466,10 @@ end #! Inputs: [KEY] #! Outputs: [share_amount] #! -proc.get_user_deposit +proc get_user_deposit # => [KEY] - push.USER_DEPOSITS_MAPPING_SLOT - exec.active_account::get_map_item + push.USER_DEPOSITS_MAPPING_SLOT[0..2] + exec.active_account::get_map_item drop drop drop # => [share_amount] end @@ -440,7 +480,7 @@ end #! Inputs: [ASSET, user_id_suffix, user_id_prefix] #! Outputs: [KEY] #! -proc.get_user_deposit_key +proc get_user_deposit_key exec.address_from_asset # => [asset_account_prefix, asset_account_suffix, user_id_suffix, user_id_prefix] # => [KEY] @@ -451,7 +491,7 @@ end #! Inputs: [ASSET] #! Outputs: [KEY] #! -proc.get_lp_shares_total_supply_key +proc get_lp_shares_total_supply_key exec.address_word_from_asset end @@ -460,7 +500,7 @@ end #! Inputs: [amount, KEY] #! Outputs: [share_amount] #! -proc.compute_deposit_shares +proc compute_deposit_shares # @todo: implement movdn.4 dropw end @@ -470,17 +510,17 @@ end #! Inputs: [share_amount, ASSET] #! Outputs: [UPDATED_ASSET] #! -proc.compute_withdraw_amount +proc compute_withdraw_amount # @todo: implement swap.4 drop end -#! Returns ASSET AMMOUNT +#! Returns ASSET AMMOUNT #! Inputs: [ASSET] #! Outputs: [amount] #! -proc.extract_asset_amount +proc extract_asset_amount drop drop drop end @@ -493,7 +533,7 @@ end #! Inputs: [ASSET] #! Outputs: [asset_accout_prefix, asset_account_suffix, 0, 0] #! -proc.address_word_from_asset +proc address_word_from_asset push.0 swap.3 drop push.0 swap.4 drop end @@ -503,11 +543,7 @@ end #! Inputs: [ASSET] #! Outputs: [asset_accout_prefix, asset_account_suffix] #! -proc.address_from_asset +proc address_from_asset swap.2 drop swap.2 drop end - - - - diff --git a/src/pages/Faucet.tsx b/src/pages/Faucet.tsx index 294434a..c7c8df1 100644 --- a/src/pages/Faucet.tsx +++ b/src/pages/Faucet.tsx @@ -7,7 +7,7 @@ import { Skeleton } from '@/components/ui/skeleton'; import { UnifiedWalletButton } from '@/components/UnifiedWalletButton'; import { useClaimNotes } from '@/hooks/useClaimNotes'; import { useUnifiedWallet } from '@/hooks/useUnifiedWallet'; -import { accountIdToBech32 } from '@/lib/utils'; + import { ZoroContext } from '@/providers/ZoroContext'; import { type FaucetMintResult, mintFromFaucet } from '@/services/faucet'; import { Loader2 } from 'lucide-react'; @@ -24,12 +24,12 @@ interface MintStatus { type TokenMintStatuses = Record; function Faucet() { - const { connected } = useUnifiedWallet(); + const { connected, address } = useUnifiedWallet(); const { refreshPendingNotes } = useClaimNotes(); const [mintStatuses, setMintStatuses] = useState( {} as TokenMintStatuses, ); - const { tokens, tokensLoading, accountId, startExpectingNotes } = useContext(ZoroContext); + const { tokens, tokensLoading, startExpectingNotes } = useContext(ZoroContext); const updateMintStatus = useCallback(( tokenSymbol: string, updates: Partial, @@ -59,14 +59,13 @@ function Faucet() { }, [tokens, mintStatuses, setMintStatuses, updateMintStatus]); const requestTokens = useCallback(async (tokenFaucetId: string): Promise => { - if (!connected || !accountId) { + if (!connected || !address) { return; } const token = tokens[tokenFaucetId]; - if (!token || !token.faucetId) { + if (!token || !token.faucetIdBech32) { return; } - const faucetId = token.faucetId; updateMintStatus(tokenFaucetId, { isLoading: true, lastAttempt: Date.now(), @@ -76,8 +75,8 @@ function Faucet() { try { const result = await mintFromFaucet( - accountIdToBech32(accountId), - accountIdToBech32(faucetId), + address.split('_')[0], + token.faucetIdBech32, ); updateMintStatus(tokenFaucetId, { isLoading: false, @@ -119,7 +118,7 @@ function Faucet() { }); }, 5100); } - }, [connected, accountId, updateMintStatus, tokens, refreshPendingNotes, startExpectingNotes]); + }, [connected, address, updateMintStatus, tokens, refreshPendingNotes, startExpectingNotes]); const getButtonText = (tokenSymbol: string, status: MintStatus): string => { return status.isLoading ? `Minting ${tokenSymbol}...` : `Request ${tokenSymbol}`; @@ -177,7 +176,7 @@ function Faucet() { : null; return ( - + {lineBetween}
- {accountIdToBech32(token.faucetId)} + {token.faucetIdBech32} - {accountIdToBech32(token.faucetId)} + {token.faucetIdBech32}
diff --git a/src/pages/Swap.tsx b/src/pages/Swap.tsx index 8b6c33a..1af1c55 100644 --- a/src/pages/Swap.tsx +++ b/src/pages/Swap.tsx @@ -21,7 +21,7 @@ import { bech32ToAccountId } from '@/lib/utils'; import { OracleContext, useOraclePrices } from '@/providers/OracleContext'; import { ZoroContext } from '@/providers/ZoroContext'; import { type TokenConfig } from '@/providers/ZoroProvider.tsx'; -import type { AccountId } from '@demox-labs/miden-sdk'; +import type { AccountId } from '@miden-sdk/miden-sdk'; import { Loader2 } from 'lucide-react'; import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import { toast } from 'react-toastify'; diff --git a/src/providers/ParaClientContext.ts b/src/providers/ParaClientContext.ts index 5cedbd4..c43ed26 100644 --- a/src/providers/ParaClientContext.ts +++ b/src/providers/ParaClientContext.ts @@ -1,4 +1,4 @@ -import type { WebClient } from '@demox-labs/miden-sdk'; +import type { WebClient } from '@miden-sdk/miden-sdk'; import { createContext, useContext } from 'react'; /** diff --git a/src/providers/UnifiedWalletContext.ts b/src/providers/UnifiedWalletContext.ts index daeac5a..011d2ad 100644 --- a/src/providers/UnifiedWalletContext.ts +++ b/src/providers/UnifiedWalletContext.ts @@ -1,4 +1,4 @@ -import type { AccountId } from '@demox-labs/miden-sdk'; +import type { AccountId } from '@miden-sdk/miden-sdk'; import type { CustomTransaction, Transaction } from '@demox-labs/miden-wallet-adapter'; import { createContext } from 'react'; diff --git a/src/providers/UnifiedWalletProvider.tsx b/src/providers/UnifiedWalletProvider.tsx index 2f4722d..14e8c7d 100644 --- a/src/providers/UnifiedWalletProvider.tsx +++ b/src/providers/UnifiedWalletProvider.tsx @@ -1,11 +1,10 @@ import { clientMutex } from '@/lib/clientMutex'; -import { NETWORK } from '@/lib/config'; +import { createNetworkId, NETWORK } from '@/lib/config'; import { AccountId, AccountInterface, - NetworkId, TransactionRequest as TxRequest, -} from '@demox-labs/miden-sdk'; +} from '@miden-sdk/miden-sdk'; import { TransactionType, useWallet } from '@demox-labs/miden-wallet-adapter'; import { useAccount, useLogout } from '@getpara/react-sdk-lite'; import { useParaMiden } from '@miden-sdk/use-miden-para-react'; @@ -125,9 +124,7 @@ export function UnifiedWalletProvider({ children }: UnifiedWalletProviderProps) useEffect(() => { if (paraAccountIdObj && walletType === 'para') { try { - const networkId = NETWORK.rpcEndpoint.includes('testnet') - ? NetworkId.Testnet - : NetworkId.Mainnet; + const networkId = createNetworkId(); // eslint-disable-next-line react-hooks/set-state-in-effect setParaAddress( paraAccountIdObj.toBech32(networkId, AccountInterface.BasicWallet), diff --git a/src/providers/ZoroContext.ts b/src/providers/ZoroContext.ts index 0791a39..d2b822d 100644 --- a/src/providers/ZoroContext.ts +++ b/src/providers/ZoroContext.ts @@ -1,11 +1,12 @@ import type { PoolInfo } from '@/hooks/usePoolsInfo'; -import type { Account, AccountId, ConsumableNoteRecord, WebClient } from '@demox-labs/miden-sdk'; +import type { Account, AccountId, ConsumableNoteRecord, Note, RpcClient, WebClient } from '@miden-sdk/miden-sdk'; import { createContext } from 'react'; import type { TokenConfig } from './ZoroProvider'; type ZoroProviderState = { poolAccountId?: AccountId; client?: WebClient; + rpcClient?: RpcClient; liquidity_pools: PoolInfo[]; accountId?: AccountId; tokens: Record; @@ -14,7 +15,7 @@ type ZoroProviderState = { getAccount: (accountId: AccountId) => Promise; getBalance: (accountId: AccountId, faucetId: AccountId) => Promise; getConsumableNotes: (accountId: AccountId) => Promise; - consumeNotes: (accountId: AccountId, noteIds: string[]) => Promise; + consumeNotes: (accountId: AccountId, notes: Note[]) => Promise; // Notes tracking state (for Para wallet) pendingNotesCount: number; diff --git a/src/providers/ZoroProvider.tsx b/src/providers/ZoroProvider.tsx index 1596782..daab8ca 100644 --- a/src/providers/ZoroProvider.tsx +++ b/src/providers/ZoroProvider.tsx @@ -1,8 +1,9 @@ import { type PoolInfo, usePoolsInfo } from '@/hooks/usePoolsInfo'; import { useUnifiedWallet } from '@/hooks/useUnifiedWallet'; +import { NETWORK } from '@/lib/config'; import { clientMutex } from '@/lib/clientMutex'; import { bech32ToAccountId, instantiateClient } from '@/lib/utils'; -import { AccountId, Address, WebClient } from '@demox-labs/miden-sdk'; +import { AccountId, Address, Endpoint, Note, RpcClient, WebClient } from '@miden-sdk/miden-sdk'; import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useParaClient } from './ParaClientContext'; import { ZoroContext } from './ZoroContext'; @@ -22,10 +23,18 @@ export function ZoroProvider({ [address, unifiedAccountId], ); const [midenClient, setMidenClient] = useState(undefined); + const rpcClientRef = useRef(null); // For Para users, use paraClient; for Miden users, create our own client const client = walletType === 'para' ? paraClient : midenClient; + const rpcClient = useMemo(() => { + if (!rpcClientRef.current) { + rpcClientRef.current = new RpcClient(new Endpoint(NETWORK.rpcEndpoint)); + } + return rpcClientRef.current; + }, []); + // Only create a client for Miden wallet users useEffect(() => { if (walletType === 'para' || midenClient || !accountId || !poolsInfo) { @@ -34,9 +43,7 @@ export function ZoroProvider({ (async () => { const c = await instantiateClient({ accountsToImport: [ - ...(accountId - ? [accountId, bech32ToAccountId(poolsInfo.poolAccountId) as AccountId] - : []), + ...(accountId ? [accountId] : []), ], }); setMidenClient(c); @@ -101,7 +108,7 @@ export function ZoroProvider({ }); }, [client, withClientLock, throttledSync]); - const consumeNotes = useCallback(async (accountId: AccountId, noteIds: string[]) => { + const consumeNotes = useCallback(async (accountId: AccountId, notes: Note[]) => { if (!client) { throw new Error('Client not initialized'); } @@ -110,7 +117,7 @@ export function ZoroProvider({ if (!account) { throw new Error('Account not found'); } - const consumeTxRequest = client.newConsumeTransactionRequest(noteIds); + const consumeTxRequest = client.newConsumeTransactionRequest(notes); const txHash = await client.submitNewTransaction(account.id(), consumeTxRequest); return txHash.toHex(); }); @@ -182,13 +189,14 @@ export function ZoroProvider({ getConsumableNotes, consumeNotes, client, + rpcClient, // Notes tracking pendingNotesCount, isExpectingNotes, startExpectingNotes, refreshPendingNotes, }; - }, [accountId, poolsInfo, isPoolsInfoFetched, syncState, getAccount, getBalance, getConsumableNotes, consumeNotes, client, pendingNotesCount, isExpectingNotes, startExpectingNotes, refreshPendingNotes]); + }, [accountId, poolsInfo, isPoolsInfoFetched, syncState, getAccount, getBalance, getConsumableNotes, consumeNotes, client, rpcClient, pendingNotesCount, isExpectingNotes, startExpectingNotes, refreshPendingNotes]); return ( diff --git a/vite.config.ts b/vite.config.ts index d6510d0..e86f6a1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,14 +1,50 @@ import react from '@vitejs/plugin-react'; +import { createProxyMiddleware } from 'http-proxy-middleware'; import path from 'path'; import { defineConfig } from 'vite'; import { nodePolyfills } from 'vite-plugin-node-polyfills'; // https://vite.dev/config/ export default defineConfig({ - plugins: [react(), nodePolyfills()], + plugins: [ + react(), + nodePolyfills(), + // Local dev proxy for gRPC-web requests to the Miden node. + // Only active during `npm run dev`, because `configureServer` is a + // dev-server-only hook and is never invoked during `npm run build` + // or in production. + // + // Why it's needed: + // - Vite dev server by default runs on localhost:5173 + // - Local Miden node by default runs on localhost:57291 + // - Different ports = different origins, so the browser blocks requests (CORS) + // - For local development, we need to set `VITE_RPC_ENDPOINT=http://localhost:5173` + // to keep requests same-origin. This middleware then forwards them to the Miden node. + // + // Note: Vite's built-in `server.proxy` config does not intercept gRPC-web + // POST requests originating from WASM web workers. This would have been an alternative + // solution. Using `http-proxy-middleware` via `configureServer` registers our middleware + // at a lower level that catches all requests. + { + name: 'grpc-web-proxy', + configureServer(server) { + const proxy = createProxyMiddleware({ + target: 'http://localhost:57291', + changeOrigin: true, + }); + server.middlewares.use((req, res, next) => { + if (req.method === 'POST' && req.headers['content-type']?.includes('grpc')) { + return proxy(req, res, next); + } + next(); + }); + }, + }, + ], resolve: { alias: { '@': path.resolve(__dirname, './src'), + '@demox-labs/miden-sdk': '@miden-sdk/miden-sdk', }, dedupe: [ '@getpara/web-sdk', @@ -18,7 +54,7 @@ export default defineConfig({ assetsInclude: ['**/*.wasm', '**/*.masm'], optimizeDeps: { exclude: [ - '@demox-labs/miden-sdk', + '@miden-sdk/miden-sdk', ], esbuildOptions: { target: 'esnext',