Skip to main content
Copy this page into your AI coding assistant (Cursor, Copilot, Claude, etc.) for accurate Amplify REST calldata API completions — the path that returns ready-to-sign EVM transactions without requiring the TypeScript SDK.
This page is a condensed, single-file reference for the REST calldata API. For full documentation with per-language tabs and step-by-step walkthroughs, see the API Calldata Integration guide.
Using a skill-aware tool? The same content is exposed as the amplify-earn-api-calldata Agent Skill and is auto-discovered at /.well-known/agent-skills/index.json. Prefer the skill in agents that support them — it loads progressively. Use this page when you need to paste a single reference into a general-purpose AI chat.

When to Use

  • Backend transaction builders in any language (Python, Go, Rust, Java, Node)
  • Custodial wallets, HSMs, multisig services — fetch calldata, sign elsewhere
  • Non-JS stacks that can’t consume @paxoslabs/amplify-sdk
  • Prototyping with curl before application code
For the TypeScript SDK with built-in wallet hooks, see the SDK AI Reference. For calling contracts directly without the API, see the Contract AI Reference.

Base URL & Authentication

Base URL:   https://api.paxoslabs.com
API prefix: /v2
Auth:       x-api-key: pxl_<public_id>_<secret>
  • Obtain a key at https://app.paxoslabs.com.
  • Send the key as a header — never as a query param (it ends up in access logs).

OpenAPI Spec

Endpoint Map

Transaction preparation

EndpointPurpose
GET /v2/core/permitDetect authorization method (permit / approval / already_approved)
GET /v2/amplify/depositBuild deposit calldata
GET /v2/amplify/withdrawBuild withdrawal order calldata
GET /v2/amplify/withdraw/cancelBuild cancel-order calldata

Discovery

EndpointPurpose
GET /v2/amplify/vaultsAccounts grouped by name with per-chain deployments[]; each deployment carries contract addresses, per-asset flags, fees, supply caps, min order size, SLAs

Order status

EndpointPurpose
GET /v2/amplify/withdrawalRequestsWithdrawal orders with orderIndex and status

Analytics

EndpointPurpose
GET /v2/amplify/vaultApysHistorical APY time series per vault
GET /v2/amplify/vaultTvlsHistorical TVL time series (+ current on-chain TVL on the final page)
GET /v2/amplify/vaultAssetsPer-asset depositable/withdrawable flags (paginated, flat)
GET /v2/amplify/vaultCompositionsCurrent portfolio composition from the rate provider
GET /v2/amplify/withdrawalVolumesHistorical withdrawal volumes (requires vaultAddress, startTime, endTime)
GET /v2/amplify/liquidityShortfallsPending withdrawal demand in excess of current vault balance

Display helpers

EndpointPurpose
GET /v2/amplify/calculateWithdrawalFeePreview fee for a specific offerAmount + wantAsset redemption
On-/v2/amplify/vaults (no dedicated endpoint needed): depositSupplyCap, minimumWithdrawalOrderSize, assets[].depositFees, assets[].withdrawFees, assets[].withdrawalSLAs.

Response Format (calldata endpoints only)

/v2/amplify/deposit, /v2/amplify/withdraw, and /v2/amplify/withdraw/cancel accept a responseFormat query parameter:
responseFormatdata (hex)abi / functionName / argsUse when
encoded (default)Signer consumes raw data (eth_sendTransaction, HSM)
fullDebugging; need raw + decoded views
structuredEncode locally (viem encodeFunctionData, ethers Interface.encodeFunctionData)

Filter Syntax (filter query parameter)

filter=field%3Dvalue                          # equality
filter=field1%3Dvalue1%20AND%20field2%3Dvalue2 # conjunction
  • URL-encode once: %3D = =, %20 = space.
  • Separate query params (chainId=1&inDeprecation=false) are ignored.

Discovery — GET /v2/amplify/vaults

Single aggregated endpoint. Params (all optional): filter (flags: name, chainId, inDeprecation, requiresKyt), pageSize (1–100, default 25), pageToken.
curl "https://api.paxoslabs.com/v2/amplify/vaults?filter=chainId%3D1%20AND%20inDeprecation%3Dfalse" \
  -H "x-api-key: $AMPLIFY_API_KEY"
{
  "vaults": [{
    "name": "Amplify USDC Core",
    "deployments": [{
      "chainId": 1,
      "boringVaultAddress": "0xbbbb...",
      "depositorAddress": "0xcccc...",
      "withdrawQueueAddress": "0xdddd...",
      "requiresKyt": false,
      "baseTokenAddress": "0xA0b8...",
      "accountantAddress": "0xaaaa...",
      "tellerAddress": "0xeeee...",
      "depositFeeAddress": null,
      "withdrawFeeAddress": null,
      "inDeprecation": false,
      "depositSupplyCap": { "raw": "1000000000000", "formatted": "1000000.0", "decimals": 6, "hasCap": true },
      "minimumWithdrawalOrderSize": { "raw": "1000000000000000000", "formatted": "1.0", "decimals": 18 },
      "assets": [{
        "assetAddress": "0xA0b8...",
        "depositable": true,
        "withdrawable": true,
        "depositFees": { "bps": 0, "percentage": "0.0000" },
        "withdrawFees": { "bps": 25, "percentage": "0.2500" },
        "withdrawalSLAs": { "expectedDelay": "86400s", "expiryBuffer": "36000s", "internalWithdrawalQueueDelaySLA": "43200s", "externalWithdrawalQueueDelaySLA": "86400s", "internalAccountantRateUpdateDelaySLA": "43200s", "externalAccountantRateUpdateSLA": "86400s" }
      }]
    }]
  }],
  "nextPageToken": null
}

Field → Transaction Parameter Mapping

Discovery fieldUsed as
deployments[].boringVaultAddressvaultAddress in /v2/core/permit and all calldata endpoints; also the ERC-20 share token
deployments[].depositorAddresstransaction.to returned by the prepared deposit
deployments[].withdrawQueueAddressSpender for the share approval before withdrawal
deployments[].baseTokenAddressDefault depositAsset / wantAsset
deployments[].requiresKyt: trueKYT attestation is resolved server-side — no special client handling required
deployments[].depositSupplyCap{ raw, formatted, decimals, hasCap }; hasCap=false → uncapped
deployments[].minimumWithdrawalOrderSizeMinimum shareAmount accepted by /v2/amplify/withdraw

Deposit Flow

Three authorization branches handled by one API:
MethodMeaning
permitEIP-2612 off-chain signature — gas-efficient, single on-chain tx
approvalStandard ERC-20 approve required first, then the deposit
already_approvedSufficient allowance exists — deposit directly

Step 1 — Detect authorization

GET /v2/core/permit
ParamRequiredNotes
vaultAddressYesdeployments[].boringVaultAddress from /v2/amplify/vaults
tokenAddressYesERC-20 deposit token
amountYesDecimal string, token base units
userAddressYesDepositor wallet
chainIdYesEVM chain ID
Response variants:
// permit
{
  "method": "permit",
  "permitData": {
    "domain": { "name": "USD Coin", "version": "2", "chainId": 1, "verifyingContract": "0xA0b8..." },
    "types":  { "Permit": [ ... ] },
    "value":  { "owner": "0x1234...", "spender": "0xcccc...", "value": "1000000", "nonce": "0", "deadline": "9999999999" },
    "deadline": "9999999999"
  }
}

// approval
{ "method": "approval", "approvalTransaction": { "encoded": "0x095ea7b3..." } }

// already_approved
{ "method": "already_approved" }

Step 2 — Satisfy authorization

BranchAction
permitSign domain + types + value via eth_signTypedData_v4 (primaryType: "Permit"). Keep signature and permitData.deadline.
approvalSend tx with to = tokenAddress, data = approvalTransaction.encoded. Wait for receipt.
already_approvedSkip to step 3.

Step 3 — Prepare deposit calldata

GET /v2/amplify/deposit
ParamRequiredNotes
vaultAddressYesboringVaultAddress
depositAssetYesERC-20 to deposit
depositAmountYesDecimal string, token base units
userAddressYesWallet that signs and submits; also the default share recipient
chainIdYesEVM chain ID
toNoShare recipient override (maps to on-chain to arg). Defaults to userAddress.
permitSignatureConditionalRequired on permit path
permitDeadlineConditionalFrom permitData.deadline; required with permitSignature
responseFormatNoencoded (default), full, structured
Response (encoded):
{ "transaction": { "to": "0xcccc...", "data": "0x47e7ef24...", "value": "0" } }

Step 4 — Sign & broadcast

Send { to, data, value }. value is always "0" for ERC-20 deposits.

Reference implementation (Node / viem)

import { createWalletClient, createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'

const BASE = 'https://api.paxoslabs.com'
const HEADERS = { 'x-api-key': process.env.AMPLIFY_API_KEY! }

// 1. Detect authorization
const permitUrl = new URL(`${BASE}/v2/core/permit`)
permitUrl.searchParams.set('vaultAddress', VAULT_ADDRESS)
permitUrl.searchParams.set('tokenAddress', DEPOSIT_ASSET)
permitUrl.searchParams.set('amount', AMOUNT)
permitUrl.searchParams.set('userAddress', account.address)
permitUrl.searchParams.set('chainId', '1')
const permit = await fetch(permitUrl, { headers: HEADERS }).then((r) => r.json())

// 2. Build deposit params, branch on method
const depositUrl = new URL(`${BASE}/v2/amplify/deposit`)
depositUrl.searchParams.set('vaultAddress', VAULT_ADDRESS)
depositUrl.searchParams.set('depositAsset', DEPOSIT_ASSET)
depositUrl.searchParams.set('depositAmount', AMOUNT)
depositUrl.searchParams.set('userAddress', account.address)
depositUrl.searchParams.set('chainId', '1')

if (permit.method === 'permit') {
  const sig = await walletClient.signTypedData({
    account,
    domain: permit.permitData.domain,
    types: permit.permitData.types,
    primaryType: 'Permit',
    message: permit.permitData.value,
  })
  depositUrl.searchParams.set('permitSignature', sig)
  depositUrl.searchParams.set('permitDeadline', permit.permitData.deadline)
} else if (permit.method === 'approval') {
  const hash = await walletClient.sendTransaction({
    to: DEPOSIT_ASSET as `0x${string}`,
    data: permit.approvalTransaction.encoded as `0x${string}`,
    chain: mainnet,
    account,
  })
  await publicClient.waitForTransactionReceipt({ hash })
}

// 3. Fetch + submit deposit
const { transaction: tx } = await fetch(depositUrl, { headers: HEADERS }).then((r) => r.json())
await walletClient.sendTransaction({
  to: tx.to as `0x${string}`,
  data: tx.data as `0x${string}`,
  value: BigInt(tx.value),
  chain: mainnet,
  account,
})

Withdrawal Flow

Withdrawals are asynchronous. Submitting an order locks shares in the WithdrawQueue; the protocol processes the queue off-cycle and transfers the asset on completion.

Step 1 — Approve share spending

Standard ERC-20 approve(spender, amount):
  • Token contract (to): boringVaultAddress
  • spender: withdrawQueueAddress
  • amount: share amount to redeem (always 18 decimals)
Wait for a receipt before step 2.

Step 2 — Prepare withdrawal calldata

GET /v2/amplify/withdraw
ParamRequiredNotes
vaultAddressYesboringVaultAddress
wantAssetYesERC-20 to receive
shareAmountYesDecimal string, 18 decimals
userAddressYesSubmitter; also the default intendedDepositor/receiver/refundReceiver when those are omitted
chainIdYesEVM chain ID
intendedDepositorNoOn-chain SubmitOrderParams.intendedDepositor. Defaults to userAddress.
receiverNoAddress that receives wantAsset on settlement. Defaults to userAddress.
refundReceiverNoAddress that receives refunded shares if the order is cancelled. Defaults to userAddress.
responseFormatNoencoded / full / structured
The server picks submitOrder vs submitOrderAndProcessAll automatically based on the account’s on-chain RolesAuthority configuration — there is no client-side atomic flag. Response:
{ "transaction": { "to": "0xdddd...", "data": "0x1a2b3c4d...", "value": "0" } }

Step 3 — Sign & submit

Broadcast. On confirmation, shares are locked under a new orderIndex.

Step 4 — Poll order status

curl "https://api.paxoslabs.com/v2/amplify/withdrawalRequests?\
filter=userAddress%3D0x1234...%20AND%20vaultAddress%3D0xbbbb..." \
  -H "x-api-key: $AMPLIFY_API_KEY"
StatusMeaning
PENDINGIn queue
COMPLETEAsset transferred
PENDING_REFUNDBeing refunded
REFUNDEDShares returned
Do not filter on status=PENDING while polling — orders disappear the moment they reach a terminal state and you lose visibility into the outcome. Filter on userAddress + vaultAddress (and optionally chainId).

Reference implementation (Node / viem)

import { encodeFunctionData, erc20Abi } from 'viem'

// 1. Approve shares to the queue
const approveData = encodeFunctionData({
  abi: erc20Abi,
  functionName: 'approve',
  args: [WITHDRAW_QUEUE as `0x${string}`, BigInt(SHARE_AMOUNT)],
})
const approveHash = await walletClient.sendTransaction({
  to: VAULT_ADDRESS as `0x${string}`,
  data: approveData,
  chain: mainnet,
  account,
})
await publicClient.waitForTransactionReceipt({ hash: approveHash })

// 2. Fetch withdraw calldata
const url = new URL('https://api.paxoslabs.com/v2/amplify/withdraw')
url.searchParams.set('vaultAddress', VAULT_ADDRESS)
url.searchParams.set('wantAsset', WANT_ASSET)
url.searchParams.set('shareAmount', SHARE_AMOUNT)
url.searchParams.set('userAddress', account.address)
url.searchParams.set('chainId', '1')
const { transaction: tx } = await fetch(url, { headers: HEADERS }).then((r) => r.json())

// 3. Submit
await walletClient.sendTransaction({
  to: tx.to as `0x${string}`,
  data: tx.data as `0x${string}`,
  value: BigInt(tx.value),
  chain: mainnet,
  account,
})

Cancellation Flow

Only PENDING orders are cancellable. The wallet submitting the cancel tx must be the same address that submitted the original order (msg.sender enforced on-chain).

Step 1 — Find orderIndex

curl "https://api.paxoslabs.com/v2/amplify/withdrawalRequests?\
filter=userAddress%3D0x1234...%20AND%20vaultAddress%3D0xbbbb...%20AND%20status%3DPENDING" \
  -H "x-api-key: $AMPLIFY_API_KEY"
orderIndex is a decimal string — pass it verbatim; never coerce to a JS number.

Step 2 — Prepare cancel calldata

GET /v2/amplify/withdraw/cancel
ParamRequiredNotes
vaultAddressYesboringVaultAddress
orderIndexYesExact string from withdrawalRequests.orderIndex
chainIdYesEVM chain ID
responseFormatNoencoded / full / structured
Response:
{ "transaction": { "to": "0xdddd...", "data": "0x5c975abb...", "value": "0" } }

Step 3 — Sign & broadcast

Send from the same wallet that submitted the order. On confirmation, locked shares return to that wallet.

Display Helpers (read-only)

Most display data is returned inline on /v2/amplify/vaults (see Discovery above) — the only standalone helper is fee preview for a specific redemption.
EndpointRequired paramsReturns
GET /v2/amplify/calculateWithdrawalFeeofferAmount, wantAsset, vaultAddress, chainId{ feeAmount, offerFeePercentage: { bps, percentage }, flatFee } — all decimal strings / integers, scaled to human-readable units
Fields already on /v2/amplify/vaults (no dedicated endpoint):
FieldShapeNotes
deployments[].depositSupplyCap{ raw, formatted, decimals, hasCap }hasCap: false → uncapped (maxUint256)
deployments[].minimumWithdrawalOrderSize{ raw, formatted, decimals }Minimum shareAmount accepted by /v2/amplify/withdraw
deployments[].assets[].depositFees / withdrawFees{ bps, percentage }Per-asset fees
deployments[].assets[].withdrawalSLAsProtobuf duration strings (e.g. "86400s")Queue + rate-update SLAs
Client caching of 10–30s is safe; invalidate after any of the caller’s own deposits, withdrawals, or cancellations.

Error Envelope

{
  "error": {
    "code": 400,
    "message": "vaultAddress is not a valid hex address",
    "status": "INVALID_ARGUMENT",
    "details": [{
      "@type": "type.paxoslabs.dev/errors/BadRequest",
      "fieldViolations": [{ "field": "vaultAddress", "description": "..." }]
    }]
  }
}
HTTPerror.statusAction
400INVALID_ARGUMENTFix request; inspect fieldViolations
401UNAUTHENTICATEDVerify x-api-key header
403PERMISSION_DENIEDRotate the key
404NOT_FOUNDRe-discover via /v2/amplify/vaults
429RESOURCE_EXHAUSTEDBack off (respect Retry-After if present)
503INTERNALRetry with exponential backoff
Retries: 429 and 503 are safe. 400, 401, 403, 404 are deterministic — do not retry without changing inputs.

Common Gotchas

GotchaConsequenceFix
Filter passed as separate query paramsPredicates silently ignoredUse single filter=field%3Dvalue%20AND%20...
permitSignature without permitDeadline400 INVALID_ARGUMENTSend both from the permit response
Expired permit deadlineOn-chain revertRe-fetch /v2/core/permit
Approval tx not mined before depositRevert (insufficient allowance)Wait for receipt before preparing deposit
Passing a client-side atomic flag to /v2/amplify/withdrawIgnoredThe server chooses submitOrder vs submitOrderAndProcessAll based on on-chain roles
Deposit amount uses wrong decimalsRevert / roundingUSDC/USDT = 6 decimals; shares always 18
shareAmount uses token decimalsToo-small withdrawal or 400Share tokens are always 18 decimals
Polling with status=PENDINGOrder “vanishes” at completionDrop the status predicate from the poll query
orderIndex coerced to numberOff-by-one for large indicesKeep as string end-to-end
Cancel from a different walletOn-chain revertSign from the original submitter
Stale address cacheCalls hit deprecated contractsRespect inDeprecation; refresh on startup

Supported Chains

ChainID
Ethereum1
Sepolia11155111
Base8453
HyperEVM999
Stable Testnet2201

Canonical Docs