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

# AI Coding Reference

> Single-page reference for @paxoslabs/amplify-sdk 1.0.0 — optimized for LLM context windows

Single-page, greppable reference for `@paxoslabs/amplify-sdk` 1.0.0. Drop the rendered page into an AI coding assistant (Cursor, Copilot, Claude Code) and it has everything required to integrate without extra docs lookups.

The public surface is `AmplifyClient`, the `Amplify` type-only namespace, `AmplifyError`, and `AmplifyTimeoutError`.

## Install

```bash theme={null}
pnpm add @paxoslabs/amplify-sdk
# or: npm i @paxoslabs/amplify-sdk
# or: yarn add @paxoslabs/amplify-sdk
```

No required peer dependencies. To submit the calldata the SDK returns, bring your own EVM client — examples below use `viem`.

```bash theme={null}
pnpm add viem
```

## Construct the client

```ts theme={null}
import { AmplifyClient } from '@paxoslabs/amplify-sdk'

const client = new AmplifyClient({
  apiKey: process.env.PAXOS_LABS_API_KEY!,
})
```

Full constructor signature:

```ts theme={null}
namespace AmplifyClient {
  interface Options {
    /** Required. Sent as the `x-api-key` header on every request. */
    apiKey: string | (() => string) | (() => Promise<string>)
    /** Optional environment override. Defaults to `AmplifyEnvironment.Production`. */
    environment?: AmplifyEnvironment | string | (() => string) | (() => Promise<string>)
    /** Optional base URL override (takes precedence over `environment`). */
    baseUrl?: string | (() => string) | (() => Promise<string>)
  }

  interface RequestOptions {
    timeoutInSeconds?: number // default 60
    maxRetries?: number // default 2
    abortSignal?: AbortSignal
    headers?: Record<string, string>
  }
}
```

Construct once at module scope and reuse across requests. The SDK defaults to production (`AmplifyEnvironment.Production`).

## Authentication

The API key is sent on every request as the `x-api-key` HTTP header. The SDK reads it from the `apiKey` constructor option. There is no other auth mode.

```ts theme={null}
const client = new AmplifyClient({
  apiKey: process.env.PAXOS_LABS_API_KEY!,
})
```

Missing/invalid keys surface as `AmplifyError` with `statusCode: 401`. Access denied for a specific vault surfaces as `statusCode: 403`.

## Imports cheat sheet

```ts theme={null}
// Runtime
import {
  AmplifyClient,
  AmplifyError,
  AmplifyTimeoutError,
} from '@paxoslabs/amplify-sdk'

// Types (request/response DTOs live under a single namespace)
import type { Amplify } from '@paxoslabs/amplify-sdk'

type DepositRequest = Amplify.amplify.deposit.PrepareDepositRequest
type DepositResponse = Amplify.PrepareDepositResponseDto
type Vault = Amplify.VaultDto
type Deployment = Amplify.VaultDeploymentDto
type AuthorizationResponse = Amplify.AuthorizationResponseDto
```

## Subclient + method index

`AmplifyClient` exposes one subclient per resource. Every subclient method:

```ts theme={null}
client.amplify.deposit.prepare(request)
client.amplify.withdraw.prepare(request)
client.amplify.withdraw.cancel(request)
client.amplify.withdraw.calculateFee(request)
client.amplify.withdraw.listRequests(request?)
client.amplify.withdraw.getVolumes(request)
client.core.authorization.detect(request)
client.amplify.users.getPositions(request)
client.amplify.vaults.list(request?)
client.amplify.vaults.listAssets(request?)
client.amplify.vaults.getApys(request?)
client.amplify.vaults.getTvls(request?)
client.amplify.vaults.getSupplyCaps(request?)
client.amplify.vaults.listCompositions(request?)
client.amplify.vaults.getLiquidityShortfalls(request?)
client.amplify.smartDepositAddresses.get(request)
```

Every method takes an optional second argument: `requestOptions` (`{ timeoutInSeconds?, maxRetries?, abortSignal?, headers? }`).

### `client.amplify.deposit.prepare(request)`

Returns ABI-encoded calldata for a vault deposit on the `DistributorCodeDepositor`.

* Request: `Amplify.amplify.deposit.PrepareDepositRequest`
* Response: `Amplify.PrepareDepositResponseDto`

```ts theme={null}
const { transaction } = await client.amplify.deposit.prepare({
  vaultAddress: '0xbbbb000000000000000000000000000000000001',
  depositAsset: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
  depositAmount: '1000000', // base units, decimal string
  userAddress: '0xUser',
  chainId: 1,
  // Optional: pass after a successful permit signing flow
  // permitSignature: '0x...',
  // permitDeadline: 9999999999,
  // Optional: defaults to userAddress
  // to: '0xRecipient',
})
// transaction.to, transaction.data, transaction.value
```

### `client.amplify.withdraw.prepare(request)`

Returns calldata for `WithdrawQueue.submitOrder`. Caller must have pre-approved `deployment.withdrawQueueAddress` to spend share tokens.

* Request: `Amplify.amplify.withdraw.PrepareWithdrawRequest`
* Response: `Amplify.PrepareWithdrawResponseDto`

```ts theme={null}
const { transaction } = await client.amplify.withdraw.prepare({
  vaultAddress: '0xbbbb000000000000000000000000000000000001',
  wantAsset: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  shareAmount: '1000000000000000000',
  userAddress: '0xUser',
  chainId: 1,
  // Optional, all default to userAddress
  // intendedDepositor: '0x...',
  // receiver: '0x...',
  // refundReceiver: '0x...',
})
```

### `client.amplify.withdraw.cancel(request)`

Returns calldata for cancelling a pending withdrawal order. Look up `orderIndex` via `listRequests`.

* Request: `Amplify.amplify.withdraw.CancelWithdrawRequest`
* Response: `Amplify.PrepareCancelWithdrawResponseDto`

```ts theme={null}
const { transaction } = await client.amplify.withdraw.cancel({
  vaultAddress: '0xbbbb000000000000000000000000000000000001',
  orderIndex: '42',
  chainId: 1,
})
```

### `client.amplify.withdraw.calculateFee(request)`

Computes the fee charged for a hypothetical withdrawal.

* Request: `Amplify.CalculateFeeRequest`
* Response: `Amplify.CalculateWithdrawalFeeResponseDto`

```ts theme={null}
const fee = await client.amplify.withdraw.calculateFee({
  offerAmount: '1000000000000000000',
  wantAsset: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  vaultAddress: '0xbbbb000000000000000000000000000000000001',
  chainId: 1,
})
// fee.feeAmount, fee.offerFeePercentage, fee.flatFee
```

### `client.amplify.withdraw.listRequests(request?)`

Lists withdrawal requests with cursor pagination and AIP-160 filters.

* Request: `Amplify.amplify.withdraw.ListRequestsWithdrawRequest`
* Response: `Amplify.WithdrawalRequestsResponseDto`

```ts theme={null}
const { withdrawalRequests, nextPageToken } = await client.amplify.withdraw.listRequests({
  filter: 'status=PENDING AND userAddress=0xUser AND vaultAddress=0xbbbb...0001',
  pageSize: 50,
})
// Each item: { id, status, orderIndex, orderAmount, vaultAddress, chainId, ... }
```

### `client.core.authorization.detect(request)`

Tells you whether the token needs EIP-2612 permit signing, a standard ERC-20 `approve`, or is already approved for the requested amount.

* Request: `Amplify.core.authorization.DetectAuthorizationRequest`
* Response: `Amplify.AuthorizationResponseDto`

```ts theme={null}
const auth = await client.core.authorization.detect({
  vaultAddress: '0xbbbb000000000000000000000000000000000001',
  tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // depositAsset
  amount: '1000000',
  userAddress: '0xUser',
  chainId: 1,
})
// auth.method: 'permit' | 'approval' | 'already_approved'
// auth.permitData?         present when method === 'permit'
// auth.approvalTransaction? present when method === 'approval'
```

For deposits pass `tokenAddress = depositAsset`. For withdrawals pass `tokenAddress = vaultAddress` (the share token). Share tokens always resolve to `approval` or `already_approved` — there is no permit path for shares.

### `client.amplify.users.getPositions(request)`

Returns one row per `(user, vault, chain)` with share balance and base-asset value.

* Request: `Amplify.GetPositionsRequest`
* Response: `Amplify.UserPositionsResponseDto`

```ts theme={null}
const { userPositions, nextPageToken } = await client.amplify.users.getPositions({
  userAddress: '0xUser',
  filter: 'chainId=1',
})
```

### `client.amplify.vaults.list(request?)`

Returns all vaults the API key can see, grouped by vault name, with one `deployments[]` entry per chain.

* Request: `Amplify.amplify.vaults.ListVaultsRequest`
* Response: `Amplify.VaultsResponseDto`

```ts theme={null}
const { vaults, nextPageToken } = await client.amplify.vaults.list({
  filter: 'chainId=1 AND inDeprecation=false',
})

for (const v of vaults) {
  for (const d of v.deployments) {
    // d.chainId, d.boringVaultAddress, d.depositorAddress, d.withdrawQueueAddress,
    // d.requiresKyt, d.inDeprecation, d.depositSupplyCap, d.minimumWithdrawalOrderSize,
    // d.assets[] -> { assetAddress, depositable, withdrawable, depositFees, withdrawFees }
  }
}
```

### `client.amplify.vaults.listAssets(request?)`

Per-asset capability listing across all visible vaults.

* Request: `Amplify.ListAssetsRequest`
* Response: `Amplify.VaultAssetsResponseDto`

```ts theme={null}
const { vaultAssets } = await client.amplify.vaults.listAssets({
  filter: 'depositable=true AND chainId=1',
})
```

### `client.amplify.vaults.getApys(request?)`

Historical + current APY series.

* Request: `Amplify.GetApysRequest`
* Response: `Amplify.VaultApysResponseDto`

```ts theme={null}
const { vaultApys } = await client.amplify.vaults.getApys({
  filter: 'vaultAddress=0xbbbb...0001',
  lookback: '604800s', // 7 days
  interval: '86400s', // 1 day
})
```

### `client.amplify.vaults.getTvls(request?)`

Historical + current TVL series, optionally aggregated across chains.

* Request: `Amplify.GetTvlsRequest`
* Response: `Amplify.VaultTvlsResponseDto`

```ts theme={null}
const { vaultTvls } = await client.amplify.vaults.getTvls({
  filter: 'chainId=1 AND vaultAddress=0xbbbb...0001',
  lookback: '604800s',
  aggregate: false,
})
```

### `client.amplify.vaults.getSupplyCaps(request?)`

Current `totalSupplyInBase`, `supplyCap`, and `percentageFilled` per `(vault, chain)`.

* Request: `Amplify.GetSupplyCapsRequest`
* Response: `Amplify.SupplyCapsResponseDto`

```ts theme={null}
const { supplyCaps } = await client.amplify.vaults.getSupplyCaps({
  filter: 'vaultAddress=0xbbbb...0001 AND chainId=1',
})
```

### `client.amplify.smartDeposits.getAddress(request)`

Returns a deterministic Smart Deposit Address for routing on-ramp funds directly into a vault.

* Request: `Amplify.amplify.SmartDepositAddressRequestDto`
* Response: `Amplify.SmartDepositAddressResponseDto`

```ts theme={null}
const { smartDepositAddress } = await client.amplify.smartDeposits.getAddress({
  vaultAddress: '0xbbbb000000000000000000000000000000000001',
  userDestinationAddress: '0xUser',
  inputToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
  chainId: 1,
  // Optional: generate unique addresses per customer
  // customerId: 'user_abc123',
})
// smartDepositAddress: '0x...' — deterministic address to receive deposits
```

When `customerId` is provided, each unique value produces a distinct deposit address even when other parameters are identical. The `customerId` is included in webhook payloads for reconciliation. Must contain only alphanumeric characters, hyphens, and underscores (max 256 characters).

## Error handling

Every SDK method throws on failure. Two error classes only:

```ts theme={null}
class AmplifyError extends Error {
  readonly statusCode?: number // HTTP status, e.g. 400, 401, 403, 404, 422
  readonly body?: unknown // parsed JSON body when the server returned JSON
  readonly rawResponse?: RawResponse // fetch Response metadata
  readonly message: string // human-readable summary
}

class AmplifyTimeoutError extends Error {
  readonly message: string
}
```

Standard handling pattern:

```ts theme={null}
import { AmplifyError, AmplifyTimeoutError } from '@paxoslabs/amplify-sdk'

try {
  await client.amplify.deposit.prepare(request)
} catch (err) {
  if (err instanceof AmplifyTimeoutError) {
    // request exceeded timeoutInSeconds — retry with backoff
  } else if (err instanceof AmplifyError) {
    switch (err.statusCode) {
      case 400:
        // bad request — fix inputs (see body for details)
        break
      case 401:
      case 403:
        // auth failure — check apiKey
        break
      case 404:
        // not found — vault, asset, or order doesn't exist for these params
        break
      case 422:
        // unprocessable — common on authorization.detect for non-2612 tokens
        break
      default:
        // 5xx or unknown — surface to user, log details
    }
  } else {
    throw err
  }
}
```

Retry helper that only retries timeouts:

```ts theme={null}
async function retryOnTimeout<T>(fn: () => Promise<T>, attempts = 3): Promise<T> {
  let lastErr: unknown
  for (let i = 0; i < attempts; i++) {
    try {
      return await fn()
    } catch (err) {
      lastErr = err
      if (!(err instanceof AmplifyTimeoutError)) throw err
      await new Promise((r) => setTimeout(r, 500 * 2 ** i))
    }
  }
  throw lastErr
}
```

## End-to-end: deposit flow (permit + approval + already-approved)

```ts theme={null}
import { AmplifyClient } from '@paxoslabs/amplify-sdk'
import type { Address, Hex } from 'viem'
import {
  createPublicClient,
  createWalletClient,
  custom,
  http,
  parseUnits,
} from 'viem'
import { mainnet } from 'viem/chains'

const client = new AmplifyClient({
  apiKey: process.env.PAXOS_LABS_API_KEY!,
})

const publicClient = createPublicClient({ chain: mainnet, transport: http() })
const walletClient = createWalletClient({
  chain: mainnet,
  transport: custom((window as any).ethereum),
})

async function deposit(params: {
  vaultAddress: Address
  depositAsset: Address
  humanAmount: string // e.g. "100"
  decimals: number // depositAsset.decimals()
  userAddress: Address
  chainId: number
}) {
  const depositAmount = parseUnits(params.humanAmount, params.decimals).toString()

  // 1) Ask the backend what kind of authorization is required.
  const auth = await client.core.authorization.detect({
    vaultAddress: params.vaultAddress,
    tokenAddress: params.depositAsset,
    amount: depositAmount,
    userAddress: params.userAddress,
    chainId: params.chainId,
  })

  let permitSignature: Hex | undefined
  let permitDeadline: number | undefined

  if (auth.method === 'permit' && auth.permitData) {
    // 2a) Token supports EIP-2612 — sign typed data.
    const { domain, types, value, deadline } = auth.permitData
    const signature = await walletClient.signTypedData({
      account: params.userAddress,
      domain,
      types: types as any,
      primaryType: 'Permit',
      message: value as any,
    })
    permitSignature = signature
    permitDeadline = Number(deadline)
  } else if (auth.method === 'approval' && auth.approvalTransaction) {
    // 2b) Token doesn't permit (or allowance < amount) — send approve().
    const hash = await walletClient.sendTransaction({
      account: params.userAddress,
      to: params.depositAsset,
      data: auth.approvalTransaction.encoded as Hex,
      chain: mainnet,
    })
    await publicClient.waitForTransactionReceipt({ hash })
  }
  // else: auth.method === 'already_approved' — nothing to do here.

  // 3) Get the deposit calldata and submit it.
  const { transaction } = await client.amplify.deposit.prepare({
    vaultAddress: params.vaultAddress,
    depositAsset: params.depositAsset,
    depositAmount,
    userAddress: params.userAddress,
    chainId: params.chainId,
    permitSignature,
    permitDeadline,
  })

  const hash = await walletClient.sendTransaction({
    account: params.userAddress,
    to: transaction.to as Address,
    data: transaction.data as Hex,
    value: BigInt(transaction.value),
    chain: mainnet,
  })
  return publicClient.waitForTransactionReceipt({ hash })
}
```

## End-to-end: withdrawal flow

Share-token approvals always go to the `WithdrawQueue` — never to the vault contract itself, never via permit.

```ts theme={null}
import type { Address, Hex } from 'viem'

async function withdraw(params: {
  vaultAddress: Address
  withdrawQueueAddress: Address // from client.amplify.vaults.list()
  wantAsset: Address
  shareAmount: string // share token base units
  userAddress: Address
  chainId: number
}) {
  // 1) Authorize the WithdrawQueue to spend shares.
  //    `authorization.detect` returns 'approval' or 'already_approved' for share tokens.
  const auth = await client.core.authorization.detect({
    vaultAddress: params.vaultAddress,
    tokenAddress: params.vaultAddress, // share token == vault address
    amount: params.shareAmount,
    userAddress: params.userAddress,
    chainId: params.chainId,
  })

  if (auth.method === 'approval' && auth.approvalTransaction) {
    const hash = await walletClient.sendTransaction({
      account: params.userAddress,
      to: params.vaultAddress, // share token
      data: auth.approvalTransaction.encoded as Hex,
      chain: mainnet,
    })
    await publicClient.waitForTransactionReceipt({ hash })
  }
  // else: already_approved — proceed directly

  // 2) Get the submitOrder calldata.
  const { transaction } = await client.amplify.withdraw.prepare({
    vaultAddress: params.vaultAddress,
    wantAsset: params.wantAsset,
    shareAmount: params.shareAmount,
    userAddress: params.userAddress,
    chainId: params.chainId,
  })

  // 3) Submit.
  const hash = await walletClient.sendTransaction({
    account: params.userAddress,
    to: transaction.to as Address,
    data: transaction.data as Hex,
    value: BigInt(transaction.value),
    chain: mainnet,
  })
  return publicClient.waitForTransactionReceipt({ hash })
}
```

## End-to-end: cancel a pending withdrawal

```ts theme={null}
import type { Address, Hex } from 'viem'

async function cancelPendingWithdrawals(params: {
  userAddress: Address
  vaultAddress: Address
  chainId: number
}) {
  // 1) Find pending orders for this user.
  const { withdrawalRequests } = await client.amplify.withdraw.listRequests({
    filter: `status=PENDING AND userAddress=${params.userAddress} AND vaultAddress=${params.vaultAddress} AND chainId=${params.chainId}`,
  })

  // 2) Cancel each one.
  for (const req of withdrawalRequests) {
    const { transaction } = await client.amplify.withdraw.cancel({
      vaultAddress: params.vaultAddress,
      orderIndex: req.orderIndex,
      chainId: params.chainId,
    })

    const hash = await walletClient.sendTransaction({
      account: params.userAddress,
      to: transaction.to as Address,
      data: transaction.data as Hex,
      value: BigInt(transaction.value),
      chain: mainnet,
    })
    await publicClient.waitForTransactionReceipt({ hash })
  }
}
```

## Common pitfalls

* **`depositAmount` / `shareAmount` are base units.** Always decimal strings. `parseUnits('1.5', 6).toString()` for USDC; never `'1.5'`.
* **Share approvals go to the `WithdrawQueue`.** Approving the vault address itself causes the withdraw `submitOrder` to revert with panic `0x11`. Read `deployment.withdrawQueueAddress`.
* **Approve `shareAmount + feeAmount`, not `shareAmount`.** `submitOrder` pulls the inclusive amount. An exact-share approve reverts with panic `0x11`. Always call `client.amplify.withdraw.calculateFee` first, then approve `BigInt(shareAmount) + BigInt(fee.feeAmount)`.
* **Preflight withdraw fees.** `submitOrder` also panics `0x11` when `feeAmount >= shareAmount` (post-fee math underflows). Bail out before `withdraw.prepare` when the fee would consume the offer.
* **Permit is for deposit assets only.** Share tokens never return `method: 'permit'` from `authorization.detect`. Don't try to bypass and sign typed data manually.
* **Decimal invariant on Amplify vaults.** Deposit, base, and share token decimals must match — otherwise on-chain deposit reverts. Verify with viem's `readContract` against each token before depositing.
* **Use `client.amplify.vaults.list()` as the source of truth for addresses.** Hardcoding contract addresses leaks across chain redeploys. Look up `boringVaultAddress`, `depositorAddress`, and `withdrawQueueAddress` per `(vault, chainId)` at runtime.
* **`AmplifyTimeoutError` is its own class.** Retry timeouts; do not retry `AmplifyError` (400/401/403/404/422) without first fixing inputs.
* **Branch errors on `err.statusCode` and `err.body`.** `AmplifyError` is the single non-timeout error class — there are no per-failure subclasses.
* **`responseFormat: 'encoded'` is the default.** You always get `transaction.to`, `transaction.data`, `transaction.value`. Pass `responseFormat: 'full'` if you want the ABI fragment, function name, and args back too.
* **`moduleResolution` must be `bundler` / `node16` / `nodenext`.** Legacy `node` resolution can't see the package's `exports` map and types collapse to `any`.
