From ff6bc7f23e0de96fde5467a7898a52d350d85497 Mon Sep 17 00:00:00 2001 From: jasspreetbawa13 Date: Tue, 21 Jul 2026 21:59:23 +0530 Subject: [PATCH 1/2] added new sdk methods for p2ide notes and validations as well --- package.json | 2 +- pnpm-lock.yaml | 18 +-- pnpm-workspace.yaml | 8 -- src/components/crosschain/IntentForm.tsx | 1 + src/hooks/useMidenP2IDNoteFactory.ts | 142 ++++++++++++++++++----- src/main.tsx | 8 +- vite.config.ts | 5 + 7 files changed, 134 insertions(+), 50 deletions(-) delete mode 100644 pnpm-workspace.yaml diff --git a/package.json b/package.json index a609ba0..d5ff015 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "prepare": "husky" }, "dependencies": { - "@epoch-protocol/epoch-intents-sdk": "^1.0.35", + "@epoch-protocol/epoch-intents-sdk": "file:../smallocator/sdk", "@fontsource-variable/jetbrains-mono": "^5.2.8", "@metamask/sdk": "^0.34.0", "@miden-sdk/miden-sdk": "^0.15.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6f73f90..fb5fe15 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@epoch-protocol/epoch-intents-sdk': - specifier: ^1.0.35 - version: 1.0.35(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(viem@2.48.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.4.2))(zod@4.4.2) + specifier: file:../smallocator/sdk + version: file:../smallocator/sdk(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(viem@2.48.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.4.2))(zod@4.4.2) '@fontsource-variable/jetbrains-mono': specifier: ^5.2.8 version: 5.2.8 @@ -315,11 +315,11 @@ packages: '@emotion/hash@0.9.2': resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} - '@epoch-protocol/epoch-commons-sdk@0.1.18': - resolution: {integrity: sha512-FJ15pOddF5ndbkRUO6ZJr67zHQe1YfVw3sQgtrxzsgsv8mc+MPKaWCkvaTQ9uSvNKJcvwqQmgqn4gcayejJtug==} + '@epoch-protocol/epoch-commons-sdk@0.1.19': + resolution: {integrity: sha512-M1ZSzK5LQyTWFPx4gHLVVOaAFHT23DKKP3c2G3cPsavmnyckxFayoIXIS9UyMTDn1g0QPeoL5R1YmKBmP7HE8Q==} - '@epoch-protocol/epoch-intents-sdk@1.0.35': - resolution: {integrity: sha512-69rFadIgAFKVgYgnZW3zyFKEYbpRUS3GXF7gAUEUqbojJzBWEVwCv/EwAQfd9tNVT6EVWFtJd2m29QZUE3sijQ==} + '@epoch-protocol/epoch-intents-sdk@file:../smallocator/sdk': + resolution: {directory: ../smallocator/sdk, type: directory} peerDependencies: viem: ^2.x @@ -5730,7 +5730,7 @@ snapshots: '@emotion/hash@0.9.2': {} - '@epoch-protocol/epoch-commons-sdk@0.1.18(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.4.2)': + '@epoch-protocol/epoch-commons-sdk@0.1.19(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.4.2)': dependencies: ethers: 6.16.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) viem: 2.42.0(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.4.2) @@ -5740,9 +5740,9 @@ snapshots: - utf-8-validate - zod - '@epoch-protocol/epoch-intents-sdk@1.0.35(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(viem@2.48.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.4.2))(zod@4.4.2)': + '@epoch-protocol/epoch-intents-sdk@file:../smallocator/sdk(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(viem@2.48.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.4.2))(zod@4.4.2)': dependencies: - '@epoch-protocol/epoch-commons-sdk': 0.1.18(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.4.2) + '@epoch-protocol/epoch-commons-sdk': 0.1.19(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.4.2) '@metamask/delegation-toolkit': 0.13.0(viem@2.48.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.4.2)) viem: 2.48.8(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.4.2) transitivePeerDependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml deleted file mode 100644 index 1b45529..0000000 --- a/pnpm-workspace.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# Supply-chain hardening (pnpm reads settings from this file as of pnpm 10). -# Wait 7 days before installing a newly published version, so hijacked releases -# have time to be reported and unpublished before they reach this project. -minimumReleaseAge: 10080 -# Refuse transitive deps that resolve outside the registry (git/file/tarball URLs). -blockExoticSubdeps: true -# Reject packages whose trust signals (provenance, signatures) weaken between updates. -trustPolicy: no-downgrade diff --git a/src/components/crosschain/IntentForm.tsx b/src/components/crosschain/IntentForm.tsx index 42a7b7a..b4992de 100644 --- a/src/components/crosschain/IntentForm.tsx +++ b/src/components/crosschain/IntentForm.tsx @@ -116,6 +116,7 @@ export function IntentForm({ const createMidenP2IDNote = useMidenP2IDNoteFactory({ midenAccountId, + evmRecipient: destination.evmAddress?.trim() || null, onStatus: setConfirmStatus, onNoteCreated: setLocalMidenNoteId, }); diff --git a/src/hooks/useMidenP2IDNoteFactory.ts b/src/hooks/useMidenP2IDNoteFactory.ts index 3465f89..902a8c9 100644 --- a/src/hooks/useMidenP2IDNoteFactory.ts +++ b/src/hooks/useMidenP2IDNoteFactory.ts @@ -1,23 +1,62 @@ import { useCallback } from "react"; import { useMidenFiWallet } from "@miden-sdk/miden-wallet-adapter-react"; -import { SendTransaction } from "@miden-sdk/miden-wallet-adapter-base"; +import { Transaction } from "@miden-sdk/miden-wallet-adapter-base"; +import { useMiden } from "@miden-sdk/react"; +import { + Note, + NoteType, + AccountId, + NoteAssets, + FungibleAsset, + NoteArray, + NoteAttachment, + TransactionRequestBuilder, +} from "@miden-sdk/miden-sdk"; import type { SolveIntentParams } from "@epoch-protocol/epoch-intents-sdk"; - -const WAIT_FOR_TRANSACTION_TIMEOUT_MS = 120_000; +import { encodeEvmRecipientToFelts } from "@epoch-protocol/epoch-intents-sdk"; interface Options { midenAccountId: string | null; + /** + * EVM payout recipient to bind into the note (F-01). Written as a note + * attachment so the allocator can reject any intent whose recipient does not + * match — closing the note-theft path. + */ + evmRecipient: string | null; onStatus: (message: string) => void; onNoteCreated: (noteId: string) => void; } -/** Mints the recallable P2IDE note the SDK asks for when an intent needs a resource lock. */ +const WAIT_FOR_TRANSACTION_TIMEOUT_MS = 120_000; + +/** Epoch Miden ids are 0x-hex; fall back to bech32 for wallet-formatted ids. */ +function toAccountId(id: string): AccountId { + const s = id.trim(); + return s.startsWith("0x") ? AccountId.fromHex(s) : AccountId.fromBech32(s); +} + +/** + * Mints the recallable P2IDE collateral note AND binds it to the EVM payout + * recipient via a note attachment (F-01). + * + * Submitted through the WALLET (`requestTransaction` + `createCustomTransaction`) + * rather than the SDK client's `useTransaction`: the wallet holds the account's + * state, whereas the SDK client's local store may not (which caused + * "account data wasn't found"). The wallet's `SendTransaction` can't carry an + * attachment, so we build a custom `TransactionRequest` whose output note is a + * P2IDE note created with `Note.createP2IDENote(..., reclaim, type, attachment)` + * — the one API that supports reclaim + attachment together. + */ export function useMidenP2IDNoteFactory({ midenAccountId, + evmRecipient, onStatus, onNoteCreated, }: Options): SolveIntentParams["createMidenP2IDNote"] { - const { requestSend, waitForTransaction } = useMidenFiWallet(); + const { requestTransaction, waitForTransaction } = useMidenFiWallet(); + // useMiden() is non-throwing (unlike useMidenClient, which throws before the + // client initializes); we gate on readiness inside the callback instead. + const { client, isReady } = useMiden(); return useCallback>( async (faucetIdParam, amountParam, allocatorId, recallBlocks) => { @@ -26,41 +65,73 @@ export function useMidenP2IDNoteFactory({ if (!midenAccountId) { throw new Error("Missing Miden account id"); } - if (!requestSend) { - throw new Error("Miden wallet adapter not available"); + if (!evmRecipient) { + throw new Error("Missing EVM recipient for note binding"); + } + if (!requestTransaction) { + throw new Error("Wallet does not support custom transactions"); } - // Before requestSend: broadcasting first would strand the user's funds. - if (!waitForTransaction) { - throw new Error("waitForTransaction not available in adapter"); + if (!isReady || !client) { + throw new Error( + "Miden client not ready yet — retry once it initializes", + ); } - const normalizedAmount = BigInt(amountParam); - if (normalizedAmount > BigInt(Number.MAX_SAFE_INTEGER)) { - throw new Error("Amount too large for wallet adapter send"); + const assets = new NoteAssets([ + new FungibleAsset(toAccountId(faucetIdParam), BigInt(amountParam)), + ]); + // F-01 binding: recipient packed into the note attachment (part of the + // note commitment, tamper-proof). + const attachment = new NoteAttachment( + BigUint64Array.from(encodeEvmRecipientToFelts(evmRecipient)), + ); + + // P2IDE reclaim height is ABSOLUTE; the SDK gives a RELATIVE recallBlocks + // (allocator min + buffer). Convert against the client's synced chain tip + // (getSyncHeight needs the chain, not the account). + const currentBlock = await client.getSyncHeight(); + if (!Number.isFinite(currentBlock) || currentBlock <= 0) { + throw new Error( + "Miden client not synced yet — retry once the block height is available", + ); + } + const reclaimHeight = currentBlock + recallBlocks; + const note = Note.createP2IDENote( + toAccountId(midenAccountId), + toAccountId(allocatorId), + assets, + reclaimHeight, + undefined, // no time-lock + NoteType.Public, + attachment, + ); + const noteId = note.id().toString(); + + const txRequest = new TransactionRequestBuilder() + .withOwnOutputNotes(new NoteArray([note])) + .build(); + + if (!noteId) { + throw new Error("Could not compute note id for the minted note"); } - // recallBlocks comes from the SDK (allocator's published reclaim minimum - // + buffer). Without it the note mints as a plain P2ID with no recall - // window, so a failed intent would strand the funds. - const payload = new SendTransaction( + // Submit through the wallet (holds the account + signs). + const customTx = Transaction.createCustomTransaction( midenAccountId, allocatorId, - faucetIdParam, - "public", - Number(normalizedAmount), - recallBlocks, + txRequest, ); - const txId = await requestSend(payload); + const txId = await requestTransaction(customTx); - const finalized = await waitForTransaction( - txId, - WAIT_FOR_TRANSACTION_TIMEOUT_MS, - ); - const first = finalized.outputNotes?.[0]; - const noteId = first ? first.id().toString() : ""; - if (!noteId) { - throw new Error(`Could not read output note id for tx ${txId}`); + // Wait for finalization before returning, so the note is committed and + // queryable when the allocator fetches it during intent validation. + // Without this the intent can race ahead of the note and be rejected + // "not found on-chain". + if (waitForTransaction) { + onStatus("P2IDE note created — waiting for finalization on Miden…"); + await waitForTransaction(txId, WAIT_FOR_TRANSACTION_TIMEOUT_MS); } + onNoteCreated(noteId); return { success: true, noteId }; } catch (err) { @@ -70,6 +141,15 @@ export function useMidenP2IDNoteFactory({ }; } }, - [midenAccountId, requestSend, waitForTransaction, onStatus, onNoteCreated], + [ + midenAccountId, + evmRecipient, + requestTransaction, + waitForTransaction, + client, + isReady, + onStatus, + onNoteCreated, + ], ); } diff --git a/src/main.tsx b/src/main.tsx index 44d8abf..2666aa6 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -10,6 +10,7 @@ import { Toaster } from "sonner"; import App from "./App"; import { config } from "./config/wagmi"; import { MidenFiSignerProvider } from "@miden-sdk/miden-wallet-adapter-react"; +import { MidenProvider } from "@miden-sdk/react"; import { AllowedPrivateData, WalletAdapterNetwork, @@ -34,7 +35,12 @@ createRoot(document.getElementById("root")!).render( appName="Miden Integration Example" allowedPrivateData={AllowedPrivateData.Assets} > - + {/* MidenProvider powers the @miden-sdk/react hooks (useSend / + useSyncState) that the P2IDE note factory uses to write the F-01 + recipient-binding attachment. */} + + + diff --git a/vite.config.ts b/vite.config.ts index b79e2ee..67910c2 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -20,6 +20,11 @@ export default defineConfig({ }, optimizeDeps: { exclude: ["@miden-sdk/miden-sdk"], + // Force Vite to pre-bundle the linked local SDK. Linked (file:) deps are not + // optimized by default, so its CommonJS transitive deps (e.g. lodash) would + // lack an ESM default export and fail at runtime. Pre-bundling interops them. + // After rebuilding the SDK, restart the dev server with `--force` to refresh. + include: ["@epoch-protocol/epoch-intents-sdk"], }, build: { target: "esnext", From b3deced643ee84e6aaeb20d255aec39d30fb069c Mon Sep 17 00:00:00 2001 From: jasspreetbawa13 Date: Sat, 25 Jul 2026 00:11:19 +0530 Subject: [PATCH 2/2] feat(miden): mint P2IDE note with SDK-provided mandate-binding felts Factory writes the binding attachment felts the SDK now passes as the 5th callback arg; drop the client-side output-leg fields. --- src/components/crosschain/IntentForm.tsx | 1 - src/hooks/useMidenP2IDNoteFactory.ts | 31 +++++++++++------------- src/services/epoch-bridge.ts | 2 +- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/components/crosschain/IntentForm.tsx b/src/components/crosschain/IntentForm.tsx index b4992de..42a7b7a 100644 --- a/src/components/crosschain/IntentForm.tsx +++ b/src/components/crosschain/IntentForm.tsx @@ -116,7 +116,6 @@ export function IntentForm({ const createMidenP2IDNote = useMidenP2IDNoteFactory({ midenAccountId, - evmRecipient: destination.evmAddress?.trim() || null, onStatus: setConfirmStatus, onNoteCreated: setLocalMidenNoteId, }); diff --git a/src/hooks/useMidenP2IDNoteFactory.ts b/src/hooks/useMidenP2IDNoteFactory.ts index 902a8c9..535b155 100644 --- a/src/hooks/useMidenP2IDNoteFactory.ts +++ b/src/hooks/useMidenP2IDNoteFactory.ts @@ -13,16 +13,9 @@ import { TransactionRequestBuilder, } from "@miden-sdk/miden-sdk"; import type { SolveIntentParams } from "@epoch-protocol/epoch-intents-sdk"; -import { encodeEvmRecipientToFelts } from "@epoch-protocol/epoch-intents-sdk"; interface Options { midenAccountId: string | null; - /** - * EVM payout recipient to bind into the note (F-01). Written as a note - * attachment so the allocator can reject any intent whose recipient does not - * match — closing the note-theft path. - */ - evmRecipient: string | null; onStatus: (message: string) => void; onNoteCreated: (noteId: string) => void; } @@ -36,8 +29,8 @@ function toAccountId(id: string): AccountId { } /** - * Mints the recallable P2IDE collateral note AND binds it to the EVM payout - * recipient via a note attachment (F-01). + * Mints the recallable P2IDE collateral note, binding it to the intent's mandate + * via the attachment felts the SDK computed (Compact-equivalent witness hash). * * Submitted through the WALLET (`requestTransaction` + `createCustomTransaction`) * rather than the SDK client's `useTransaction`: the wallet holds the account's @@ -49,7 +42,6 @@ function toAccountId(id: string): AccountId { */ export function useMidenP2IDNoteFactory({ midenAccountId, - evmRecipient, onStatus, onNoteCreated, }: Options): SolveIntentParams["createMidenP2IDNote"] { @@ -59,14 +51,20 @@ export function useMidenP2IDNoteFactory({ const { client, isReady } = useMiden(); return useCallback>( - async (faucetIdParam, amountParam, allocatorId, recallBlocks) => { + async ( + faucetIdParam, + amountParam, + allocatorId, + recallBlocks, + bindingAttachmentFelts, + ) => { onStatus("Resource lock required — creating P2IDE note on Miden…"); try { if (!midenAccountId) { throw new Error("Missing Miden account id"); } - if (!evmRecipient) { - throw new Error("Missing EVM recipient for note binding"); + if (!bindingAttachmentFelts?.length) { + throw new Error("Missing mandate-binding attachment felts from SDK"); } if (!requestTransaction) { throw new Error("Wallet does not support custom transactions"); @@ -80,10 +78,10 @@ export function useMidenP2IDNoteFactory({ const assets = new NoteAssets([ new FungibleAsset(toAccountId(faucetIdParam), BigInt(amountParam)), ]); - // F-01 binding: recipient packed into the note attachment (part of the - // note commitment, tamper-proof). + // Mandate binding: witness hash the SDK computed, written verbatim as the + // note attachment (part of the note commitment, tamper-proof). const attachment = new NoteAttachment( - BigUint64Array.from(encodeEvmRecipientToFelts(evmRecipient)), + BigUint64Array.from(bindingAttachmentFelts), ); // P2IDE reclaim height is ABSOLUTE; the SDK gives a RELATIVE recallBlocks @@ -143,7 +141,6 @@ export function useMidenP2IDNoteFactory({ }, [ midenAccountId, - evmRecipient, requestTransaction, waitForTransaction, client, diff --git a/src/services/epoch-bridge.ts b/src/services/epoch-bridge.ts index 2019ddd..193c4b1 100644 --- a/src/services/epoch-bridge.ts +++ b/src/services/epoch-bridge.ts @@ -66,7 +66,7 @@ export function formatQuoteTokenIn( * intent is fulfilled — privacy-preserving on the Miden side, trustless on EVM side. */ -const ZERO_HASH = +export const ZERO_HASH = "0x0000000000000000000000000000000000000000000000000000000000000000"; export function normalizeMidenIdToHex(id: string): string {