Overview
This guide teaches you how to build your own liquidity provider service from scratch. You’ll learn the complete technical flow for:- Polling for quote requests from users
- Calculating and submitting competitive quotes
- Monitoring for quote acceptance
- Executing the on-chain order fulfillment
- Settling cross-chain via LayerZero
- Withdrawing your earned fees
This is a technical implementation guide. You should be comfortable with TypeScript, blockchain transactions, and smart contract interactions.
Prerequisites
- Blockchain development experience: Understanding of transactions, gas, signatures
- TypeScript/JavaScript knowledge
- Web3 library: Viem, ethers.js, or web3.js
- Funded wallet with gas tokens and stablecoins on multiple chains
- API key from Lidian Developer Portal
Core Concepts
The LP Business Model
As an LP, you profit from the spread between input and output amounts:Copy
User sends: 1000 USDC on Base Sepolia
LP quotes: 998.50 USDC on Polygon Amoy (15 bps fee = 1.50 USDC)
LP provides: 998.50 USDC from their liquidity
LP receives: 1000 USDC + 1.50 USDC fee (after settlement)
Net profit: 1.50 USDC (minus gas costs ~$0.10)
The Order Lifecycle
Copy
1. QUOTE REQUEST → LP receives user's swap intent
2. QUOTE CREATION → LP calculates output amount with fee
3. QUOTE ACCEPTANCE → User signs order with EIP-712
4. DEPOSIT → LP relays signed order to source chain contract
5. FILL → LP provides liquidity on destination chain
6. SETTLEMENT → LP initiates LayerZero message back to source
7. WITHDRAWAL → LP claims input tokens + earned fee
Step 1: Poll for Quote Requests
Fetch Quote Requests from API
Copy
interface QuoteRequest {
id: string;
inputChain: string; // e.g., "base-sepolia"
inputToken: string; // e.g., "0x036CbD..."
inputAmount: string; // e.g., "1000000" (1 USDC with 6 decimals)
outputChain: string; // e.g., "polygon-amoy"
outputToken: string; // e.g., "0x41E94..."
offerer: string; // User's wallet address
recipient: string; // Recipient's wallet address
startTime: number;
endTime: number;
}
async function pollForQuoteRequests() {
const response = await fetch('https://api.lidian.finance/api/v1/quote-requests', {
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.LIDIAN_API_KEY
}
});
const { data } = await response.json();
return data; // Array of QuoteRequest[]
}
Continuous Polling Loop
Copy
const processedRequests = new Set<string>();
async function monitorQuoteRequests() {
while (true) {
try {
const requests = await pollForQuoteRequests();
for (const request of requests) {
if (processedRequests.has(request.id)) continue;
console.log(`New quote request: ${request.id}`);
await handleQuoteRequest(request);
processedRequests.add(request.id);
}
} catch (error) {
console.error('Polling error:', error);
}
await sleep(2000); // Poll every 2 seconds
}
}
Step 2: Calculate Quote with Fee
Fee Structure
Different tokens have different basis point fees:Copy
const TOKEN_FEES = {
'base-sepolia': {
'0x036CbD53842c5426634e7929541eC2318f3dCF7e': 15, // USDC: 15 bps
'0x66F5018cdb5d6Eb0a3d1AC57F8b77243eb0010fF': 5, // USDT: 5 bps
},
'polygon-amoy': {
'0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582': 3, // USDC: 3 bps
'0x86f6D9931BE92Ad61572e80c0F5FAe45950D4cA1': 3, // USDT: 3 bps
}
};
Calculate Output Amount
Copy
function calculateOutputAmount(
inputAmount: bigint,
inputDecimals: number,
outputDecimals: number,
feeBasisPoints: number
): bigint {
// For stablecoins (1:1 ratio), calculate fee deduction
// feeBasisPoints: 15 = 0.15% = 0.0015
const FEE_DENOMINATOR = 10000n;
const feeBps = BigInt(feeBasisPoints);
// Calculate output = input * (1 - fee)
// output = input * (10000 - feeBps) / 10000
const outputAmount = (inputAmount * (FEE_DENOMINATOR - feeBps)) / FEE_DENOMINATOR;
// Adjust for decimal differences if needed
if (inputDecimals !== outputDecimals) {
const decimalDiff = outputDecimals - inputDecimals;
if (decimalDiff > 0) {
return outputAmount * (10n ** BigInt(decimalDiff));
} else {
return outputAmount / (10n ** BigInt(-decimalDiff));
}
}
return outputAmount;
}
// Example:
// Input: 1,000,000 (1 USDC with 6 decimals)
// Fee: 15 bps
// Output: 1,000,000 * (10000 - 15) / 10000 = 998,500 (0.9985 USDC)
// LP earns: 1,500 (0.0015 USDC = $1.50 on $1000 swap)
Check Liquidity Availability
Copy
import { createPublicClient, http, parseUnits } from 'viem';
import { polygonAmoy } from 'viem/chains';
async function checkLiquidity(
chain: typeof polygonAmoy,
tokenAddress: string,
requiredAmount: bigint,
lpWallet: string
): Promise<boolean> {
const client = createPublicClient({
chain,
transport: http()
});
const balance = await client.readContract({
address: tokenAddress,
abi: [{
name: 'balanceOf',
type: 'function',
stateMutability: 'view',
inputs: [{ name: 'account', type: 'address' }],
outputs: [{ type: 'uint256' }]
}],
functionName: 'balanceOf',
args: [lpWallet]
});
return balance >= requiredAmount;
}
Step 3: Submit Quote to API
Create Quote Object
Copy
async function submitQuote(
quoteRequestId: string,
outputAmount: string
): Promise<string> {
const response = await fetch('https://api.lidian.finance/api/v1/quotes', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.LIDIAN_API_KEY
},
body: JSON.stringify({
quoteRequestId,
outputAmount,
liquidityProviderId: process.env.LP_WALLET_ADDRESS
})
});
const { data } = await response.json();
return data.id; // Returns quote ID (e.g., "quotes/abc123")
}
Handle Quote Request (Complete Flow)
Copy
async function handleQuoteRequest(request: QuoteRequest) {
// 1. Get token fee
const inputFee = TOKEN_FEES[request.inputChain]?.[request.inputToken] || 0;
const outputFee = TOKEN_FEES[request.outputChain]?.[request.outputToken] || 0;
const maxFee = Math.max(inputFee, outputFee);
// 2. Calculate output amount
const inputAmount = BigInt(request.inputAmount);
const outputAmount = calculateOutputAmount(
inputAmount,
6, // USDC decimals
6, // USDC decimals
maxFee
);
// 3. Check if we have liquidity
const hasLiquidity = await checkLiquidity(
getChain(request.outputChain),
request.outputToken,
outputAmount,
process.env.LP_WALLET_ADDRESS
);
if (!hasLiquidity) {
console.log('Insufficient liquidity, skipping quote');
return;
}
// 4. Submit quote
const quoteId = await submitQuote(request.id, outputAmount.toString());
console.log(`Quote submitted: ${quoteId} - Output: ${outputAmount.toString()}`);
// 5. Start monitoring for acceptance
monitorQuoteAcceptance(quoteId, request);
}
Step 4: Monitor for Quote Acceptance
Poll for Order Creation
When a user accepts your quote, they sign the order and create it in the API:Copy
async function monitorQuoteAcceptance(
quoteId: string,
originalRequest: QuoteRequest
) {
const timeout = Date.now() + 300000; // 5 minute timeout
while (Date.now() < timeout) {
try {
// Fetch the quote to see if it was accepted
const response = await fetch(
`https://api.lidian.finance/api/v1/quotes/${quoteId}`,
{
headers: { 'x-api-key': process.env.LIDIAN_API_KEY }
}
);
const { data: quote } = await response.json();
// Check if order was created
if (quote.orderId) {
console.log(`Quote accepted! Order ID: ${quote.orderId}`);
await processOrder(quote.orderId, originalRequest, quote.outputAmount);
return;
}
// Check if quote expired/cancelled
if (quote.status === 'expired' || quote.status === 'cancelled') {
console.log(`Quote ${quoteId} ${quote.status}`);
return;
}
} catch (error) {
console.error('Error monitoring quote:', error);
}
await sleep(2000); // Poll every 2 seconds
}
console.log(`Quote monitoring timeout: ${quoteId}`);
}
Fetch Order Details
Copy
interface Order {
id: string;
signature: string; // EIP-712 signature from user
quoteId: string;
status: string;
moneyMoverAccountId: string; // User's wallet
liquidityProviderAccountId: string; // Your wallet
}
async function fetchOrder(orderId: string): Promise<Order> {
const response = await fetch(
`https://api.lidian.finance/api/v1/orders/${orderId}`,
{
headers: { 'x-api-key': process.env.LIDIAN_API_KEY }
}
);
const { data } = await response.json();
return data;
}
Step 5: Execute Deposit on Source Chain
Build Order Struct for Contract
Copy
import { encodeFunctionData, keccak256, encodePacked } from 'viem';
// Helper: Calculate chain ID hash
function getChainHash(chainKey: string): `0x${string}` {
return keccak256(encodePacked(['string'], [chainKey]));
}
interface OrderStruct {
inputAmount: bigint;
outputAmount: bigint;
inputToken: `0x${string}`;
outputToken: `0x${string}`;
startTime: number;
endTime: number;
srcChainId: `0x${string}`;
dstChainId: `0x${string}`;
offerer: `0x${string}`;
recipient: `0x${string}`;
}
Call deposit() on Lidian Contract
Copy
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { baseSepolia } from 'viem/chains';
const LIDIAN_CONTRACT = '0xc2c00E85ab6bcf865986DE84053a6169bCBfa70F'; // Base Sepolia
const LIDIAN_ABI = [{
name: 'deposit',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{
name: 'order',
type: 'tuple',
components: [
{ name: 'inputAmount', type: 'uint128' },
{ name: 'outputAmount', type: 'uint128' },
{ name: 'inputToken', type: 'address' },
{ name: 'outputToken', type: 'address' },
{ name: 'startTime', type: 'uint32' },
{ name: 'endTime', type: 'uint32' },
{ name: 'srcChainId', type: 'bytes32' },
{ name: 'dstChainId', type: 'bytes32' },
{ name: 'offerer', type: 'address' },
{ name: 'recipient', type: 'address' }
]
},
{ name: 'signature', type: 'bytes' }
],
outputs: []
}];
async function executeDeposit(
order: OrderStruct,
signature: `0x${string}`
) {
const account = privateKeyToAccount(process.env.LP_PRIVATE_KEY as `0x${string}`);
const client = createWalletClient({
account,
chain: baseSepolia,
transport: http()
});
console.log('Executing deposit on source chain...');
const hash = await client.writeContract({
address: LIDIAN_CONTRACT,
abi: LIDIAN_ABI,
functionName: 'deposit',
args: [order, signature]
});
console.log(`Deposit tx: ${hash}`);
// Wait for confirmation
const receipt = await client.waitForTransactionReceipt({ hash });
console.log(`Deposit confirmed at block ${receipt.blockNumber}`);
return receipt;
}
Important: The deposit transaction uses the user’s signature to pull tokens from their wallet. The LP does not need to hold the input tokens.
Step 6: Execute Fill on Destination Chain
Approve Token Spending (if needed)
Copy
const ERC20_ABI = [{
name: 'approve',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'amount', type: 'uint256' }
],
outputs: [{ type: 'bool' }]
}];
async function approveTokenIfNeeded(
tokenAddress: `0x${string}`,
spenderAddress: `0x${string}`,
amount: bigint
) {
const account = privateKeyToAccount(process.env.LP_PRIVATE_KEY as `0x${string}`);
const client = createWalletClient({
account,
chain: polygonAmoy,
transport: http()
});
// Check current allowance
const publicClient = createPublicClient({
chain: polygonAmoy,
transport: http()
});
const allowance = await publicClient.readContract({
address: tokenAddress,
abi: [{
name: 'allowance',
type: 'function',
stateMutability: 'view',
inputs: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' }
],
outputs: [{ type: 'uint256' }]
}],
functionName: 'allowance',
args: [account.address, spenderAddress]
});
if (allowance >= amount) {
console.log('Sufficient allowance already exists');
return;
}
// Approve tokens
console.log('Approving tokens...');
const hash = await client.writeContract({
address: tokenAddress,
abi: ERC20_ABI,
functionName: 'approve',
args: [spenderAddress, amount]
});
await client.waitForTransactionReceipt({ hash });
console.log('Approval confirmed');
}
Call fill() on Destination Chain
Copy
const LIDIAN_CONTRACT_AMOY = '0x7bE37DE6d81C6B231c8579901C6717f1A08c41ed'; // Polygon Amoy
async function executeFill(order: OrderStruct) {
const account = privateKeyToAccount(process.env.LP_PRIVATE_KEY as `0x${string}`);
const client = createWalletClient({
account,
chain: polygonAmoy,
transport: http()
});
// 1. Approve Lidian contract to spend output tokens
await approveTokenIfNeeded(
order.outputToken,
LIDIAN_CONTRACT_AMOY,
order.outputAmount
);
// 2. Call fill()
console.log('Executing fill on destination chain...');
const hash = await client.writeContract({
address: LIDIAN_CONTRACT_AMOY,
abi: [{
name: 'fill',
type: 'function',
stateMutability: 'payable',
inputs: [{
name: 'order',
type: 'tuple',
components: [
{ name: 'inputAmount', type: 'uint128' },
{ name: 'outputAmount', type: 'uint128' },
{ name: 'inputToken', type: 'address' },
{ name: 'outputToken', type: 'address' },
{ name: 'startTime', type: 'uint32' },
{ name: 'endTime', type: 'uint32' },
{ name: 'srcChainId', type: 'bytes32' },
{ name: 'dstChainId', type: 'bytes32' },
{ name: 'offerer', type: 'address' },
{ name: 'recipient', type: 'address' }
]
}],
outputs: []
}],
functionName: 'fill',
args: [order]
});
console.log(`Fill tx: ${hash}`);
const receipt = await client.waitForTransactionReceipt({ hash });
console.log(`Fill confirmed at block ${receipt.blockNumber}`);
return receipt;
}
After fill, the LP’s output tokens are locked in the destination chain contract. You must complete settlement to unlock your funds on the source chain.
Step 7: Settle via LayerZero
Quote LayerZero Fee
Copy
const LZ_ADAPTER_AMOY = '0x9ecf258aA68a483ac89057cBB9deAD71D6a7a3E0';
const ADAPTER_ABI = [{
name: 'quote',
type: 'function',
stateMutability: 'view',
inputs: [
{ name: 'dstChainId', type: 'bytes32' },
{ name: 'msgType', type: 'uint8' },
{ name: '_options', type: 'bytes' },
{ name: 'payInLzToken', type: 'bool' },
{ name: 'srcChainId', type: 'bytes32' },
{ name: 'sender', type: 'address' }
],
outputs: [{ name: 'nativeFee', type: 'uint256' }]
}];
async function quoteLzFee(
srcChainHash: `0x${string}`,
dstChainHash: `0x${string}`,
lpAddress: `0x${string}`
): Promise<bigint> {
const client = createPublicClient({
chain: polygonAmoy,
transport: http()
});
// LayerZero options: 300k gas for lzReceive
const options = '0x00030100110100000000000000000000000000030d40'; // 300k gas
const fee = await client.readContract({
address: LZ_ADAPTER_AMOY,
abi: ADAPTER_ABI,
functionName: 'quote',
args: [
dstChainHash, // destination (source chain)
0, // msgType = settlement
options,
false, // don't pay in LZ token
srcChainHash, // source chain
lpAddress // filler address
]
});
return fee as bigint;
}
Execute Settlement
Copy
async function executeSettlement(
srcChainHash: `0x${string}`,
dstChainHash: `0x${string}`
) {
const account = privateKeyToAccount(process.env.LP_PRIVATE_KEY as `0x${string}`);
const client = createWalletClient({
account,
chain: polygonAmoy,
transport: http()
});
// 1. Quote the LayerZero fee
const lzFee = await quoteLzFee(srcChainHash, dstChainHash, account.address);
console.log(`LayerZero fee: ${lzFee.toString()} wei`);
// 2. Call settle() with LZ fee as msg.value
console.log('Executing settlement...');
const options = '0x00030100110100000000000000000000000000030d40';
const hash = await client.writeContract({
address: LZ_ADAPTER_AMOY,
abi: [{
name: 'settle',
type: 'function',
stateMutability: 'payable',
inputs: [
{ name: 'filler', type: 'address' },
{ name: 'dstChainId', type: 'bytes32' },
{ name: '_options', type: 'bytes' }
],
outputs: []
}],
functionName: 'settle',
args: [account.address, dstChainHash, options],
value: lzFee
});
console.log(`Settlement tx: ${hash}`);
const receipt = await client.waitForTransactionReceipt({ hash });
console.log(`Settlement confirmed at block ${receipt.blockNumber}`);
return receipt;
}
Settlement triggers a LayerZero cross-chain message. It takes ~30-60 seconds for the message to be delivered and verified on the source chain.
Step 8: Withdraw Funds + Fee
Wait for Settlement Delivery
Copy
async function waitForSettlement(orderId: string): Promise<boolean> {
const timeout = Date.now() + 120000; // 2 minute timeout
while (Date.now() < timeout) {
try {
// Check if settlement message was delivered
// You can track this via LayerZero scan or contract events
const settled = await checkIfSettled(orderId);
if (settled) {
console.log('Settlement message delivered');
return true;
}
} catch (error) {
console.error('Error checking settlement:', error);
}
await sleep(5000); // Check every 5 seconds
}
console.log('Settlement timeout');
return false;
}
Execute Withdrawal
Copy
async function executeWithdrawal(
inputToken: `0x${string}`,
amount: bigint
) {
const account = privateKeyToAccount(process.env.LP_PRIVATE_KEY as `0x${string}`);
const client = createWalletClient({
account,
chain: baseSepolia,
transport: http()
});
console.log('Executing withdrawal...');
console.log(`Amount: ${amount.toString()}`);
const hash = await client.writeContract({
address: LIDIAN_CONTRACT,
abi: [{
name: 'withdraw',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'token', type: 'address' },
{ name: 'amount', type: 'uint256' }
],
outputs: []
}],
functionName: 'withdraw',
args: [inputToken, amount]
});
console.log(`Withdrawal tx: ${hash}`);
const receipt = await client.waitForTransactionReceipt({ hash });
console.log(`Withdrawal confirmed at block ${receipt.blockNumber}`);
console.log('✅ Order complete! Fees earned.');
return receipt;
}
Complete Implementation Example
Copy
async function processOrder(
orderId: string,
quoteRequest: QuoteRequest,
outputAmount: string
) {
try {
// 1. Fetch order with signature
const order = await fetchOrder(orderId);
// 2. Build order struct
const orderStruct: OrderStruct = {
inputAmount: BigInt(quoteRequest.inputAmount),
outputAmount: BigInt(outputAmount),
inputToken: quoteRequest.inputToken as `0x${string}`,
outputToken: quoteRequest.outputToken as `0x${string}`,
startTime: quoteRequest.startTime,
endTime: quoteRequest.endTime,
srcChainId: getChainHash(quoteRequest.inputChain),
dstChainId: getChainHash(quoteRequest.outputChain),
offerer: quoteRequest.offerer as `0x${string}`,
recipient: quoteRequest.recipient as `0x${string}`
};
// 3. Deposit on source chain
await executeDeposit(orderStruct, order.signature as `0x${string}`);
// 4. Fill on destination chain
await executeFill(orderStruct);
// 5. Settle back to source chain
await executeSettlement(
orderStruct.srcChainId,
orderStruct.dstChainId
);
// 6. Wait for settlement delivery
const settled = await waitForSettlement(orderId);
if (!settled) {
throw new Error('Settlement timeout');
}
// 7. Withdraw funds + fee
const withdrawAmount = orderStruct.inputAmount; // This includes your fee
await executeWithdrawal(orderStruct.inputToken, withdrawAmount);
console.log(`✅ Order ${orderId} completed successfully!`);
} catch (error) {
console.error(`❌ Order processing failed:`, error);
throw error;
}
}
Profit Calculation
After completing an order, calculate your earnings:Copy
function calculateProfit(
inputAmount: bigint,
outputAmount: bigint,
gasUsed: {
deposit: bigint;
fill: bigint;
settle: bigint;
withdraw: bigint;
},
gasPrice: bigint
): bigint {
// Fee earned (difference between input and output)
const feeEarned = inputAmount - outputAmount;
// Total gas cost
const totalGas = gasUsed.deposit + gasUsed.fill + gasUsed.settle + gasUsed.withdraw;
const gasCost = totalGas * gasPrice;
// Net profit (in wei)
const netProfit = feeEarned - gasCost;
return netProfit;
}
// Example:
// Input: 1,000 USDC (1,000,000,000 with 6 decimals)
// Output: 998.5 USDC (998,500,000 with 6 decimals)
// Fee earned: 1.5 USDC (1,500,000)
// Gas cost: ~$0.10 (100,000 wei assuming USDC = ETH for simplicity)
// Net profit: 1.4 USDC ($1.40)
Production Considerations
Error Handling
Copy
async function processOrderWithRetry(
orderId: string,
maxRetries: number = 3
) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
await processOrder(orderId, quoteRequest, outputAmount);
return; // Success
} catch (error) {
console.error(`Attempt ${attempt} failed:`, error);
if (attempt === maxRetries) {
// Final failure - alert admin
await sendAlert(`Order ${orderId} failed after ${maxRetries} attempts`);
throw error;
}
// Wait before retry (exponential backoff)
await sleep(Math.pow(2, attempt) * 1000);
}
}
}
Balance Monitoring
Copy
async function checkBalances(): Promise<void> {
const chains = ['base-sepolia', 'polygon-amoy'];
for (const chain of chains) {
const balance = await getTokenBalance(
chain,
USDC_ADDRESS,
LP_WALLET
);
if (balance < MIN_BALANCE_THRESHOLD) {
await sendAlert(`Low balance on ${chain}: ${balance}`);
}
}
}
// Run every 5 minutes
setInterval(checkBalances, 5 * 60 * 1000);
Nonce Management
Copy
class NonceManager {
private nonces = new Map<string, number>();
async getNonce(address: string, chainId: number): Promise<number> {
const key = `${address}-${chainId}`;
if (!this.nonces.has(key)) {
const nonce = await fetchOnChainNonce(address, chainId);
this.nonces.set(key, nonce);
}
return this.nonces.get(key)!;
}
incrementNonce(address: string, chainId: number): void {
const key = `${address}-${chainId}`;
const current = this.nonces.get(key) || 0;
this.nonces.set(key, current + 1);
}
}
Testing Your Implementation
Testnet Setup
-
Get testnet tokens from faucets:
- Base Sepolia: https://www.coinbase.com/faucets
- Polygon Amoy: https://faucet.polygon.technology/
-
Fund your LP wallet with:
- Gas tokens (ETH, MATIC)
- Stablecoins (USDC, USDT)
-
Test with small amounts first:
- Start with $1-10 swaps
- Verify all transactions succeed
- Check balance updates
Debug Logging
Copy
function logOrderProgress(step: string, data: any) {
console.log(`[${new Date().toISOString()}] ${step}`, {
orderId: data.orderId,
chain: data.chain,
txHash: data.txHash,
gasUsed: data.gasUsed,
...data
});
}
Contract Addresses
Testnet Contracts
| Network | Lidian Contract | LayerZero Adapter |
|---|---|---|
| Base Sepolia | 0xc2c00E85ab6bcf865986DE84053a6169bCBfa70F | 0xe6dF658A8f53D47fec5294F351Adf11111C6fa01 |
| Polygon Amoy | 0x7bE37DE6d81C6B231c8579901C6717f1A08c41ed | 0x9ecf258aA68a483ac89057cBB9deAD71D6a7a3E0 |
| Avalanche Fuji | 0x875200c20719eB6914405c5C989226F97418e928 | 0xDfaF9D6f669625eb63C98F3A34d03eC06ea2A073 |
Next Steps
Now that you understand the implementation:- Build your service using this guide as a reference
- Test thoroughly on testnets before mainnet
- Monitor performance and optimize for your use case
- Scale gradually as you gain confidence
For production API keys and support, contact the Lidian team.
Additional Resources
- API Reference - All API endpoints
- Smart Contracts - Contract architecture
- WebSocket API - Real-time updates
- EIP-712 Signing - Signature format

