> For the complete documentation index, see [llms.txt](https://docs.zama.org/protocol/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zama.org/protocol/confidential-swap/guides/create-an-intent.md).

# Create an intent

Submit a confidential swap intent directly against the contract, using the Zama SDK to encrypt amounts, the real index, and your slippage floor.

How to submit a confidential swap intent directly against the contract, without the web app.

## Dependencies

The only hard requirements are an EVM client and the [Zama SDK](https://docs.zama.org/protocol/sdk), which handles encryption and decryption against the relayer.

```bash
pnpm add @zama-fhe/sdk viem
```

You also need the `ConfidentialSwap` proxy address from [Contract addresses](/protocol/confidential-swap/reference/addresses.md), an account funded with gas, and a confidential balance on the asset you are selling.

{% hint style="warning" %}
**Balances are part of your bundle's privacy, not just its funding.** Whether a confidential balance exists is public, and your public wraps bound how much it could hold. A candidate selling an asset you never initialized, or claiming a size you could never have funded, is one an observer discards — and the decoys around your real order go with it. Initialize and fund every asset your bundles sell, well before you trade on it. See [Protecting your privacy](/protocol/confidential-swap/protocol/privacy.md).
{% endhint %}

## 1. Construct the SDK once

Build the SDK once at startup and reuse it for every encryption and decryption. It caches the key material it fetches from the relayer in the storage you give it, so constructing a new one per call pays that round-trip again.

```ts
import { MemoryStorage, ZamaSDK } from "@zama-fhe/sdk";
import { mainnet } from "@zama-fhe/sdk/chains";
import { node } from "@zama-fhe/sdk/node";
import { createConfig } from "@zama-fhe/sdk/viem";

const chain = { ...mainnet, network: RPC_URL, relayerUrl: RELAYER_URL };

const sdk = new ZamaSDK(
  createConfig({
    chains: [chain],
    relayers: { [chain.id]: node() }, // FHE WASM in worker_threads; use `web()` from `@zama-fhe/sdk/web` in a browser
    publicClient,
    walletClient,
    storage: new MemoryStorage(),
  }),
);
```

## 2. Authorize the operator

The contract pulls your funds as an operator on the confidential token. Grant it on **every asset your bundle sells**, once per expiry period. Grants are public events, and their pattern can leak: granting the real asset first and the decoys after hands an observer a hint. Issue them for every asset you may ever sell, in one batch unrelated to any trade.

```ts
await walletClient.writeContract({
  address: confidentialToken,
  abi: erc7984Abi,
  functionName: "setOperator",
  args: [SWAP_CONTRACT_ADDRESS, expiryTimestamp],
});
```

Assets your bundle only buys need no authorization.

## 3. Pick your candidates

Every candidate names a registered, active pair. Discover them from the contract rather than hard-coding:

```ts
const pairIds = await publicClient.readContract({
  address: SWAP_CONTRACT_ADDRESS, abi, functionName: "getActivePairIds",
});
// computePairId(asset0, asset1) is also available for a pair you already know.
// Assets are stored in canonical ascending address order.
const pair = await publicClient.readContract({
  address: SWAP_CONTRACT_ADDRESS, abi, functionName: "getPair", args: [pairIds[0]],
});
```

`fromAsset0To1` is `true` to sell `asset0` for `asset1`, `false` for the other direction.

One candidate is the trade the user actually wants; the rest exist to cover it. The contract accepts any valid bundle, so how much cover they provide is entirely your decision:

* **Start from the privacy you are aiming for.** Hiding the direction takes the same pair in both directions. Hiding what the user buys takes one sell asset and several buy assets. Hiding both takes several pairs and directions. Each of those costs more funded assets — see [Choose the privacy you need](/protocol/confidential-swap/protocol/privacy.md#choose-the-privacy-you-need).
* **Fit the decoys to the wallet in front of you.** A candidate on an asset the user could not credibly have funded is one an observer discards. With only cUSDC funded you can still hide what they are buying, but not that they are selling cUSDC. Either fund the other assets first, or aim for the privacy this wallet can support — and be explicit about which you did.
* **Give the decoy amounts the same shape as the real one.** Market makers decrypt every candidate's amount. A real `100.000000` beside a decoy `99.9994` is not cover: the two numbers were plainly not produced the same way. Match the entropy of the real amount: same magnitude, same precision.

{% hint style="warning" %}
**Candidates must be sorted in strictly ascending `(pairId, fromAsset0To1)` order.** The contract rejects any other arrangement, and the rule exists so the real order's position cannot leak. Sort the bundle *after* you decide which candidate is real, then find its index in the sorted array.
{% endhint %}

## 4. Encrypt

One proof covers every encrypted value: each candidate's amount, the real index, and your floor. Amounts are `euint64` in the confidential token's base units; the index is `euint8`.

```ts
const { encryptedValues, inputProof } = await sdk.encrypt({
  contractAddress: SWAP_CONTRACT_ADDRESS,
  userAddress: account.address,
  values: [
    ...orders.map((o) => ({ value: o.amountIn, type: "euint64" as const })),
    { value: BigInt(realIndex), type: "euint8" as const },
    { value: amountOutMin, type: "euint64" as const },
  ],
});
```

`amountOutMin` is the value the best quote is gated against, before the fee is taken. To guarantee a **net** amount, gross it up first — see [Fees and the slippage floor](/protocol/confidential-swap/protocol/fees.md).

## 5. Submit

```ts
const hash = await walletClient.writeContract({
  address: SWAP_CONTRACT_ADDRESS,
  abi,
  functionName: "createIntent",
  args: [
    {
      orders: orders.map((o, i) => ({
        pairId: o.pairId,
        fromAsset0To1: o.fromAsset0To1,
        eAmountIn: encryptedValues[i],
      })),
      eRealIndex: encryptedValues[orders.length],
      eAmountOutMin: encryptedValues[orders.length + 1],
      quotingDurationInBlocks,
      settlementDurationInBlocks,
      marketMakers: [], // empty = open to every market maker at creation time
    },
    inputProof,
  ],
});
```

The write returns a transaction hash that you can use to read the id from the `IntentCreated` event in the receipt:

```ts
const receipt = await publicClient.waitForTransactionReceipt({ hash });
const [created] = parseEventLogs({ abi, eventName: "IntentCreated", logs: receipt.logs });
const intentId = created.args.intentId;
```

Both durations are bounded by admin-set minimums and maximums. `settlementDurationInBlocks` is the *minimum* guaranteed window: when every market maker quotes before `quotingEndsAtBlock`, the unused quoting blocks are added to it. Size it with FHE decryption latency in mind — that latency grows with the number of quotes an intent received, and a window too short leaves the winner unable to settle.

Pass a non-empty `marketMakers` list to restrict the intent to a chosen subset. It must be sorted strictly ascending, and every entry must hold the market-maker role at creation.

## 6. Confirm the intent is live

{% hint style="warning" %}
**A successful transaction does not mean a funded intent.** Confidential transfers do not revert on an insufficient balance — they move zero. An underfunded intent, a zero real amount, or an out-of-range real index all produce a void intent that accepts quotes and can never settle.
{% endhint %}

Read `eIsFunded` from the `IntentCreated` event and public-decrypt it:

```ts
const { clearValues } = await sdk.decryption.decryptPublicValues([eIsFunded]);
if (!clearValues[eIsFunded]) throw new Error("intent is void");
```

## 7. Track the outcome

Follow `IntentQuoted`, `IntentResolved`, `IntentSettled` and `IntentReclaimed` — see [Events and errors](/protocol/confidential-swap/reference/events-and-errors.md) for the full signatures.

Encrypted attempts are not proof of success. `IntentSettled` and `IntentReclaimed` both carry a publicly decryptable `eSucceed` — public-decrypt it to learn whether anything actually moved. For the terminal state itself, `getIntent(intentId)` returns the intent with its publicly decryptable `eIsFinalized`. User-decrypt your own amounts to learn how much moved:

```ts
const values = await sdk.decryption.decryptValues([
  { encryptedValue: eAmountOut, contractAddress: SWAP_CONTRACT_ADDRESS },
]);
```

## 8. Reclaim if nothing fills

```ts
await walletClient.writeContract({
  address: SWAP_CONTRACT_ADDRESS, abi, functionName: "reclaimExpiredIntent",
  args: [intentId, noWinnerProof],
});
```

`noWinnerProof` is empty bytes on the no-quote path and after the settlement window closes. It is only required to reclaim **early** — while the settlement window is still open on an intent that was quoted and resolved with no winner — where it must be a public-decryption proof that the resolved best quoter is the zero address.

## Next steps

* [Decoy bundles](/protocol/confidential-swap/protocol/decoy-bundles.md) — choosing candidates that actually hide your trade
* [Contract interface](/protocol/confidential-swap/reference/contract-interface.md) — every entry point with its gate
* [Market makers](/protocol/confidential-swap/guides/market-makers.md) — the other side of the auction


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.zama.org/protocol/confidential-swap/guides/create-an-intent.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
