> ## Documentation Index
> Fetch the complete documentation index at: https://anypay-edge.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Building Actions

> Full builder API: swap, lend, deposit, assertCondition, and custom

```ts theme={null}
import {
  swap,
  lend,
  deposit,
  assertCondition,
  custom,
  dynamic,
  self,
} from '0xtrails'
```

Amounts are generally one of:

* **`string`** — human-readable, e.g. `"50"` or `"0.03"`. Scaled by the token's decimals.
* **`bigint`** — raw wei, used as-is.
* **`dynamic()`** — "use the intent wallet's runtime balance at execution time", accepted only on fields that spend a balance. See [Dynamic Values](/sdk/composable-actions/dynamic-values).

## `swap` — on-chain token swap

Trade type is inferred from which amount you pass: `amountIn` → `EXACT_INPUT`, `amountOut` → `EXACT_OUTPUT`. Setting both (or neither) throws.

`dynamic()` is allowed on `amountIn` for exact-input swaps and `maxAmountIn` for exact-output swaps. `amountOut` and `minAmountOut` must be concrete values because they are execution thresholds.

```ts theme={null}
// EXACT_INPUT: spend 1 WETH, revert if output < 3000 USDC
swap({
  tokenIn: 'WETH',
  tokenOut: 'USDC',
  amountIn: '1',
  minAmountOut: '3000',
  fee: '0.05', // "0.01" | "0.05" | "0.3" (default) | "1"
})

// EXACT_OUTPUT: receive exactly 3000 USDC, spend at most 2 WETH
swap({
  tokenIn: 'WETH',
  tokenOut: 'USDC',
  amountOut: '3000',
  maxAmountIn: '2',
})

// Route through SushiSwap instead of Uniswap V3
swap({
  provider: 'SUSHISWAP_V3',
  tokenIn: 'USDC',
  tokenOut: 'WETH',
  amountIn: '100',
})
```

**Defaults:**

* `fee`: `"0.3"` (0.3% pool).
* `provider`: `"UNISWAP_V3"`. If it isn't deployed on the destination chain, the error tells you to try SushiSwap.
* `recipient`: the intent wallet for the final action; for intermediate actions, output flows back so the next action can consume it. Override only if you want to send output to a third party.

## Swap utilities

For anything that isn't "build calldata for `useQuote`" — on-chain quoting, pool discovery, raw `Call[]` composition, address lookups — `0xtrails` exports the underlying chain-scoped helpers behind `swap`. The same adapter shape is exported for both Uniswap V3 and SushiSwap V3: `quoteSwap`, `getPool`, `swapExactInputSingle`, and `swapExactOutputSingle`.

```ts theme={null}
import { uniswapV3, sushiswapV3 } from '0xtrails'
```

<Info>
  Low-level helpers take raw `bigint` wei — use viem's `parseUnits` to convert from human-readable amounts. Fee tiers take human strings: `"0.01"` / `"0.05"` / `"0.3"` / `"1"`.
</Info>

Typical use cases:

* **Pre-flight quoting** — render expected output / price impact in the UI before the user commits, using `quoteSwap`.
* **Pool liquidity check** — skip a pair or switch `fee` tier when `getPool` returns `null`.
* **Custom compositions** — build `Call[]` directly and pass them through `to.calls` (`useQuote`) or `calls` (`useTrailsSendTransaction`) when you need something the typed `swap` doesn't cover (e.g. multi-hop paths, permit2, …).

### Chain-scoped adapter

`uniswapV3.onChain(chain)` (or `sushiswapV3.onChain(chain)`) returns an adapter bound to a specific chain (name or chain ID):

```ts theme={null}
import { uniswapV3 } from '0xtrails'

const uni = uniswapV3.onChain('base')
// or: uniswapV3.onChain(8453)
```

### `quoteSwap`

Query the provider's V3 quoter — no signing, no broadcast. Use this to render expected output, gas estimate, and price movement before the user commits.

```ts theme={null}
import { parseUnits } from 'viem'

const quote = await uni.quoteSwap({
  type: 'exactInputSingle',
  tokenIn: 'USDC',
  tokenOut: 'WETH',
  fee: '0.05',
  amountIn: parseUnits('100', 6), // 100 USDC
})

// quote.amountOut
// quote.gasEstimate
// quote.initializedTicksCrossed
// quote.sqrtPriceX96After
```

`type` can be `"exactInputSingle"` or `"exactOutputSingle"`. Pass `amountIn` for exact input quotes. For exact output quotes, pass the desired output amount as `amountOutMinimum`:

```ts theme={null}
const exactOutputQuote = await uni.quoteSwap({
  type: 'exactOutputSingle',
  tokenIn: 'USDC',
  tokenOut: 'WETH',
  fee: '0.05',
  amountIn: 0n, // ignored for exactOutputSingle
  amountOutMinimum: parseUnits('0.03', 18), // desired WETH output
})
```

### `getPool`

Find the pool address for a pair and fee tier. Returns `null` when no pool exists — handy for switching fee tier or skipping the pair.

```ts theme={null}
const pool = await uni.getPool({
  token0: 'USDC',
  token1: 'WETH',
  fee: '0.05',
})

if (!pool) {
  // Try a different fee tier or route
}
```

### `swapExactInputSingle`

Build an approval + swap `Call[]` for an exact-input swap. Pass the calls through `to.calls` / `calls` to embed inside an intent, or send them directly from a wallet.

```ts theme={null}
import { parseUnits } from 'viem'

const calls = uni.swapExactInputSingle({
  tokenIn: 'USDC',
  tokenOut: 'WETH',
  fee: '0.05',
  recipient: userEoa,
  amountIn: parseUnits('100', 6),           // 100 USDC
  amountOutMinimum: parseUnits('0.03', 18), // 0.03 WETH floor
})
```

SushiSwap V3 uses the same fields plus a required `deadline`:

```ts theme={null}
import { sushiswapV3 } from '0xtrails'
import { parseUnits } from 'viem'

const sushi = sushiswapV3.onChain('base')
const deadline = BigInt(Math.floor(Date.now() / 1000) + 20 * 60)

const calls = sushi.swapExactInputSingle({
  tokenIn: 'USDC',
  tokenOut: 'WETH',
  fee: '0.05',
  recipient: userEoa,
  deadline,
  amountIn: parseUnits('100', 6),
  amountOutMinimum: parseUnits('0.03', 18),
})
```

### `swapExactOutputSingle`

Build an approval + swap `Call[]` for an exact-output swap.

```ts theme={null}
import { parseUnits } from 'viem'

const calls = uni.swapExactOutputSingle({
  tokenIn: 'USDC',
  tokenOut: 'WETH',
  fee: '0.05',
  recipient: userEoa,
  amountOut: parseUnits('0.03', 18),     // 0.03 WETH exactly
  amountInMaximum: parseUnits('100', 6), // 100 USDC ceiling
})
```

For SushiSwap V3 exact-output calls, include `deadline` as well.

### Address lookups

Top-level helpers return contract addresses per chain:

```ts theme={null}
uniswapV3.swapRouterAddressOn('base')
uniswapV3.factoryAddressOn('base')

sushiswapV3.swapRouterAddressOn('base')
sushiswapV3.factoryAddressOn('base')
```

### Capability checks

Not every chain has both providers deployed. `isSupportedOn` returns `false` instead of throwing — useful for falling back between providers or warning the user.

```ts theme={null}
import { uniswapV3, sushiswapV3 } from '0xtrails'

const adapter = uniswapV3.isSupportedOn('base')
  ? uniswapV3.onChain('base')
  : sushiswapV3.isSupportedOn('base')
    ? sushiswapV3.onChain('base')
    : null

if (!adapter) {
  // Neither provider is deployed on this chain — surface a friendly error.
}
```

## `lend` — supply into a money-market

Use for supported lending markets such as Aave. **Use [`useEarnMarkets`](/sdk/composable-actions/markets-and-providers) to fetch the `marketId`.**

```ts theme={null}
lend({
  marketId: 'base-usdc-aave-v3-lending',
  amount: '50',
})
```

Optional: `receiverAddress` defaults to the connected user wallet, so aTokens / fTokens / cTokens land on the user's EOA automatically. Only override when you want the position to belong to a different owner.

## `deposit` — deposit into a vault

Use for vault-shaped markets (ERC-4626, Morpho, Yearn, SummerFi, Sky, …).

```ts theme={null}
deposit({
  marketId: 'base-usdc-pusdc-0x1401d1271c47648ac70cbcdfa3776d4a87ce006b-4626-vault',
  amount: '100',
})
```

Optional params:

* `receiverAddress` — **defaults to the connected user wallet** so vault shares land on the user's EOA.

## `assertCondition` — on-chain guard

If the condition is false at execution time the entire batch reverts.

```ts theme={null}
// Ensure the swap really produced >= 2400 USDC
assertCondition({ erc20Balance: { token: 'USDC', gte: '2400' } })

// Native balance check
assertCondition({ nativeBalance: { gte: '0.5' } })

// Deadline (unix timestamp)
assertCondition({ notExpired: { deadline: 1712600000 } })

// Allowance check
assertCondition({
  erc20Allowance: { token: 'USDC', spender: '0x…', gte: '1000' },
})
```

<Info>
  `assertCondition` is the only builder that does **not** accept `dynamic()` — its values must be known at quote time.
</Info>

## `custom` — arbitrary contract call (escape hatch)

Use when nothing above fits. Pair with `buildCall` and `erc20Utils` for ergonomic calldata — see [ERC-20 Helpers](/sdk/composable-actions/erc20-helpers).

```ts theme={null}
import { custom, erc20Utils } from '0xtrails'
import { encodeFunctionData, parseUnits } from 'viem'

const MY_CONTRACT = '0x0000000000000000000000000000000000001234'
const usdc = erc20Utils.USDC.addressOn('base')

const actions = [
  custom({
    ...erc20Utils.approve({
      tokenAddress: usdc,
      spender: MY_CONTRACT,
      amount: parseUnits('100', 6),
    }),
  }),
  custom({
    to: MY_CONTRACT,
    data: encodeFunctionData({
      abi: stakingAbi,
      functionName: 'stake',
      args: [parseUnits('100', 6)],
    }),
  }),
]
```

If custom calldata contains a `dynamic()` amount, pass `dynamicAmountToken` so the encoder knows which ERC-20 balance to hydrate at execution time. The only exception is a bare ERC-20 `approve(spender, dynamic())`, where the token is inferred from the call target.
