Create an on-chain intent plus an encrypted recipient bundle. The two together form a complete submission.
The TypeScript SDK is WIP. Today, submission is two steps: an on-chain createIntent call on BridgeIntentV2, plus an encrypted recipient bundle stored on the relayer via POST /store-recipients. The SDK will wrap both into a single submit call.
A submission is two artifacts:
An on-chain intent (the public part) created by calling BridgeIntentV2.createIntent on the source chain. This is the solver-facing offer: “I’m offering X of tokenA for Y of tokenB delivered on chain Z, plus a reward of R.”
An encrypted recipient bundle (the confidential part) stored on the relayer. This contains the actual recipient addresses and per-recipient amounts, encrypted to the relayer’s ECIES public key.
Together, they form a complete confidential intent. Solvers see the public offer pre-bid (batched with others so they can’t tell individual amounts apart), and only the winning solver receives the re-encrypted recipient bundle.
The on-chain call publishes the solver-facing parameters plus an encrypted blob that only the relayer can decrypt (the on-chain recovery anchor, see privacy guarantees).
After the on-chain intent is mined, tell the relayer who should actually receive the funds on the destination chain. This information is encrypted; the relayer stores it and only reveals it (re-encrypted) to the solver that wins the bid.
// Step A: fetch the relayer's ECIES public keyconst { publicKey } = await fetch(`${RELAYER_URL}/ecies-pubkey`).then(r => r.json());// Step B: build the plaintext recipient bundleconst plaintext = JSON.stringify({ recipients: ["0xRecipient1", "0xRecipient2"], amounts: ["500000000", "499000000"], // base units of tokenB, must sum to <= expectedAmountB});// Step C: ECIES-encrypt with the relayer's pubkeyconst encrypted = await eciesEncrypt(publicKey, Buffer.from(plaintext));// Step D: POST to the relayerawait fetch(`${RELAYER_URL}/store-recipients`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ intentId, chainId: destChainId, encryptedData: encrypted.toString("hex"), }),});
For payroll, refunds, or any N-recipient flow, submit many intents in a loop today:
const intentIds = [];for (const item of items) { const id = await submitOne(item); // createIntent + /store-recipients for each intentIds.push(id);}
An atomic batched-intent contract path is planned so the whole batch can land in a single on-chain transaction and share a single signature. Today, each recipient = one createIntent call.
createIntent can revert for on-chain reasons (insufficient allowance, bad addresses, zero amount, past deadline). /store-recipients can reject if the intent ID doesn’t exist on-chain, the chain ID is wrong, or the encrypted payload can’t be decoded.Typical client-side handling:
try { const id = await submitOne(item);} catch (err) { switch (err.code) { case "INSUFFICIENT_ALLOWANCE": case "INSUFFICIENT_BALANCE": case "DEADLINE_IN_PAST": // Fixable client-side break; case "RATE_LIMITED": // Platform throughput ceiling hit, back off with jitter break; }}