| Method | Description |
|---|---|
| Permit | Gas-efficient off-chain signature (EIP-2612). Single transaction — no separate approval needed. |
| Approval | Standard ERC-20 approve transaction followed by the deposit transaction. |
| Already Approved | Direct deposit when sufficient allowance already exists. |
Prerequisites
- A Paxos Labs API key
- An EVM-compatible signer (private key, HSM, or wallet service)
- An HTTP client library for your language
Step 0: Fetch Available Accounts
Retrieve all accounts accessible with your API key. Accounts are grouped byname, with a deployments[] array per chain. Record boringVaultAddress, chainId, and baseTokenAddress for the deployment you want to deposit into.
curl "https://api.paxoslabs.com/v2/amplify/vaults?filter=chainId%3D1%20AND%20inDeprecation%3Dfalse" \
-H "x-api-key: pxl_your_key"
deployments[i] entry:
| Field | Usage |
|---|---|
boringVaultAddress | Passed to GET /v2/core/permit and GET /v2/amplify/deposit as vaultAddress |
chainId | Passed to all endpoints |
baseTokenAddress | The primary deposit asset address |
Step 1: Check Authorization
GET /v2/core/permit detects whether the deposit token supports EIP-2612 permits, requires a standard ERC-20 approval, or already has sufficient allowance.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
vaultAddress | string | Yes | BoringVault contract address (boringVaultAddress from discovery) |
tokenAddress | string | Yes | ERC-20 deposit token address |
amount | string | Yes | Deposit amount in token base units (decimal string) |
userAddress | string | Yes | Depositor’s wallet address |
chainId | number | Yes | EVM chain ID |
Response Variants
The responsemethod field tells you which path to follow:
permit — Gas-efficient off-chain signature (EIP-2612). Single transaction, no separate approval needed.
{
"method": "permit",
"permitData": {
"domain": {
"name": "USD Coin",
"version": "2",
"chainId": 1,
"verifyingContract": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"types": { "Permit": [...] },
"value": {
"owner": "0x1234...",
"spender": "0xcccc...",
"value": "1000000",
"nonce": "0",
"deadline": "9999999999"
},
"deadline": "9999999999"
}
}
approval — Standard ERC-20 approve() required before depositing.
{
"method": "approval",
"approvalTransaction": {
"encoded": "0x095ea7b3000000000000000000000000..."
}
}
already_approved — Sufficient allowance exists. Skip directly to the deposit.
{
"method": "already_approved"
}
Step 2: Handle Authorization
Permit Path
Sign the EIP-712 typed data frompermitData using eth_signTypedData_v4 (or your library’s equivalent), then include the signature in the deposit request.
Approval Path
Send theapprovalTransaction.encoded calldata as a transaction to the deposit token contract address (tokenAddress from step 1). Wait for confirmation, then proceed to the deposit.
Already Approved Path
Skip directly to step 3.Step 3: Prepare Deposit Calldata
GET /v2/amplify/deposit returns the transaction object for the deposit.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
vaultAddress | string | Yes | BoringVault contract address (0x + 40 hex chars) |
depositAsset | string | Yes | ERC-20 token address to deposit |
depositAmount | string | Yes | Amount in token base units (decimal string) |
userAddress | string | Yes | Wallet address signing and submitting the transaction. Also the default share recipient when to is omitted. |
chainId | number | Yes | EVM chain ID |
to | string | No | Destination address that receives the vault shares (maps to the on-chain to argument on DistributorCodeDepositor.deposit()). Defaults to userAddress when omitted. |
permitSignature | string | No | EIP-2612 permit signature (65-byte hex). Required for permit path. |
permitDeadline | number | No | Permit deadline as Unix timestamp. Required when permitSignature is provided. |
responseFormat | string | No | encoded (default), full, or structured |
Response
{
"transaction": {
"to": "0xcccc000000000000000000000000000000000001",
"data": "0x47e7ef24000000000000000000000000...",
"value": "0",
"abi": [{"type": "function", "name": "deposit", "inputs": [...]}],
"functionName": "deposit",
"args": ["0xA0b8...", "1000000", "999500", "0x1234..."]
}
}
The
abi, functionName, and args fields are only present when
responseFormat is full or structured. See
Authentication
for details.Step 4: Sign and Submit
Send the transaction using your signer or wallet infrastructure:to— the contract address to calldata— the ABI-encoded calldata (when usingencodedorfullformat)value— ETH to send (usually"0"for ERC-20 deposits)
Complete Examples
- Node.js
- Python
- Go
- Java
import { createWalletClient, createPublicClient, http, custom } from "viem";
import { mainnet } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
const API_KEY = process.env.AMPLIFY_API_KEY!;
const PRIVATE_KEY = process.env.PRIVATE_KEY as `0x${string}`;
const BASE = "https://api.paxoslabs.com";
const HEADERS = { "x-api-key": API_KEY };
const VAULT_ADDRESS = "0xbbbb000000000000000000000000000000000001";
const DEPOSIT_ASSET = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const CHAIN_ID = 1;
const AMOUNT = "1000000"; // 1 USDC (6 decimals)
async function main() {
const account = privateKeyToAccount(PRIVATE_KEY);
const walletClient = createWalletClient({
account,
chain: mainnet,
transport: http(),
});
const publicClient = createPublicClient({
chain: mainnet,
transport: http(),
});
// Step 1: Check authorization
console.log("Checking authorization method...");
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", String(CHAIN_ID));
const permitResp = await fetch(permitUrl, { headers: HEADERS }).then((r) =>
r.json()
);
console.log(`Authorization method: ${permitResp.method}`);
// Step 2: Build deposit request params
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", String(CHAIN_ID));
if (permitResp.method === "permit") {
const { domain, types, value } = permitResp.permitData;
const signature = await walletClient.signTypedData({
account,
domain,
types,
primaryType: "Permit",
message: value,
});
depositUrl.searchParams.set("permitSignature", signature);
depositUrl.searchParams.set(
"permitDeadline",
permitResp.permitData.deadline
);
} else if (permitResp.method === "approval") {
console.log("Sending approval transaction...");
const approvalHash = await walletClient.sendTransaction({
to: DEPOSIT_ASSET as `0x${string}`,
data: permitResp.approvalTransaction.encoded as `0x${string}`,
chain: mainnet,
account,
});
await publicClient.waitForTransactionReceipt({ hash: approvalHash });
console.log(`Approval confirmed: ${approvalHash}`);
}
// Step 3: Get deposit calldata
console.log("Fetching deposit calldata...");
const depositResp = await fetch(depositUrl, { headers: HEADERS }).then(
(r) => r.json()
);
const tx = depositResp.transaction;
// Step 4: Sign and submit
console.log("Submitting deposit transaction...");
const hash = await walletClient.sendTransaction({
to: tx.to as `0x${string}`,
data: tx.data as `0x${string}`,
value: BigInt(tx.value),
chain: mainnet,
account,
});
const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log(`Deposit confirmed in block ${receipt.blockNumber}: ${hash}`);
}
main().catch(console.error);
import os
import requests
from web3 import Web3
API_KEY = os.environ["AMPLIFY_API_KEY"]
PRIVATE_KEY = os.environ["PRIVATE_KEY"]
BASE = "https://api.paxoslabs.com"
HEADERS = {"x-api-key": API_KEY}
VAULT_ADDRESS = "0xbbbb000000000000000000000000000000000001"
DEPOSIT_ASSET = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
CHAIN_ID = 1
AMOUNT = "1000000" # 1 USDC (6 decimals)
w3 = Web3(Web3.HTTPProvider("https://eth.llamarpc.com"))
account = w3.eth.account.from_key(PRIVATE_KEY)
# Step 1: Check authorization
print("Checking authorization method...")
permit_resp = requests.get(f"{BASE}/v2/core/permit", headers=HEADERS, params={
"vaultAddress": VAULT_ADDRESS,
"tokenAddress": DEPOSIT_ASSET,
"amount": AMOUNT,
"userAddress": account.address,
"chainId": CHAIN_ID,
}).json()
print(f"Authorization method: {permit_resp['method']}")
# Step 2: Handle authorization
deposit_params = {
"vaultAddress": VAULT_ADDRESS,
"depositAsset": DEPOSIT_ASSET,
"depositAmount": AMOUNT,
"userAddress": account.address,
"chainId": CHAIN_ID,
}
if permit_resp["method"] == "permit":
sig = w3.eth.account.sign_typed_data(
PRIVATE_KEY,
permit_resp["permitData"]["domain"],
permit_resp["permitData"]["types"],
permit_resp["permitData"]["value"],
)
deposit_params["permitSignature"] = sig.signature.hex()
deposit_params["permitDeadline"] = permit_resp["permitData"]["deadline"]
elif permit_resp["method"] == "approval":
print("Sending approval transaction...")
approval_tx = {
"to": Web3.to_checksum_address(DEPOSIT_ASSET),
"data": permit_resp["approvalTransaction"]["encoded"],
"gas": 60_000,
"nonce": w3.eth.get_transaction_count(account.address),
"chainId": CHAIN_ID,
}
signed = account.sign_transaction(approval_tx)
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
w3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Approval confirmed: {tx_hash.hex()}")
# Step 3: Get deposit calldata
print("Fetching deposit calldata...")
deposit_resp = requests.get(
f"{BASE}/v2/amplify/deposit", headers=HEADERS, params=deposit_params
).json()
tx = deposit_resp["transaction"]
# Step 4: Sign and submit
print("Submitting deposit transaction...")
deposit_tx = {
"to": Web3.to_checksum_address(tx["to"]),
"data": tx["data"],
"value": int(tx["value"]),
"gas": 300_000,
"nonce": w3.eth.get_transaction_count(account.address),
"chainId": CHAIN_ID,
}
signed = account.sign_transaction(deposit_tx)
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Deposit confirmed in block {receipt['blockNumber']}: {tx_hash.hex()}")
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"math/big"
"net/http"
"net/url"
"os"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
)
const (
baseURL = "https://api.paxoslabs.com"
vaultAddress = "0xbbbb000000000000000000000000000000000001"
depositAsset = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
chainID = 1
amount = "1000000"
)
type Transaction struct {
To string `json:"to"`
Data string `json:"data"`
Value string `json:"value"`
}
func apiGet(path string, params url.Values) (map[string]interface{}, error) {
apiKey := os.Getenv("AMPLIFY_API_KEY")
u := fmt.Sprintf("%s%s?%s", baseURL, path, params.Encode())
req, _ := http.NewRequest("GET", u, nil)
req.Header.Set("x-api-key", apiKey)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var result map[string]interface{}
json.Unmarshal(body, &result)
return result, nil
}
func main() {
pk, _ := crypto.HexToECDSA(os.Getenv("PRIVATE_KEY"))
fromAddr := crypto.PubkeyToAddress(pk.PublicKey)
client, _ := ethclient.Dial("https://eth.llamarpc.com")
ctx := context.Background()
// Step 1: Check authorization
fmt.Println("Checking authorization method...")
permitResp, _ := apiGet("/v2/core/permit", url.Values{
"vaultAddress": {vaultAddress},
"tokenAddress": {depositAsset},
"amount": {amount},
"userAddress": {fromAddr.Hex()},
"chainId": {fmt.Sprint(chainID)},
})
method := permitResp["method"].(string)
fmt.Printf("Authorization method: %s\n", method)
// Step 2: Handle authorization
depositParams := url.Values{
"vaultAddress": {vaultAddress},
"depositAsset": {depositAsset},
"depositAmount": {amount},
"userAddress": {fromAddr.Hex()},
"chainId": {fmt.Sprint(chainID)},
}
if method == "approval" {
fmt.Println("Sending approval transaction...")
approvalTx := permitResp["approvalTransaction"].(map[string]interface{})
encoded := approvalTx["encoded"].(string)
nonce, _ := client.PendingNonceAt(ctx, fromAddr)
gasPrice, _ := client.SuggestGasPrice(ctx)
tx := types.NewTransaction(
nonce,
common.HexToAddress(depositAsset),
big.NewInt(0),
60000,
gasPrice,
common.FromHex(encoded),
)
signer := types.NewEIP155Signer(big.NewInt(chainID))
signedTx, _ := types.SignTx(tx, signer, pk)
client.SendTransaction(ctx, signedTx)
fmt.Printf("Approval submitted: %s\n", signedTx.Hash().Hex())
}
// For permit: sign EIP-712 typed data and add to depositParams
// Step 3: Get deposit calldata
fmt.Println("Fetching deposit calldata...")
depositResp, _ := apiGet("/v2/amplify/deposit", depositParams)
txData := depositResp["transaction"].(map[string]interface{})
// Step 4: Sign and submit
fmt.Println("Submitting deposit transaction...")
nonce, _ := client.PendingNonceAt(ctx, fromAddr)
gasPrice, _ := client.SuggestGasPrice(ctx)
depositTx := types.NewTransaction(
nonce,
common.HexToAddress(txData["to"].(string)),
big.NewInt(0),
300000,
gasPrice,
common.FromHex(txData["data"].(string)),
)
signedDepositTx, _ := types.SignTx(
depositTx, types.NewEIP155Signer(big.NewInt(chainID)), pk,
)
client.SendTransaction(ctx, signedDepositTx)
fmt.Printf("Deposit submitted: %s\n", signedDepositTx.Hash().Hex())
}
import org.web3j.crypto.Credentials;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.http.HttpService;
import org.web3j.utils.Numeric;
import java.math.BigInteger;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class AmplifyDeposit {
static final String BASE = "https://api.paxoslabs.com";
static final String API_KEY = System.getenv("AMPLIFY_API_KEY");
static final String VAULT_ADDRESS = "0xbbbb000000000000000000000000000000000001";
static final String DEPOSIT_ASSET = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
static final int CHAIN_ID = 1;
static final String AMOUNT = "1000000";
static HttpClient httpClient = HttpClient.newHttpClient();
static JsonObject apiGet(String path) throws Exception {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(BASE + path))
.header("x-api-key", API_KEY)
.GET().build();
HttpResponse<String> resp = httpClient.send(req,
HttpResponse.BodyHandlers.ofString());
return JsonParser.parseString(resp.body()).getAsJsonObject();
}
public static void main(String[] args) throws Exception {
Credentials credentials = Credentials.create(System.getenv("PRIVATE_KEY"));
String userAddress = credentials.getAddress();
Web3j web3 = Web3j.build(new HttpService("https://eth.llamarpc.com"));
// Step 1: Check authorization
System.out.println("Checking authorization method...");
String permitPath = String.format(
"/v2/core/permit?vaultAddress=%s&tokenAddress=%s&amount=%s&userAddress=%s&chainId=%d",
VAULT_ADDRESS, DEPOSIT_ASSET, AMOUNT, userAddress, CHAIN_ID);
JsonObject permitResp = apiGet(permitPath);
String method = permitResp.get("method").getAsString();
System.out.println("Authorization method: " + method);
// Step 2: Handle approval if needed
if ("approval".equals(method)) {
System.out.println("Sending approval transaction...");
String encoded = permitResp.getAsJsonObject("approvalTransaction")
.get("encoded").getAsString();
BigInteger nonce = web3.ethGetTransactionCount(
userAddress,
org.web3j.protocol.core.DefaultBlockParameterName.PENDING
).send().getTransactionCount();
BigInteger gasPrice = web3.ethGasPrice().send().getGasPrice();
RawTransaction approvalTx = RawTransaction.createTransaction(
nonce, gasPrice, BigInteger.valueOf(60000),
DEPOSIT_ASSET, BigInteger.ZERO, encoded);
byte[] signedApproval = TransactionEncoder.signMessage(
approvalTx, CHAIN_ID, credentials);
web3.ethSendRawTransaction(Numeric.toHexString(signedApproval))
.send();
System.out.println("Approval submitted");
}
// Step 3: Get deposit calldata
System.out.println("Fetching deposit calldata...");
String depositPath = String.format(
"/v2/amplify/deposit?vaultAddress=%s&depositAsset=%s" +
"&depositAmount=%s&userAddress=%s&chainId=%d",
VAULT_ADDRESS, DEPOSIT_ASSET, AMOUNT, userAddress, CHAIN_ID);
JsonObject depositResp = apiGet(depositPath);
JsonObject tx = depositResp.getAsJsonObject("transaction");
// Step 4: Sign and submit
System.out.println("Submitting deposit transaction...");
BigInteger nonce = web3.ethGetTransactionCount(
userAddress,
org.web3j.protocol.core.DefaultBlockParameterName.PENDING
).send().getTransactionCount();
BigInteger gasPrice = web3.ethGasPrice().send().getGasPrice();
RawTransaction depositTx = RawTransaction.createTransaction(
nonce, gasPrice, BigInteger.valueOf(300000),
tx.get("to").getAsString(), BigInteger.ZERO,
tx.get("data").getAsString());
byte[] signedDeposit = TransactionEncoder.signMessage(
depositTx, CHAIN_ID, credentials);
String txHash = web3.ethSendRawTransaction(
Numeric.toHexString(signedDeposit)).send().getTransactionHash();
System.out.println("Deposit submitted: " + txHash);
}
}
Error Responses
| Status | Meaning |
|---|---|
| 400 | Invalid parameters (missing field, bad address format, invalid permit signature, deposit amount below fees, etc.) |
| 404 | No account found for the given vaultAddress + chainId |
| 503 | Upstream RPC unavailable (share rate, fee module, or supply cap read failed) |