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.

A deposit moves an ERC-20 asset from the user’s wallet into a BoringVault and mints share tokens back. The flow is the same regardless of vault:
  1. Call client.permit.authorize(...) to discover whether the deposit asset needs a permit signature, a separate approve transaction, or already has sufficient allowance.
  2. Handle the three response shapes (permit / approval / already_approved).
  3. Call client.deposit.prepareDeposit(...) to get ABI-encoded calldata.
  4. Submit the returned transaction with the user’s wallet.
Throughout this guide, client refers to a singleton AmplifyClient created on the server — see Project setup for the wiring. All token amounts are base-units decimal strings; the SDK does no decimal parsing.
See the AI Coding Reference for the full parameter list on every method shown here.

Step 1: Decide on permit vs. approval

import type { Amplify } from '@paxoslabs/amplify-sdk'

const auth = await client.permit.authorize({
  vaultAddress: '0xbbbb000000000000000000000000000000000001',
  tokenAddress: depositAsset, // the ERC-20 you're depositing (e.g. USDC)
  amount: depositAmount,      // base-units decimal string
  userAddress,
  chainId: 1,
})
The response is a discriminated union on method:
type PermitResponseDto =
  | { method: 'permit'; permitData: Amplify.PermitTypedDataResponseDto }
  | { method: 'approval'; approvalTransaction: Amplify.ApprovalTransactionDto }
  | { method: 'already_approved' }
methodWhat to do
permitSign auth.permitData off-chain (gasless) and pass permitSignature + permitDeadline to prepareDeposit.
approvalSubmit auth.approvalTransaction first, wait for it to confirm, then call prepareDeposit.
already_approvedSufficient allowance already exists. Go straight to prepareDeposit.
1

Branch on auth.method

import type { Address, Hex } from 'viem'

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

if (auth.method === 'permit') {
  // see Step 2a
} else if (auth.method === 'approval') {
  // see Step 2b
} else if (auth.method === 'already_approved') {
  // skip to Step 3
}
2

2a — Permit path

For permit-supporting tokens (USDC mainnet, DAI, most modern ERC-20s with EIP-2612), sign the typed data with viem and forward the signature to the backend. The primaryType is always 'Permit'.
import { createWalletClient, custom } from 'viem'
import { mainnet } from 'viem/chains'

if (auth.method === 'permit') {
  const walletClient = createWalletClient({
    account: userAddress,
    chain: mainnet,
    transport: custom(window.ethereum),
  })

  permitSignature = await walletClient.signTypedData({
    account: userAddress,
    domain: auth.permitData.domain,
    types: auth.permitData.types,
    primaryType: 'Permit',
    message: auth.permitData.value,
  })
  permitDeadline = Number(auth.permitData.deadline)
}
With wagmi:
import { useSignTypedData } from 'wagmi'

const { signTypedDataAsync } = useSignTypedData()

if (auth.method === 'permit') {
  permitSignature = await signTypedDataAsync({
    domain: auth.permitData.domain,
    types: auth.permitData.types,
    primaryType: 'Permit',
    message: auth.permitData.value,
  })
  permitDeadline = Number(auth.permitData.deadline)
}
3

2b — Approval path

When the token does not support permit (or you want to force a standard ERC-20 flow), the backend returns a ready-to-submit approvalTransaction containing ABI-encoded approve() calldata. Submit it to the deposit asset address and wait for the receipt before calling prepareDeposit.
import { useSendTransaction, usePublicClient } from 'wagmi'

const { sendTransactionAsync } = useSendTransaction()
const publicClient = usePublicClient()

if (auth.method === 'approval') {
  const approvalHash = await sendTransactionAsync({
    to: depositAsset, // the token being approved
    data: auth.approvalTransaction.encoded as Hex,
    chainId,
  })

  await publicClient!.waitForTransactionReceipt({ hash: approvalHash })
}
Always wait for the approval receipt before submitting the deposit. If the deposit lands before the approval is mined, the deposit transaction will revert with an ERC20: insufficient allowance style error.
4

2c — Already approved

if (auth.method === 'already_approved') {
  // Nothing to do — fall through to Step 3.
}
5

Step 3 — Prepare the deposit

const prepared = await client.deposit.prepareDeposit({
  vaultAddress: '0xbbbb000000000000000000000000000000000001',
  depositAsset, // e.g. '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' for USDC
  depositAmount, // base-units decimal string
  userAddress,
  // Optional: send shares to a different address than the signer
  // to: receiverAddress,
  chainId: 1,
  // Permit fields — pass BOTH or NEITHER
  ...(permitSignature && permitDeadline ? { permitSignature, permitDeadline } : {}),
})

// prepared.transaction = { to, data, value, ... }
Required fields:
  • vaultAddress — BoringVault contract address.
  • depositAsset — ERC-20 token address you’re depositing.
  • depositAmount — amount in base units (decimal string).
  • userAddress — wallet that signs and submits the deposit. Also the default share recipient when to is omitted.
  • chainId — EVM chain ID.
Optional fields:
  • to — destination address that receives the vault shares. Defaults to userAddress.
  • permitSignature + permitDeadline — required together when you came through the permit branch in Step 2a.
  • responseFormat'encoded' (default), 'full', or 'structured'. Pass 'full' to also receive abi, functionName, and args.
6

Step 4 — Submit the transaction

const tx = prepared.transaction
const depositHash = await sendTransactionAsync({
  to: tx.to as Address,
  data: tx.data as Hex,
  value: BigInt(tx.value),
  chainId,
})

await publicClient!.waitForTransactionReceipt({ hash: depositHash })
tx.value is a decimal string (usually "0" for ERC-20 deposits); cast to BigInt before passing to viem/wagmi.

End-to-end example

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!,
})

async function deposit({
  userAddress,
  depositAsset,
  userInput,
  chainId,
}: {
  userAddress: Address
  depositAsset: Address
  userInput: string // e.g. "10" for 10 USDC
  chainId: number
}) {
  const publicClient = createPublicClient({ chain: mainnet, transport: http() })
  const walletClient = createWalletClient({
    account: userAddress,
    chain: mainnet,
    transport: custom(window.ethereum),
  })

  // 1. Discover a vault that accepts the deposit asset
  const { vaults } = await client.vaults.list({
    filter: `chainId=${chainId}`,
  })

  const vault = vaults
    .flatMap((v) => v.deployments)
    .find((d) =>
      d.assets.some(
        (a) => a.assetAddress.toLowerCase() === depositAsset.toLowerCase() && a.depositable,
      ),
    )
  if (!vault) {
    throw new Error(`No vault on chain ${chainId} accepts ${depositAsset}`)
  }

  // 2. Read decimals from the deposit asset
  const decimals = await publicClient.readContract({
    address: depositAsset,
    abi: [{ type: 'function', name: 'decimals', inputs: [], outputs: [{ type: 'uint8' }], stateMutability: 'view' }],
    functionName: 'decimals',
  })
  const depositAmount = parseUnits(userInput, decimals).toString()

  // 3. Authorize
  const auth = await client.permit.authorize({
    vaultAddress: vault.boringVaultAddress as Address,
    tokenAddress: depositAsset,
    amount: depositAmount,
    userAddress,
    chainId,
  })

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

  // 4. Handle the three branches
  if (auth.method === 'permit') {
    permitSignature = await walletClient.signTypedData({
      account: userAddress,
      domain: auth.permitData.domain,
      types: auth.permitData.types,
      primaryType: 'Permit',
      message: auth.permitData.value,
    })
    permitDeadline = Number(auth.permitData.deadline)
  } else if (auth.method === 'approval') {
    const hash = await walletClient.sendTransaction({
      to: depositAsset,
      data: auth.approvalTransaction.encoded as Hex,
    })
    await publicClient.waitForTransactionReceipt({ hash })
  }

  // 5. Prepare
  const prepared = await client.deposit.prepareDeposit({
    vaultAddress: vault.boringVaultAddress as Address,
    depositAsset,
    depositAmount,
    userAddress,
    chainId,
    ...(permitSignature && permitDeadline ? { permitSignature, permitDeadline } : {}),
  })

  // 6. Submit
  const tx = prepared.transaction
  const depositHash = await walletClient.sendTransaction({
    to: tx.to as Address,
    data: tx.data as Hex,
    value: BigInt(tx.value),
  })
  await publicClient.waitForTransactionReceipt({ hash: depositHash })

  return depositHash
}

Converting user input to base units

import { parseUnits } from 'viem'

// Read decimals live from the contract (do not hardcode).
const decimals = await publicClient.readContract({
  address: depositAsset,
  abi: [{ type: 'function', name: 'decimals', inputs: [], outputs: [{ type: 'uint8' }], stateMutability: 'view' }],
  functionName: 'decimals',
})

const depositAmount = parseUnits(userInput, decimals).toString()

Error handling

import { AmplifyError, AmplifyTimeoutError } from '@paxoslabs/amplify-sdk'

try {
  await client.deposit.prepareDeposit({ /* ... */ })
} catch (err) {
  if (err instanceof AmplifyTimeoutError) {
    // Retry, surface a timeout UI, etc.
  } else if (err instanceof AmplifyError) {
    // err.statusCode  number  — HTTP status (e.g. 400, 401, 500)
    // err.body        unknown — parsed backend error body (when JSON)
    // err.message     string  — human-readable summary
    // err.rawResponse RawResponse — the original Response
    console.error('Amplify error', err.statusCode, err.message)
  } else {
    throw err
  }
}
When surfacing errors to the browser, log err.body and err.rawResponse server-side and return a generic message to the client.

Next steps