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.
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>
OpenAPI Spec
Endpoint Map
Transaction preparation
| Endpoint | Purpose |
|---|
GET /v2/core/permit | Detect authorization method (permit / approval / already_approved) |
GET /v2/amplify/deposit | Build deposit calldata |
GET /v2/amplify/withdraw | Build withdrawal order calldata |
GET /v2/amplify/withdraw/cancel | Build cancel-order calldata |
Discovery
| Endpoint | Purpose |
|---|
GET /v2/amplify/vaults | Accounts grouped by name with per-chain deployments[]; each deployment carries contract addresses, per-asset flags, fees, supply caps, min order size, SLAs |
Order status
| Endpoint | Purpose |
|---|
GET /v2/amplify/withdrawalRequests | Withdrawal orders with orderIndex and status |
Analytics
| Endpoint | Purpose |
|---|
GET /v2/amplify/vaultApys | Historical APY time series per vault |
GET /v2/amplify/vaultTvls | Historical TVL time series (+ current on-chain TVL on the final page) |
GET /v2/amplify/vaultAssets | Per-asset depositable/withdrawable flags (paginated, flat) |
GET /v2/amplify/vaultCompositions | Current portfolio composition from the rate provider |
GET /v2/amplify/withdrawalVolumes | Historical withdrawal volumes (requires vaultAddress, startTime, endTime) |
GET /v2/amplify/liquidityShortfalls | Pending withdrawal demand in excess of current vault balance |
Display helpers
| Endpoint | Purpose |
|---|
GET /v2/amplify/calculateWithdrawalFee | Preview fee for a specific offerAmount + wantAsset redemption |
GET /v2/amplify/supplyCaps | Up-to-the-block totalSupplyInBase, supplyCap, and percentageFilled per chain deployment |
GET /v2/amplify/userPositions | User shareBalance, positionValueInBase, baseAssetAddress, and exchangeRateToBase per chain deployment |
On-/v2/amplify/vaults (no dedicated endpoint needed): depositSupplyCap, minimumWithdrawalOrderSize, assets[].depositFees, assets[].withdrawFees, assets[].withdrawalSLAs.
/v2/amplify/deposit, /v2/amplify/withdraw, and /v2/amplify/withdraw/cancel accept a responseFormat query parameter:
responseFormat | data (hex) | abi / functionName / args | Use when |
|---|
encoded (default) | ✅ | ❌ | Signer consumes raw data (eth_sendTransaction, HSM) |
full | ✅ | ✅ | Debugging; need raw + decoded views |
structured | ❌ | ✅ | Encode 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 field | Used as |
|---|
deployments[].boringVaultAddress | vaultAddress in /v2/core/permit and all calldata endpoints; also the ERC-20 share token |
deployments[].depositorAddress | transaction.to returned by the prepared deposit |
deployments[].withdrawQueueAddress | Spender for the share approval before withdrawal |
deployments[].baseTokenAddress | Default depositAsset / wantAsset |
deployments[].requiresKyt: true | KYT attestation is resolved server-side — no special client handling required |
deployments[].depositSupplyCap | { raw, formatted, decimals, hasCap }; hasCap=false → uncapped |
deployments[].minimumWithdrawalOrderSize | Minimum shareAmount accepted by /v2/amplify/withdraw |
Deposit Flow
Three authorization branches handled by one API:
| Method | Meaning |
|---|
permit | EIP-2612 off-chain signature — gas-efficient, single on-chain tx |
approval | Standard ERC-20 approve required first, then the deposit |
already_approved | Sufficient allowance exists — deposit directly |
Step 1 — Detect authorization
GET /v2/core/permit
| Param | Required | Notes |
|---|
vaultAddress | Yes | deployments[].boringVaultAddress from /v2/amplify/vaults |
tokenAddress | Yes | ERC-20 deposit token |
amount | Yes | Decimal string, token base units |
userAddress | Yes | Depositor wallet |
chainId | Yes | EVM 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
| Branch | Action |
|---|
permit | Sign domain + types + value via eth_signTypedData_v4 (primaryType: "Permit"). Keep signature and permitData.deadline. |
approval | Send tx with to = tokenAddress, data = approvalTransaction.encoded. Wait for receipt. |
already_approved | Skip to step 3. |
Step 3 — Prepare deposit calldata
GET /v2/amplify/deposit
| Param | Required | Notes |
|---|
vaultAddress | Yes | boringVaultAddress |
depositAsset | Yes | ERC-20 to deposit |
depositAmount | Yes | Decimal string, token base units |
userAddress | Yes | Wallet that signs and submits; also the default share recipient |
chainId | Yes | EVM chain ID |
to | No | Share recipient override (maps to on-chain to arg). Defaults to userAddress. |
permitSignature | Conditional | Required on permit path |
permitDeadline | Conditional | From permitData.deadline; required with permitSignature |
responseFormat | No | encoded (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
| Param | Required | Notes |
|---|
vaultAddress | Yes | boringVaultAddress |
wantAsset | Yes | ERC-20 to receive |
shareAmount | Yes | Decimal string, 18 decimals |
userAddress | Yes | Submitter; also the default intendedDepositor/receiver/refundReceiver when those are omitted |
chainId | Yes | EVM chain ID |
intendedDepositor | No | On-chain SubmitOrderParams.intendedDepositor. Defaults to userAddress. |
receiver | No | Address that receives wantAsset on settlement. Defaults to userAddress. |
refundReceiver | No | Address that receives refunded shares if the order is cancelled. Defaults to userAddress. |
responseFormat | No | encoded / 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"
| Status | Meaning |
|---|
PENDING | In queue |
COMPLETE | Asset transferred |
PENDING_REFUND | Being refunded |
REFUNDED | Shares 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
| Param | Required | Notes |
|---|
vaultAddress | Yes | boringVaultAddress |
orderIndex | Yes | Exact string from withdrawalRequests.orderIndex |
chainId | Yes | EVM chain ID |
responseFormat | No | encoded / 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). Three dedicated helpers cover what isn’t on the discovery response: fee preview for a specific redemption, up-to-the-block supply-cap utilization, and a user’s current position.
| Endpoint | Required params | Returns |
|---|
GET /v2/amplify/calculateWithdrawalFee | offerAmount, wantAsset, vaultAddress, chainId | { feeAmount, offerFeePercentage: { bps, percentage }, flatFee } — all decimal strings / integers, scaled to human-readable units |
GET /v2/amplify/supplyCaps | optional filter (flags: vaultAddress, chainId); optional pageSize, pageToken | { supplyCaps: [{ vaultAddress, chainId, totalSupplyInBase, supplyCap, percentageFilled }], nextPageToken } — supplyCap / percentageFilled are null for uncapped vaults. With no filter, every live vault deployment is returned. Filter flags: vaultAddress (optional), chainId (optional). |
GET /v2/amplify/userPositions | required userAddress (top-level, hex); optional filter (flags: vaultAddress, chainId); optional pageSize, pageToken | { userPositions: [{ vaultAddress, chainId, shareBalance, positionValueInBase, baseAssetAddress, exchangeRateToBase }], nextPageToken, tokenMetadata } — baseAssetAddress is read from the DB (or via accountant.base() for stand-in vaults where the canonical base asset isn’t natively deployed). With no filter, the user position is returned for every live vault deployment. Filter flags: vaultAddress (optional), chainId (optional). |
Fields already on /v2/amplify/vaults (no dedicated endpoint):
| Field | Shape | Notes |
|---|
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[].withdrawalSLAs | Protobuf 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": "..." }]
}]
}
}
| HTTP | error.status | Action |
|---|
| 400 | INVALID_ARGUMENT | Fix request; inspect fieldViolations |
| 401 | UNAUTHENTICATED | Verify x-api-key header |
| 403 | PERMISSION_DENIED | Rotate the key |
| 404 | NOT_FOUND | Re-discover via /v2/amplify/vaults |
| 429 | RESOURCE_EXHAUSTED | Back off (respect Retry-After if present) |
| 503 | INTERNAL | Retry with exponential backoff |
Retries: 429 and 503 are safe. 400, 401, 403, 404 are deterministic — do not retry without changing inputs.
Common Gotchas
| Gotcha | Consequence | Fix |
|---|
| Filter passed as separate query params | Predicates silently ignored | Use single filter=field%3Dvalue%20AND%20... |
permitSignature without permitDeadline | 400 INVALID_ARGUMENT | Send both from the permit response |
| Expired permit deadline | On-chain revert | Re-fetch /v2/core/permit |
| Approval tx not mined before deposit | Revert (insufficient allowance) | Wait for receipt before preparing deposit |
Passing a client-side atomic flag to /v2/amplify/withdraw | Ignored | The server chooses submitOrder vs submitOrderAndProcessAll based on on-chain roles |
Deposit amount uses wrong decimals | Revert / rounding | USDC/USDT = 6 decimals; shares always 18 |
shareAmount uses token decimals | Too-small withdrawal or 400 | Share tokens are always 18 decimals |
Polling with status=PENDING | Order “vanishes” at completion | Drop the status predicate from the poll query |
orderIndex coerced to number | Off-by-one for large indices | Keep as string end-to-end |
| Cancel from a different wallet | On-chain revert | Sign from the original submitter |
| Stale address cache | Calls hit deprecated contracts | Respect inDeprecation; refresh on startup |
Supported Chains
| Chain | ID |
|---|
| Ethereum | 1 |
| Sepolia | 11155111 |
| Base | 8453 |
| HyperEVM | 999 |
| Stable Testnet | 2201 |
Canonical Docs