Skip to main content

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.

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

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.
pnpm add viem

Construct the client

import { AmplifyClient } from '@paxoslabs/amplify-sdk'

const client = new AmplifyClient({
  apiKey: process.env.PAXOS_LABS_API_KEY!,
})
Full constructor signature:
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.
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

// 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.PrepareDepositRequest
type DepositResponse = Amplify.PrepareDepositResponseDto
type Vault = Amplify.VaultDto
type Deployment = Amplify.VaultDeploymentDto
type PermitResponse = Amplify.PermitResponseDto

Subclient + method index

AmplifyClient exposes one subclient per resource. Every subclient method:
client.deposit.prepareDeposit(request)
client.withdraw.prepareWithdrawal(request)
client.withdraw.cancel(request)
client.withdraw.calculateFee(request)
client.withdraw.listRequests(request?)
client.withdraw.getVolumes(request)
client.permit.authorize(request)
client.users.getPositions(request)
client.vaults.list(request?)
client.vaults.listAssets(request?)
client.vaults.getApys(request?)
client.vaults.getTvls(request?)
client.vaults.getSupplyCaps(request?)
client.vaults.listCompositions(request?)
client.vaults.getLiquidityShortfalls(request?)
Every method takes an optional second argument: requestOptions ({ timeoutInSeconds?, maxRetries?, abortSignal?, headers? }).

client.deposit.prepareDeposit(request)

Returns ABI-encoded calldata for a vault deposit on the DistributorCodeDepositor.
  • Request: Amplify.PrepareDepositRequest
  • Response: Amplify.PrepareDepositResponseDto
const { transaction } = await client.deposit.prepareDeposit({
  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.withdraw.prepareWithdrawal(request)

Returns calldata for WithdrawQueue.submitOrder. Caller must have pre-approved deployment.withdrawQueueAddress to spend share tokens.
  • Request: Amplify.PrepareWithdrawalRequest
  • Response: Amplify.PrepareWithdrawResponseDto
const { transaction } = await client.withdraw.prepareWithdrawal({
  vaultAddress: '0xbbbb000000000000000000000000000000000001',
  wantAsset: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  shareAmount: '1000000000000000000',
  userAddress: '0xUser',
  chainId: 1,
  // Optional, all default to userAddress
  // intendedDepositor: '0x...',
  // receiver: '0x...',
  // refundReceiver: '0x...',
})

client.withdraw.cancel(request)

Returns calldata for cancelling a pending withdrawal order. Look up orderIndex via listRequests.
  • Request: Amplify.CancelRequest
  • Response: Amplify.PrepareCancelWithdrawResponseDto
const { transaction } = await client.withdraw.cancel({
  vaultAddress: '0xbbbb000000000000000000000000000000000001',
  orderIndex: '42',
  chainId: 1,
})

client.withdraw.calculateFee(request)

Computes the fee charged for a hypothetical withdrawal.
  • Request: Amplify.CalculateFeeRequest
  • Response: Amplify.CalculateWithdrawalFeeResponseDto
const fee = await client.withdraw.calculateFee({
  offerAmount: '1000000000000000000',
  wantAsset: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  vaultAddress: '0xbbbb000000000000000000000000000000000001',
  chainId: 1,
})
// fee.feeAmount, fee.offerFeePercentage, fee.flatFee

client.withdraw.listRequests(request?)

Lists withdrawal requests with cursor pagination and AIP-160 filters.
  • Request: Amplify.ListRequestsRequest
  • Response: Amplify.WithdrawalRequestsResponseDto
const { withdrawalRequests, nextPageToken } = await client.withdraw.listRequests({
  filter: 'status=PENDING AND userAddress=0xUser AND vaultAddress=0xbbbb...0001',
  pageSize: 50,
})
// Each item: { id, status, orderIndex, orderAmount, vaultAddress, chainId, ... }

client.permit.authorize(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.AuthorizeRequest
  • Response: Amplify.PermitResponseDto
const auth = await client.permit.authorize({
  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.users.getPositions(request)

Returns one row per (user, vault, chain) with share balance and base-asset value.
  • Request: Amplify.GetPositionsRequest
  • Response: Amplify.UserPositionsResponseDto
const { userPositions, nextPageToken } = await client.users.getPositions({
  userAddress: '0xUser',
  filter: 'chainId=1',
})

client.vaults.list(request?)

Returns all vaults the API key can see, grouped by vault name, with one deployments[] entry per chain.
  • Request: Amplify.ListRequest
  • Response: Amplify.VaultsResponseDto
const { vaults, nextPageToken } = await client.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.vaults.listAssets(request?)

Per-asset capability listing across all visible vaults.
  • Request: Amplify.ListAssetsRequest
  • Response: Amplify.VaultAssetsResponseDto
const { vaultAssets } = await client.vaults.listAssets({
  filter: 'depositable=true AND chainId=1',
})

client.vaults.getApys(request?)

Historical + current APY series.
  • Request: Amplify.GetApysRequest
  • Response: Amplify.VaultApysResponseDto
const { vaultApys } = await client.vaults.getApys({
  filter: 'vaultAddress=0xbbbb...0001',
  lookback: '604800s', // 7 days
  interval: '86400s', // 1 day
})

client.vaults.getTvls(request?)

Historical + current TVL series, optionally aggregated across chains.
  • Request: Amplify.GetTvlsRequest
  • Response: Amplify.VaultTvlsResponseDto
const { vaultTvls } = await client.vaults.getTvls({
  filter: 'chainId=1 AND vaultAddress=0xbbbb...0001',
  lookback: '604800s',
  aggregate: false,
})

client.vaults.getSupplyCaps(request?)

Current totalSupplyInBase, supplyCap, and percentageFilled per (vault, chain).
  • Request: Amplify.GetSupplyCapsRequest
  • Response: Amplify.SupplyCapsResponseDto
const { supplyCaps } = await client.vaults.getSupplyCaps({
  filter: 'vaultAddress=0xbbbb...0001 AND chainId=1',
})

Error handling

Every SDK method throws on failure. Two error classes only:
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:
import { AmplifyError, AmplifyTimeoutError } from '@paxoslabs/amplify-sdk'

try {
  await client.deposit.prepareDeposit(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 permit.authorize for non-2612 tokens
        break
      default:
        // 5xx or unknown — surface to user, log details
    }
  } else {
    throw err
  }
}
Retry helper that only retries timeouts:
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)

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.permit.authorize({
    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.deposit.prepareDeposit({
    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.
import type { Address, Hex } from 'viem'

async function withdraw(params: {
  vaultAddress: Address
  withdrawQueueAddress: Address // from client.vaults.list()
  wantAsset: Address
  shareAmount: string // share token base units
  userAddress: Address
  chainId: number
}) {
  // 1) Authorize the WithdrawQueue to spend shares.
  //    `permit.authorize` returns 'approval' or 'already_approved' for share tokens.
  const auth = await client.permit.authorize({
    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.withdraw.prepareWithdrawal({
    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

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.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.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.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 prepareWithdrawal when the fee would consume the offer.
  • Permit is for deposit assets only. Share tokens never return method: 'permit' from permit.authorize. 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.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.