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

# SDK Reference

> Constructor, REST API client, data helpers, transaction methods, result shapes, and errors for the Gauntlet SDK.

## Constructor

```typescript theme={null}
import { GauntletClient } from '@gauntlet-xyz/sdk'
import { createPublicClient, createWalletClient, http } from 'viem'
import { base } from 'viem/chains'

const client = new GauntletClient({
  evmClients: {
    [base.id]: createPublicClient({ chain: base, transport: http(process.env.RPC_URL_BASE!) }),
  },
  wallet: createWalletClient({
    account,
    chain: base,
    transport: http(process.env.RPC_URL_BASE!),
  }),
})
```

| Parameter         | Type                                     | Required                | Description                                                                                                                                                                                                                                      |
| ----------------- | ---------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `evmClients`      | `Record<number \| string, PublicClient>` | For transaction methods | Chain ID to viem PublicClient map                                                                                                                                                                                                                |
| `wallet`          | `WalletClient`                           | For transaction methods | Any viem-compatible WalletClient — used only to read the sender address. The SDK never signs.                                                                                                                                                    |
| `apiKey`          | `string`                                 | No                      | Partner API key from the Developer Portal — sent as `x-api-key` on `client.api` requests. Anonymous access is rate-limited.                                                                                                                      |
| `apiUrl`          | `string`                                 | No                      | Override the REST API origin. Defaults to `https://api.gauntlet.xyz`. In a browser this may be a relative path (e.g. a Next.js rewrite like `/gauntlet-api`), which resolves against the page origin; outside a browser a relative value throws. |
| `attributionMode` | `AttributionMode`                        | No                      | Defaults to `AttributionMode.PUBLIC`                                                                                                                                                                                                             |
| `builderCode`     | `string`                                 | No                      | Builder identifier for attribution — must be requested from Gauntlet, not self-serve. NOT the same as API key. Without it, transactions are unattributed.                                                                                        |

## Discover Vaults

```typescript theme={null}
import { getVaults } from '@gauntlet-xyz/sdk/evm'
// also available from the root: import { getVaults } from '@gauntlet-xyz/sdk'
import { base } from 'viem/chains'

const candidates = await getVaults(client, { chainId: base.id })
// returns:
// [
//   {
//     vaultId: "baseUsdcPrime",
//     name: "...",
//     protocol: "morpho",
//     deployments: [{ chainId: 8453, supplyToken: [{ symbol: "USDC", ... }], ... }]
//   },
//   ...
// ]
```

## Fee Wrapper Vaults (Partners)

Some partners integrate through a **fee wrapper vault**: an Aera vault that Gauntlet deploys exclusively for that partner, with the partner's fee terms applied. Because a fee wrapper vault belongs to one partner, it is not included in the SDK's bundled manifest. `getVaults` does not return it, and transaction functions throw `VaultNotFoundError` for its id.

To make your fee wrapper vault accessible through the SDK, register it with `client.setManifest`. `setManifest` replaces the whole manifest, so read the bundled one from `client.manifest` and append your vault to keep the standard vaults available:

```typescript theme={null}
const manifest = await client.manifest

client.setManifest({
  ...manifest,
  vaults: [
    ...manifest.vaults,
    {
      vaultId: 'acmeUsdcFeeWrapper', // any unique id, you choose it
      name: 'Acme USDC',
      protocol: 'aera',
      strategy: 'Fee Wrapper',
      deployments: [
        {
          chain: 'evm',
          chainId: 8453,
          vaultAddress: '0xYourFeeWrapperVault', // provided by Gauntlet
          vaultType: 'multi-depositor',
          supplyToken: [
            {
              address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
              symbol: 'USDC',
              decimals: 6,
            },
          ],
        },
      ],
    },
  ],
})
```

Gauntlet provides the deployment values (vault address, vault type, and supply tokens) when your fee wrapper vault is deployed. After registration, every SDK function accepts the id like any bundled vault:

```typescript theme={null}
const steps = await getDepositTx(client, {
  vaultId: 'acmeUsdcFeeWrapper',
  amount: 1_000_000n,
})
```

The manifest lives on the client instance. Call `setManifest` once on each `GauntletClient` you construct, before the first call that references the vault.

## Transaction Functions

Import from `@gauntlet-xyz/sdk/evm`. These communicate on-chain via your RPC URLs. Require `wallet` in the client config — the SDK reads the account address from `wallet.account`.

### getDepositTx

The `vaultId` string resolves to a `VaultDeployment` from the manifest. This is how the SDK knows which token to approve (`supplyToken[0].address`), which contract to call (`vaultAddress` or `provisionerAddress`), and whether the vault supports sync or async deposits.

```typescript theme={null}
import { getDepositTx, VaultId } from '@gauntlet-xyz/sdk/evm'
// also available from the root: import { getDepositTx, VaultId } from '@gauntlet-xyz/sdk'

const steps = await getDepositTx(client, {
  vaultId: VaultId.AeraUsdAlpha,
  amount: 1_000_000n,
  receiver: '0xReceiver', // optional, defaults to wallet account
})
```

| Parameter     | Type      | Required | Description                                                                                                                                                                                                                                                                                     |
| ------------- | --------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `vaultId`     | `string`  | Yes      | Vault identifier — resolves token, contract, and deposit mode from the manifest                                                                                                                                                                                                                 |
| `amount`      | `bigint`  | Yes      | Amount in token base units                                                                                                                                                                                                                                                                      |
| `chainId`     | `number`  | No       | Defaults to the vault's primary chain (Base for current multichain vaults)                                                                                                                                                                                                                      |
| `assetSymbol` | `string`  | No       | Required for multi-asset vaults to select the asset token                                                                                                                                                                                                                                       |
| `depositMode` | `string`  | No       | Override deposit mode: `'async'` (queued) or `'sync'` (instant). When omitted, uses the vault's native mode — async for Aera, sync for Morpho. Vaults with `depositMode: 'both'` default to async.                                                                                              |
| `receiver`    | `Address` | No       | Address that receives the minted vault units. Defaults to `wallet.account`. Aera V1 vaults require the receiver to equal the signer and throw `UnsupportedFeatureError` for any other address. On V2, sync deposits to a separate receiver require the receiver to approve the depositor first. |
| `slippageBps` | `number`  | No       | Slippage tolerance in basis points (e.g. `100` = 1%). Defaults to `100`. Must be an integer between 0 and 10000.                                                                                                                                                                                |
| `minUnitsOut` | `bigint`  | No       | Caller-reviewed minimum output for an explicit Aera V2 sync deposit. Must be greater than zero.                                                                                                                                                                                                 |

### getSyncDepositQuote

Returns the expected vault units, slippage-adjusted minimum, numeraire value, and Instant Supply fee
for an Aera V2 sync deposit.

```typescript theme={null}
const quote = await getSyncDepositQuote(client, {
  vaultId: VaultId.AeraUsdAlpha,
  amount: 1_000_000n,
  slippageBps: 100,
})
// quote: { unitsOut, minUnitsOut, numeraireOut, feeBps, slippageBps }
```

`slippageBps` is required in the returned `SyncDepositQuote` and records the basis-point tolerance
used to derive `minUnitsOut`. `feeBps` is the fee applied to the deposited token amount in basis
points. `numeraireOut` is the post-fee deposit amount converted to the vault's numeraire — use this,
not `unitsOut`, when displaying what the deposit is worth. Pass `quote.minUnitsOut` to `getDepositTx`
with `depositMode: 'sync'`. The value must be positive. If approval is required, confirm it, request
a fresh quote, and rebuild before asking for the deposit signature.

### getSyncDepositRate

Reads the live Aera V2 Instant Supply fee without requiring a deposit amount. The exported
`SyncDepositRateParams` type contains the same vault and token selectors used by
`getSyncDepositQuote`.

```typescript theme={null}
import { getSyncDepositRate, VaultId } from '@gauntlet-xyz/sdk/evm'

const rate = await getSyncDepositRate(client, {
  vaultId: VaultId.AeraUsdAlpha,
})
// rate: { feeBps }
```

| Parameter     | Type     | Required               | Description                                                                |
| ------------- | -------- | ---------------------- | -------------------------------------------------------------------------- |
| `vaultId`     | `string` | Yes                    | Aera V2 vault identifier                                                   |
| `chainId`     | `number` | No                     | Defaults to the vault's primary chain (Base for current multichain vaults) |
| `assetSymbol` | `string` | For multi-asset vaults | Selects the deposit token                                                  |

Unlike the withdraw rate, the deposit fee is a flat basis-point value with no price-age premium, so
`feeBps` can be shown as soon as Instant Supply is selected, before an amount is entered.

### getAeraTokenModeSupport

Reads currently available token modes from the live Aera runtime. Use the result to decide which deposit and withdrawal actions to show.

```typescript theme={null}
import { getAeraTokenModeSupport, VaultId } from '@gauntlet-xyz/sdk/evm'

const support = await getAeraTokenModeSupport(client, {
  vaultId: VaultId.AeraUsdAlpha,
})

if (support.syncRedeem) {
  // Offer an instant withdrawal.
}
```

Returns `Promise<AeraTokenModeSupport>`:

```typescript theme={null}
type AeraTokenModeSupport = {
  asyncDeposit: boolean
  asyncRedeem: boolean
  syncDeposit: boolean
  syncRedeem: boolean
}
```

The values combine token configuration with live runtime requirements. Both sync flags are `false` while the V2 solving gate pauses the provisioner/token pair, and `syncRedeem` is also `false` unless the active fee calculator is V2. Read failures surface to the caller.

| Parameter     | Type     | Required               | Description                                                                |
| ------------- | -------- | ---------------------- | -------------------------------------------------------------------------- |
| `vaultId`     | `string` | Yes                    | Aera vault identifier                                                      |
| `chainId`     | `number` | No                     | Defaults to the vault's primary chain (Base for current multichain vaults) |
| `assetSymbol` | `string` | For multi-asset vaults | Selects the token                                                          |

### getSyncWithdrawRate

Reads the live Aera V2 instant-withdraw multiplier without requiring a withdrawal size or account.
The exported `SyncWithdrawRateParams` type contains the same vault and token selectors used by
`getSyncWithdrawQuote`.

```typescript theme={null}
import { getSyncWithdrawRate, VaultId } from '@gauntlet-xyz/sdk/evm'

const rate = await getSyncWithdrawRate(client, {
  vaultId: VaultId.AeraUsdAlpha,
})
// rate: { baseMultiplierBps, dynamicPremiumBps, effectiveMultiplierBps }
```

| Parameter     | Type     | Required               | Description                                                                |
| ------------- | -------- | ---------------------- | -------------------------------------------------------------------------- |
| `vaultId`     | `string` | Yes                    | Aera V2 vault identifier                                                   |
| `chainId`     | `number` | No                     | Defaults to the vault's primary chain (Base for current multichain vaults) |
| `assetSymbol` | `string` | For multi-asset vaults | Selects the withdraw token                                                 |

The three returned multipliers are bigint basis-point values. `effectiveMultiplierBps` is the
base multiplier after subtracting the dynamic premium for the current oracle price age.

### getSyncWithdrawQuote

Builds a block-consistent quote for an Aera V2 instant withdrawal without sending a transaction.

```typescript theme={null}
import { getSyncWithdrawQuote, VaultId } from '@gauntlet-xyz/sdk/evm'

const quote = await getSyncWithdrawQuote(client, {
  vaultId: VaultId.AeraUsdAlpha,
  amount: 1_000_000n,
  slippageBps: 100,
})
```

Pass exactly one sizing mode:

| Parameter      | Type      | Required               | Description                                                                                                                         |
| -------------- | --------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `vaultId`      | `string`  | Yes                    | Aera V2 vault identifier                                                                                                            |
| `amount`       | `bigint`  | One sizing mode        | Exact token output; returns `kind: 'withdraw'` and `maxUnitsIn`                                                                     |
| `shares`       | `bigint`  | One sizing mode        | Exact shares burned; returns `kind: 'redeem'` and `minTokensOut`                                                                    |
| `entireAmount` | `true`    | One sizing mode        | Quotes all shares owned by `account`                                                                                                |
| `account`      | `Address` | With `entireAmount`    | Required for a full-position quote; optional for lock data on explicit amount/share quotes                                          |
| `chainId`      | `number`  | No                     | Defaults to the vault's primary chain (Base for current multichain vaults)                                                          |
| `assetSymbol`  | `string`  | For multi-asset vaults | Selects the withdraw token                                                                                                          |
| `slippageBps`  | `number`  | No                     | Integer from 0–10000; defaults to 100 (1%). For `shares` and `entireAmount`, 10000 is rejected because it makes `minTokensOut` zero |

The returned `SyncWithdrawQuote` includes estimated `shares` and `tokensOut`, executable `maxUnitsIn`/`minTokensOut` bounds, the effective rate, epoch capacity, optional `unitsLockedUntil`, and a block-stamped `context`. Redeem quotes also include `shareSafeTokensOut`, the largest exact-token withdrawal that leaves enough slippage headroom within the quoted shares. `capacity.knownLiquidityTokens` is the vault's current token balance when no pull-funds calldata is configured; it is undefined when the provisioner may source more liquidity. Capacity and lock fields are diagnostics; state can change after quoting, so the transaction may still revert on-chain.

### getWithdrawTx

```typescript theme={null}
import { getWithdrawTx, VaultId } from '@gauntlet-xyz/sdk/evm'
// also available from the root: import { getWithdrawTx, VaultId } from '@gauntlet-xyz/sdk'

// Withdraw all shares
const steps = await getWithdrawTx(client, {
  vaultId: VaultId.AeraUsdAlpha,
  entireAmount: true,
  receiver: '0xReceiver', // optional
})

// Withdraw by shares
const steps = await getWithdrawTx(client, {
  vaultId: VaultId.AeraUsdAlpha,
  shares: 500_000000000000000000n,
})

// Withdraw by asset amount
const steps = await getWithdrawTx(client, {
  vaultId: VaultId.AeraUsdAlpha,
  amount: 1_000_000n,
})
```

| Parameter                              | Type                      | Required     | Description                                                                                                                                                                                                                                                                                                      |
| -------------------------------------- | ------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `vaultId`                              | `string`                  | Yes          | Vault identifier                                                                                                                                                                                                                                                                                                 |
| `shares` \| `amount` \| `entireAmount` | —                         | Yes (one of) | Exact shares, exact asset amount, or full position                                                                                                                                                                                                                                                               |
| `chainId`                              | `number`                  | No           | Defaults to the vault's primary chain (Base for current multichain vaults)                                                                                                                                                                                                                                       |
| `assetSymbol`                          | `string`                  | No           | Required for multi-asset vaults to select the withdraw token                                                                                                                                                                                                                                                     |
| `depositMode`                          | `string`                  | No           | Override withdraw mode: `'async'` (queued) or `'sync'` (instant). When omitted, uses the vault's native mode — async for Aera, sync for Morpho. Vaults with `depositMode: 'both'` default to async.                                                                                                              |
| `account`                              | `Address`                 | No           | Only used with `entireAmount`. Defaults to `wallet.account`; when supplied, it must match the configured wallet.                                                                                                                                                                                                 |
| `receiver`                             | `Address`                 | No           | Address that receives the withdrawn assets. Vault shares are always burned from the signer (`wallet.account`); `receiver` only redirects where the assets land. Defaults to `wallet.account`. Aera V1 vaults require the receiver to equal the signer and throw `UnsupportedFeatureError` for any other address. |
| `slippageBps`                          | `number`                  | No           | Slippage tolerance in basis points (e.g. `100` = 1%). Defaults to `100`. Must be an integer between 0 and 10000.                                                                                                                                                                                                 |
| `syncWithdrawQuote`                    | `SyncWithdrawQuoteBounds` | No           | Pins a quote's `minTokensOut` or `maxUnitsIn` and implies sync mode. If `slippageBps` is omitted, the builder uses the quote's value; an explicit value must match. An explicit async request is rejected.                                                                                                       |

For a quoted sync withdrawal, pass the same sizing input. The builder reuses quote slippage when the transaction omits it:

```typescript theme={null}
const quote = await getSyncWithdrawQuote(client, {
  vaultId: VaultId.AeraUsdAlpha,
  shares: 500_000000000000000000n,
  slippageBps: 50,
})

const steps = await getWithdrawTx(client, {
  vaultId: VaultId.AeraUsdAlpha,
  shares: 500_000000000000000000n,
  syncWithdrawQuote: quote,
})
```

The builder validates the quote's vault, chain, token, account, slippage, and original sizing request. For `entireAmount`, it also rereads the wallet's current share balance and rejects stale quote shares. A quote created for another account cannot be used by the configured wallet.

`shares` and `entireAmount` quotes with `slippageBps: 10000` are rejected because they would produce `minTokensOut: 0`.

<Warning>
  A sync deposit on an Aera vault locks all of the depositor's vault units for the vault's deposit refund timeout, currently 1 hour. Until the window ends, any withdrawal (sync or async) or transfer of the units reverts on-chain with `Aera__UnitsLocked`. Async deposits do not trigger the lock.
</Warning>

## User Vault Balance

### Balance lifecycle

An async deposit or withdrawal passes through a **pending state** while the vault solver queues and processes the operation:

* **`pendingDeposit`** — funds are locked in the provisioner contract. They are not yet earning yield. Once the solver settles the request, they move to `balance` and begin earning.
* **`pendingWithdraw`** — vault shares have been redeemed but the underlying assets have not yet been transferred. They are no longer earning yield. Once the solver settles the request, they arrive as ERC-20 tokens in the receiver wallet.

The solver typically processes requests within 2 hours; the maximum window is 12 hours. Funds are safe in both pending states — the delay is operational, not a risk.

How an amount moves through the three states depends on whether the user chose sync or async:

| Path           | Where the balance lands                                                                                                   | Notes                                                               |
| -------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| Async deposit  | `pendingDeposit` for \~2–12 hours (usually \~2), then moves to `balance`                                                  | Not earning during pending; best execution price once settled       |
| Sync deposit   | Directly into `balance`                                                                                                   | Slightly worse price; no waiting                                    |
| Sync withdraw  | Removed from `balance` immediately; claimable as ERC-20 in the receiver wallet                                            | Slightly worse price; no waiting                                    |
| Async withdraw | Moves from `balance` to `pendingWithdraw` for \~2–12 hours (usually \~2), then claimable as ERC-20 in the receiver wallet | No longer earning during pending; best execution price once settled |

On Aera vaults, both withdraw paths revert with `Aera__UnitsLocked` while the user's units are locked: a sync deposit locks all of the user's vault units for 1 hour.

### getUserCurrentBalance

```typescript theme={null}
import { getUserCurrentBalance, VaultId } from '@gauntlet-xyz/sdk'
// VaultId also available from: import { VaultId } from '@gauntlet-xyz/sdk/evm'

const balance = await getUserCurrentBalance(client, {
  vaultId: VaultId.AeraUsdAlpha,
  address: '0xUser',
})
```

| Parameter | Type      | Required | Description                                                                                                                                                     |
| --------- | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `vaultId` | `string`  | Yes      | Must resolve to an Aera multi-depositor vault — throws `UnsupportedProtocolError` otherwise                                                                     |
| `address` | `Address` | Yes      | Account to query                                                                                                                                                |
| `chainId` | `number`  | No       | When omitted, returns one entry per chain the vault is deployed on. When provided, returns only that chain — throws `ChainMismatchError` if not deployed there. |

Returns `Promise<UserCurrentBalance[]>` — one entry per chain the vault is deployed on:

```typescript theme={null}
type UserCurrentBalance = {
  chain: string           // chain identifier, e.g. "base" — included for non-EVM compatibility
  token: Address          // token address
  decimals: number        // token decimals
  pendingDeposit: bigint  // assets locked in provisioner after async deposit — not yet earning yield; 0n if none
  balance: bigint         // assets actively earning in the vault; 0n if no position
  pendingWithdraw: bigint // assets redeemed but not yet claimable after async withdraw — no longer earning; 0n if none
}
```

All numeric fields are always present. If the account has no position on a given chain, all three bigint fields are `0n` — this is not an error.

## Result Shape

Both `getDepositTx` and `getWithdrawTx` return `Promise<PreparedTx[]>`.

```typescript theme={null}
type PreparedTx = {
  payload: {
    type: string    // 'approve' | 'deposit' | 'requestDeposit' | 'redeem' | 'requestRedeem' | 'withdraw'
    to: Address     // contract to call
    data: Hex       // ABI-encoded calldata with attribution suffix already concatenated
    account?: Address
  }
  tx: EvmTxStep    // structured ABI fields + raw attribution bytes — use with writeContract
}

type EvmTxStep = {
  type: 'approve' | 'deposit' | 'requestDeposit' | 'redeem' | 'requestRedeem' | 'withdraw'
  address: Address        // contract to call
  abi: Abi                // ABI fragment for this call
  functionName: string
  args: readonly unknown[]
  account: Address        // sender address
  attribution?: Hex       // raw attribution bytes — must pass as dataSuffix to writeContract
}
```

Steps must be executed in order. An `approve` step, when present, always comes first.

Each step exposes two submission paths with different trade-offs:

### Path 1 — `step.payload` + `sendTransaction`

**Use for:** backend scripts, embedded wallets (Privy, Dynamic), server-side signing, EVM pre-simulation (`eth_call` on the exact bytes to be broadcast).

Attribution is pre-concatenated into `payload.data` — it cannot be lost regardless of wallet or provider.

```typescript theme={null}
for (const step of steps) {
  await walletClient.sendTransaction(step.payload)
}
```

### Path 2 — `step.tx` + `writeContract`

**Use for:** browser wallets via wagmi (MetaMask, Coinbase Wallet, WalletConnect), or when you need wagmi simulation hooks.

Pass `step.tx.attribution` as `dataSuffix` — wagmi appends it to the ABI-encoded calldata before sending. **If `dataSuffix` is omitted or the EIP-1193 provider strips it, the transaction succeeds but volume is not attributed.**

```typescript theme={null}
for (const step of steps) {
  await walletClient.writeContract({
    address: step.tx.address,
    abi: step.tx.abi,
    functionName: step.tx.functionName,
    args: step.tx.args,
    account: step.tx.account,
    dataSuffix: step.tx.attribution, // required — omitting silently drops attribution
  })
}
```

<Card title="Attribution" icon="tag" href="/attribution/attribution-with-sdk">
  How ERC-8021 builder codes work, why `dataSuffix` matters, and how to verify attribution is tracked.
</Card>

## REST API Client — `client.api`

`client.api` is a typed client for every endpoint of the [Gauntlet REST API](/onboarding/credentials). It is available on any configured `GauntletClient` (only `apiKey` is used — no RPC required), or standalone:

```typescript theme={null}
import { GauntletApi } from '@gauntlet-xyz/sdk'

const api = new GauntletApi({ apiKey: process.env.GAUNTLET_API_KEY })
const { data: vaults } = await api.vaults()
```

Response types are generated from the API's OpenAPI spec, so they match the server exactly.

| Method                                          | Endpoint                                           | Returns                                                                                     |
| ----------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `vaults(options?)`                              | `GET /v1/vaults`                                   | All indexed vaults with live metrics (TVL, APY, unit price)                                 |
| `vaultsBySlug(slug)`                            | `GET /v1/vaults/slug/{slug}`                       | All enabled deployments for a logical vault                                                 |
| `primaryVaultTimeseriesBySlug(slug, options?)`  | `GET /v1/vaults/slug/{slug}/primary/timeseries`    | Primary deployment TVL / unit-price / APY history, including resolved deployment provenance |
| `vault(vaultId)`                                | `GET /v1/vaults/{id}`                              | One vault with live metrics                                                                 |
| `vaultDefinition(vaultId)`                      | `GET /v1/vaults/{id}/definition`                   | Raw indexed vault definition                                                                |
| `vaultTimeseries(vaultId, options?)`            | `GET /v1/vaults/{id}/timeseries`                   | TVL / unit-price / APY history                                                              |
| `positions(wallet, options?)`                   | `GET /v1/users/{wallet}/positions`                 | All of a wallet's positions with PnL                                                        |
| `position(wallet, vaultId)`                     | `GET /v1/users/{wallet}/positions/{id}`            | One position with PnL breakdown                                                             |
| `positionTimeseries(wallet, vaultId, options?)` | `GET /v1/users/{wallet}/positions/{id}/timeseries` | Value / cost-basis / PnL / ROI history                                                      |
| `activity(wallet, options?)`                    | `GET /v1/users/{wallet}/activity`                  | One page of the wallet's immutable event log                                                |
| `activityRows(wallet, options?)`                | —                                                  | Async iterator over the full activity log — follows pagination cursors for you              |
| `tvl(options?)`                                 | `GET /v1/tvl`                                      | Aggregate Gauntlet TVL, optionally with per-source breakdown                                |
| `latestPrice(options)`                          | `GET /v1/prices`                                   | Latest (or point-in-time) USD price for a token                                             |
| `priceTimeseries(options)`                      | `GET /v1/prices/timeseries`                        | USD price history for a token                                                               |
| `health()`                                      | `GET /health`                                      | Service liveness (version + uptime)                                                         |
| `chainSyncStatus()`                             | `GET /health/chains`                               | Per-chain indexer sync freshness                                                            |

Timeseries and list methods accept `start` / `end` (ISO 8601), `granularity` (`'hour' | 'day' | 'week' | 'month'`), `limit`, `order`, and an opaque `next` cursor from the previous response's `meta.next_cursor`.

Failed requests throw `GauntletApiError` with `.status`, `.path`, and a machine-readable `.code` when the API provides one. An aggregate response can also succeed while single items fail; those failures arrive in the response's `meta.partial_errors` as `PartialResponseError` entries (`code`, `message`, optional `resource_id`). The type is exported from the SDK.

### Units and vault ids

The API emits amounts as **human-unit decimal strings** (e.g. `"1250.5"`) and identifies vaults by a **CAIP-10-style id** (`"{chainId}:{address}"`, lowercase address) rather than the manifest vault id. The SDK ships exact converters for both — they throw instead of silently rounding:

| Helper                                                       | Description                                                                                                                                            |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `decimalToBigInt(value, decimals)`                           | Decimal string → base-unit `bigint`. Throws `DecimalPrecisionError` if the value has more fractional digits than `decimals`.                           |
| `bigIntToDecimal(value, decimals)`                           | Base-unit `bigint` → decimal string.                                                                                                                   |
| `sharesToBigInt(value)`                                      | Share amount → base-unit `bigint`. Vault shares are always 18 decimals (`SHARE_DECIMALS`).                                                             |
| `apiVaultIdFromVaultId(client, vaultId, chainId?)`           | Manifest vault id (e.g. `VaultId.AeraUsdAlpha`) → API CAIP-10 id. Defaults to the vault's primary chain (Base for current multichain vaults).          |
| `vaultIdFromApiVaultId(client, apiVaultId)`                  | API CAIP-10 id → manifest vault id, or `undefined` when the vault isn't in the bundled manifest (the API indexes more vaults than the manifest lists). |
| `parseApiVaultId(id)` / `formatApiVaultId(chainId, address)` | Low-level CAIP-10 parsing/formatting. Parsing throws `InvalidCaipIdError` on malformed ids.                                                            |

## Activity Flows

Raw activity rows are an immutable event log — an Aera async deposit, for example, is two rows (`deposit_pending`, then `deposit` or `deposit_refunded`) linked by `request_hash`. `getActivityFlows` fetches the log and stitches those lifecycles into one flow per user action, replacing client-side event-log scanning over RPC.

```typescript theme={null}
import { getActivityFlows } from '@gauntlet-xyz/sdk'

const flows = await getActivityFlows(client.api, '0xUser')
const open = flows.filter(f => f.status === 'pending')
```

| Parameter         | Type          | Required | Description                                             |
| ----------------- | ------------- | -------- | ------------------------------------------------------- |
| `api`             | `GauntletApi` | Yes      | Usually `client.api`                                    |
| `walletAddress`   | `string`      | Yes      | Wallet to query                                         |
| `options.vaultId` | `string`      | No       | CAIP-10 vault id — narrows to one vault                 |
| `options.maxRows` | `number`      | No       | Stop paginating after this many rows. Defaults to 1000. |

Returns `Promise<ActivityFlow[]>`, newest first:

```typescript theme={null}
type ActivityFlow = {
  kind: 'deposit' | 'withdraw' | 'transfer_in' | 'transfer_out'
  status: 'settled' | 'pending' | 'refunded'
  vaultId: string          // CAIP-10 id
  requestHash: string | null  // Aera async correlation hash; null for sync flows
  requestedAt: Date | null // when the request row landed
  settledAt: Date | null   // when the settle/refund row landed; null while pending
  assets: AssetAmount      // magnitude of the asset movement (requested amount for refunds)
  shares: bigint           // magnitude of the share movement, 18-decimal base units
  txHashes: string[]       // request first, then settlement
}

type AssetAmount = {
  decimal: string          // human-unit decimal string as the API emits it
  raw: bigint | null       // base-unit integer; null when the token's decimals are unknown
  token: TokenRef | null
}
```

The pure stitcher `stitchActivityFlows(rows)` is also exported if you fetch rows yourself.

### waitForRequestSettlement

Polls the activity log until an Aera async request reaches a terminal state. Use after submitting a `requestDeposit` / `requestRedeem` transaction.

```typescript theme={null}
import { waitForRequestSettlement } from '@gauntlet-xyz/sdk'

const flow = await waitForRequestSettlement(client.api, '0xUser', requestHash)
// flow.status is 'settled' or 'refunded'
```

| Option           | Type     | Description                                                                 |
| ---------------- | -------- | --------------------------------------------------------------------------- |
| `vaultId`        | `string` | CAIP-10 vault id — narrows polling to one vault                             |
| `pollIntervalMs` | `number` | Defaults to 5000                                                            |
| `timeoutMs`      | `number` | Defaults to 600000 (10 minutes). Throws `SettlementTimeoutError` on expiry. |

## Position History

Replays a wallet's complete activity for one vault into a chronological position timeline — running share balance, escrowed pending amounts, and cumulative net asset flows after every event. Complements `client.api.positionTimeseries`, which gives sampled value/PnL history.

```typescript theme={null}
import { getPositionHistory, apiVaultIdFromVaultId } from '@gauntlet-xyz/sdk'

const apiVaultId = await apiVaultIdFromVaultId(client, VaultId.AeraUsdAlpha)
const history = await getPositionHistory(client.api, '0xUser', apiVaultId)
```

Returns `Promise<PositionHistory>`. A wallet that has never touched the vault gets an empty timeline, not an error.

```typescript theme={null}
type PositionHistory = {
  vaultId: string          // CAIP-10 id
  token: TokenRef | null   // the vault's asset token, when known
  points: PositionHistoryPoint[]  // chronological, one per activity row
}

type PositionHistoryPoint = {
  timestamp: Date
  txHash: string
  type: string             // activity row type, e.g. 'deposit', 'withdraw_pending'
  sharesDelta: bigint      // signed share movement of this row, 18-decimal base units
  assetsDelta: string      // signed asset movement, human decimal string
  sharesBalance: bigint    // shares held after this row (escrowed redeem shares excluded)
  pendingDepositAssets: string  // assets escrowed awaiting share mint
  pendingRedeemShares: bigint   // shares escrowed awaiting asset return
  netAssetsIn: string      // cumulative settled deposits minus settled withdrawals
}
```

The pure builder `buildPositionHistory(rows)` is also exported.

## Privy

`@gauntlet-xyz/sdk/privy` wires a Privy embedded or connected wallet into the SDK. Privy wallets are matched structurally (`{ address, getEthereumProvider() }`), so the SDK takes no `@privy-io` dependency.

```typescript theme={null}
import { createGauntletClientFromPrivy } from '@gauntlet-xyz/sdk/privy'
import { useWallets } from '@privy-io/react-auth'
import { base } from 'viem/chains'

const { wallets } = useWallets()

const client = await createGauntletClientFromPrivy({
  wallet: wallets[0],
  chains: [base],
  builderCode: 'your-builder-code',
})
```

| Parameter                                            | Type                         | Required | Description                                                                 |
| ---------------------------------------------------- | ---------------------------- | -------- | --------------------------------------------------------------------------- |
| `wallet`                                             | `PrivyEthereumWallet`        | Yes      | The Privy wallet to sign with (e.g. `useWallets().wallets[0]`)              |
| `chains`                                             | `[Chain, ...Chain[]]`        | Yes      | Chains the client should read from; the first is the wallet's signing chain |
| `transports`                                         | `Record<ChainId, Transport>` | No       | Per-chain transport override; defaults to each chain's public RPC           |
| `apiKey`, `apiUrl`, `attributionMode`, `builderCode` | —                            | No       | Passed through to `GauntletClient`                                          |

To wrap only the wallet (and build the rest of the client yourself), use `walletClientFromPrivy(wallet, chain)`, which returns a viem `WalletClient`.

## Types

### SyncWithdrawQuote and SyncWithdrawQuoteBounds

`SyncWithdrawQuote` is a discriminated union. `kind: 'redeem'` carries the `shares` and `minTokensOut` required by on-chain `redeem`; `kind: 'withdraw'` carries the `tokensOut` and `maxUnitsIn` required by on-chain `withdraw`.

```typescript theme={null}
type SyncWithdrawQuoteBounds =
  | {
      kind: 'redeem'
      shares: bigint
      minTokensOut: bigint
      context: SyncWithdrawQuoteContext
    }
  | {
      kind: 'withdraw'
      tokensOut: bigint
      maxUnitsIn: bigint
      context: SyncWithdrawQuoteContext
    }

type SyncWithdrawQuote = SyncWithdrawQuoteBounds & {
  shares: bigint
  maxUnitsIn: bigint
  tokensOut: bigint
  minTokensOut: bigint
  shareSafeTokensOut?: bigint
  rate: SyncRedeemRate
  capacity: SyncWithdrawCapacity
  unitsLockedUntil?: bigint
}

type SyncWithdrawCapacity = {
  epochCapNumeraire: bigint
  epochRedeemedNumeraire: bigint
  remainingNumeraire: bigint
  remainingTokens: bigint
  knownLiquidityTokens?: bigint
  requestNumeraire: bigint
  exceedsCapacity: boolean
}
```

The exported `SyncWithdrawQuote` is assignable to `SyncWithdrawQuoteBounds`, so the full quote can be passed directly to `getWithdrawTx`.

### VaultInfo

```typescript theme={null}
type VaultInfo = {
  vaultId: string
  name: string
  protocol: 'aera' | 'morpho'
  strategy: string
  deployments: VaultDeployment[]
}
```

### VaultDeployment

The object `vaultId` resolves to. Carries all metadata the SDK needs to construct deposit and withdraw transactions — you never supply these directly.

```typescript theme={null}
type VaultDeployment = {
  chain: 'evm'
  chainId: number
  vaultAddress: Address           // ERC4626 vault contract
  provisionerAddress?: Address    // multi-depositor vaults: deposit routes through this instead
  vaultType: 'single-depositor' | 'multi-depositor'
  depositMode: 'sync' | 'async' | 'both'  // validates the depositMode param in getDepositTx / getWithdrawTx
  supplyToken: TokenInfo[]        // tokens accepted by this vault; provides address and decimals
}
```

### TokenInfo

```typescript theme={null}
type TokenInfo = {
  symbol: string
  address: Address
  decimals: number
}
```

### VaultFilter

```typescript theme={null}
type VaultFilter = {
  chainId?: number
  protocol?: string
}
```

### AttributionMode

```typescript theme={null}
enum AttributionMode {
  PUBLIC  = 'public',
  ENCODED = 'encoded',  // not yet implemented
  PRIVATE = 'private',  // not yet implemented
}
```

## Errors

All errors extend `GauntletSDKError`, which extends `Error`.

| Error                           | Description                                                                                                                                                                    |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `VaultNotFoundError`            | Vault ID doesn't exist or isn't deployed on the requested chain. Has `.vaultId` and optional `.chainId` properties.                                                            |
| `UnsupportedAssetError`         | Token not accepted by this vault. Has `.asset` and `.vaultId` properties.                                                                                                      |
| `ChainMismatchError`            | Chain parameter doesn't match vault deployment. Has `.expected` and `.received` properties.                                                                                    |
| `UnsupportedDepositModeError`   | Requested sync/async mode not supported by this vault. Has `.vaultId`, `.requested`, and `.available` properties.                                                              |
| `RpcNotConfiguredError`         | No `evmClients` entry provided for the required chain ID. Has `.chainId` property.                                                                                             |
| `AccountRequiredError`          | No wallet is configured for transaction building, or an `entireAmount` quote omits `account`.                                                                                  |
| `UnsupportedProtocolError`      | Vault protocol is not supported by this method (e.g. `getUserCurrentBalance` only supports Aera multi-depositor). Has `.protocol` property.                                    |
| `InvalidWithdrawParamsError`    | The request does not provide exactly one sizing mode, or supplied quote bounds do not match the transaction request, including an account-scoped quote used by another wallet. |
| `AccountMismatchError`          | The `account` passed to `getWithdrawTx({ entireAmount: true })` does not match the configured wallet. Has `.expected` and `.received` properties.                              |
| `InvalidSyncWithdrawBoundError` | A sync quote or transaction would submit a zero bound. Has `.bound`.                                                                                                           |
| `InvalidSyncDepositBoundError`  | `minUnitsOut` is zero, negative, or supplied without explicit Aera sync mode.                                                                                                  |
| `StalePriceError`               | The oracle price is too old for sync redeem. Has `.blockTimestamp`, `.maxPriceAge`, and `.priceTimestamp`.                                                                     |
| `InvalidSlippageBPSError`       | `slippageBps` is not an integer in the range 0–10000. Has `.slippage` property.                                                                                                |
| `UnimplementedFeatureError`     | Feature exists in the API but is not yet implemented (e.g. `AttributionMode.ENCODED`). Has `.feature` property.                                                                |
| `UnsupportedFeatureError`       | The selected runtime cannot execute the feature, such as sync redeem without both V2 provisioner and fee calculator. Has `.feature` property.                                  |
| `UnitConversionError`           | Failed to convert token units for a vault — fee calculator unavailable on-chain. Has `.vaultAddress` property.                                                                 |
| `GauntletApiError`              | A `client.api` request failed. Has `.status`, `.path`, and optional `.code` (machine-readable API error code) properties.                                                      |
| `InvalidDecimalError`           | Value passed to a decimal converter is not a valid decimal string. Has `.value` property.                                                                                      |
| `DecimalPrecisionError`         | Converting a decimal string to base units would lose precision — the value has more fractional digits than the token's decimals. Has `.value` and `.decimals` properties.      |
| `InvalidCaipIdError`            | Malformed CAIP-10 vault id (expected `"{chainId}:{address}"`). Has `.id` property.                                                                                             |
| `SettlementTimeoutError`        | `waitForRequestSettlement` deadline expired before the request settled. Has `.requestHash` and `.timeoutMs` properties.                                                        |

## Go Deeper

<CardGroup cols={2}>
  <Card title="Examples" icon="play" href="/sdk/examples">
    End-to-end code for deposits, withdrawals, and error handling.
  </Card>

  <Card title="API Reference" icon="database" href="/onboarding/credentials">
    Use the raw API directly if you need more control than the SDK provides.
  </Card>
</CardGroup>
