> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tachyon.pe/llms.txt
> Use this file to discover all available pages before exploring further.

# Guide: confidential cross-chain transfer

> Full walkthrough from the sender's first signature on chain A to the recipient's balance landing on chain B.

<Warning>
  The TypeScript SDK is WIP. This guide shows the raw two-step flow you can wire up today.
</Warning>

## Full flow at a glance

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant Sender as Sender (chain A)
    participant Chain A as Chain A (source)
    participant Tachyon
    participant Solvers
    participant Chain B as Chain B (destination)
    participant Recipient

    Note over Sender: inputs: tokenIn, tokenOut, amountIn,<br/> minAmountOut, reward, destChainId, recipient
    Sender->>Chain A: approve(bridge, amountIn + reward)
    Sender->>Chain A: createIntent(...public fields...)
    Chain A-->>Tachyon: event (public fields + encrypted recovery blob)
    Sender->>Tachyon: POST /store-recipients (ECIES-encrypted bundle)
    Tachyon->>Solvers: batched plaintext broadcast (rate, token, fees)
    Solvers->>Tachyon: bid
    Tachyon-->>Solvers: re-encrypt recipient to winning solver
    Solvers->>Chain B: deliver tokens to stealth Safe
    Tachyon->>Chain A: settle (release solver reward)
    Recipient->>Tachyon: POST /recipient/relay-proxy (signed Safe tx)
    Tachyon->>Chain B: relay-proxy broadcast (gasless sweep)
    Chain B-->>Recipient: funds at recipient's main address
```

This guide walks through every step.

## Prerequisites

* A sender wallet on the source chain (chain A) with `tokenIn` and a little native gas
* The recipient registered once via [`POST /recipient/register`](/api/relayer) so the relayer can derive their stealth keys
* An ECIES encrypt function (see [the `eciesEncrypt` helper in endpoints-and-helpers](/reference/endpoints-and-helpers#ecies-encrypt-for-post-store-recipients-and-twap-post-orders))

Service URLs and contract addresses used below are documented in [endpoints and helpers](/reference/endpoints-and-helpers).

## 1. Register the recipient (first time only)

```ts theme={null}
const message = `REGISTER_STEALTH:${recipientAddress}`;
const signature = await recipientWallet.signMessage(message);

await fetch("https://relayer.tachyon.pe/recipient/register", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ recipientAddress, signature }),
});
```

Calling again for the same address returns existing keys.

## 2. Approve the bridge contract on chain A

```ts theme={null}
import { Contract } from "ethers";
const token = new Contract(tokenIn, erc20ABI, senderWallet);
await (await token.approve(bridgeAddress, amountIn + reward)).wait();
```

`amountIn + reward` is what the bridge pulls when `createIntent` is called.

## 3. Call `createIntent` on chain A

Publishes the public solver-facing offer. The contract emits the public fields plus an encrypted blob that's only recoverable by the relayer's key, the on-chain recovery anchor.

```ts theme={null}
import { Contract } from "ethers";
const bridge = new Contract(bridgeAddress, bridgeIntentABI, senderWallet);

const tx = await bridge.createIntent(
  tokenIn,               // _tokenA
  tokenOut,              // _tokenB
  amountIn,              // _amountA
  minAmountOut,          // _expectedAmountB (slippage floor)
  reward,                // _reward (solver incentive)
  destChainId,           // _destChainId (e.g. 8453 for Base)
  auctionDuration        // seconds, minimum 20
);
await tx.wait();

const intentId = (await bridge.getLatestIntentId()).toString();
```

At this point the intent is live on-chain but the relayer doesn't yet know who the funds go to on the destination side.

## 4. Submit the encrypted recipient bundle

Use the `storeRecipients` helper from [endpoints and helpers](/reference/endpoints-and-helpers#submit-a-recipient-bundle-end-to-end):

```ts theme={null}
await storeRecipients({
  intentId,
  destChainId,
  recipients: ["0xRecipient..."],
  amounts:    ["4950000"], // base units of tokenOut on chain B, sum <= minAmountOut
});
```

This fetches the relayer's ECIES public key, encrypts `{ recipients, amounts }`, and POSTs to the relayer. Only the relayer (and later the winning solver, under re-encryption) can read this.

## 5. Track to completion

```ts theme={null}
const intent = await waitForCompletion(intentId);
console.log("destination tx:", intent.solveTxHash);
console.log("source settle tx:", intent.settleTxHash);
```

`waitForCompletion` is in [endpoints and helpers](/reference/endpoints-and-helpers#poll-an-intent-to-completion). See [tracking intents](/sdk/tracking-intents) for the full state machine.

## 6. Recipient side (chain B)

Funds land at a Safe at a fresh stealth address on chain B. The recipient:

1. Lists their stealth balances via [`GET /recipient/addresses`](/api/relayer) with a signed request.
2. Derives the stealth key for a specific delivery via [`POST /recipient/derive-stealth-key`](/api/relayer).
3. Signs a Safe transaction and calls [`POST /recipient/relay-proxy`](/api/relayer). The relayer broadcasts it and pays gas, so the recipient doesn't need native gas on chain B.

Full recipient flow in [retrieving funds](/sdk/retrieving-funds).

## Intent states and what happens at each

| State       | What it means                                            | Funds                                             |
| ----------- | -------------------------------------------------------- | ------------------------------------------------- |
| `pending`   | Intent on-chain, awaiting solver bids                    | Locked in bridge                                  |
| `solving`   | Solver won the auction, fulfilling on chain B            | Locked in bridge                                  |
| `settled`   | Chain B delivery confirmed, chain A settlement in flight | Released to solver on settlement                  |
| `completed` | Lifecycle terminal. `settleTxHash` set.                  | With recipient on chain B                         |
| `failed`    | Solver took it but failed to deliver                     | **Refunded to sender** via escape path on chain A |
| `expired`   | Auction closed with no bidder                            | **Refunded to sender** via escape path            |
| `cancelled` | Sender cancelled before a winner                         | **Refunded to sender**                            |

`failed`, `expired`, and `cancelled` never lose funds. The bridge contract's escape path refunds the sender.

## What happens under the hood

After step 4 submits the encrypted bundle, Tachyon:

1. Batches your intent with other live intents that share the same token pair and chain pair.
2. Broadcasts to solvers in plaintext: rate, token pair, total batch amount, available fees. No sender or recipient info.
3. When a solver bids to fill some portion of the batch, re-encrypts your recipient bundle to that solver's key.
4. Solver delivers on chain B to the stealth Safe.
5. Attestation from the secure enclave is verified on-chain before the sender's funds are released to the solver.

The only actor that ever sees the plaintext recipient address is the one solver that won the bid for your intent.

## Error handling

Common failure modes:

| Where                               | What                                     | How to fix                                                      |
| ----------------------------------- | ---------------------------------------- | --------------------------------------------------------------- |
| `createIntent` revert               | Insufficient allowance / balance         | Approve enough before calling                                   |
| `createIntent` revert               | Invalid destination chain ID             | Use a chain from [supported chains](/concepts/supported-chains) |
| `POST /store-recipients` 400        | Intent ID doesn't exist on-chain         | Wait for `createIntent` tx to mine before calling               |
| `POST /store-recipients` 400        | ECIES decryption failure                 | Re-fetch `/ecies-pubkey`; library curve mismatch                |
| Intent stays `pending` past auction | No solver took the offer                 | Retry with higher `reward` or a more liquid chain pair          |
| `expired` status                    | `auctionDuration` elapsed with no bidder | Sender refunded, retry with wider parameters                    |

See [error reference](/reference/errors) for the full list.

<Card title="Try it on testnet" icon="play" href="/quickstart">
  Five-minute version of this guide.
</Card>
