Skip to main content
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:
  1. 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.”
  2. 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.

1. Create the on-chain intent

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).
BridgeIntentV2.createIntent(
  address _tokenA,
  address _tokenB,
  uint256 _amountA,
  uint256 _expectedAmountB,
  uint256 _reward,
  uint256 _destChainId,
  uint256 _auctionDuration
)
Using ethers:
import { Contract } from "ethers";

const bridge = new Contract(bridgeAddress, bridgeIntentABI, wallet);
const tx = await bridge.createIntent(
  tokenA,                    // source token address
  tokenB,                    // destination token address
  amountIn,                  // amount of tokenA
  minAmountOut,              // minimum tokenB on destination (your slippage floor)
  reward,                    // solver reward, paid in tokenA from amountIn
  destChainId,               // numeric destination chain ID
  auctionDuration            // seconds, minimum 20
);
await tx.wait();
const intentId = await bridge.getLatestIntentId();
Before calling this, the user must have approved tokenA to the bridge contract for amountIn + reward.

2. Store the encrypted recipient bundle

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 key
const { publicKey } = await fetch(`${RELAYER_URL}/ecies-pubkey`).then(r => r.json());

// Step B: build the plaintext recipient bundle
const 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 pubkey
const encrypted = await eciesEncrypt(publicKey, Buffer.from(plaintext));

// Step D: POST to the relayer
await fetch(`${RELAYER_URL}/store-recipients`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    intentId,
    chainId: destChainId,
    encryptedData: encrypted.toString("hex"),
  }),
});
See POST /store-recipients for the full request shape.

How solvers see your intent

Once both steps are done, the relayer does the following:
  1. Batches your intentId with other live intents that share the same token pair + chain pair.
  2. Broadcasts to all solvers in plaintext: rate, token pair, total batch size, available fees. No sender, no per-intent recipient, no per-intent amount.
  3. Waits for a solver to bid to fill some portion of the batch.
  4. On bid win, re-encrypts the recipient bundle to the winning solver’s keypair and hands it over.
  5. Solver delivers to the stealth address(es) on the destination chain.
You never see any of this, just track the intentId until settlement.

Planned SDK surface

const { intentId } = await tachyon.intent.submit({
  sourceChain: "base_mainnet",
  destChain:   "horizen_mainnet",
  tokenIn:  USDC_BASE,
  tokenOut: USDC_HORIZEN,
  amountIn: 1_000_000_000n,
  minAmountOut: 999_000_000n,
  reward: 50_000_000n,                     // 5% of amountIn is a common default
  deadline: Math.floor(Date.now() / 1000) + 3600,
  recipients: [{ address: "0xRecipient...", amount: 999_000_000n }],
  viewers: ["0xAuditor..."],                // optional
}, wallet);
This wraps the two steps above: signs and sends createIntent, then encrypts the recipient list and posts to /store-recipients.

Submitting a batch

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.

Validation errors

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;
  }
}
See error reference for the complete list. The _auctionDuration arg on createIntent sets how long solvers have to bid before the bridge finalizes the winner.
Use caseSuggested auctionDuration
Interactive UI (“send now”)60–120 seconds
Batch / payroll60–300 seconds per intent
TWAP tranche-levelperiod / 2 (minimum 20 seconds, service default)
Server-to-server backfill300–600 seconds
A longer auction gives more solvers a chance to bid (better rates). A shorter one ships faster but may get no bid if solvers are idle.

Track intents to settlement

Polling, subscriptions, and webhooks.