Developer Quickstart
Execute Your First Swap in 5 Minutes
This guide walks you through executing a swap of 100 USDC to EURC on the Arc network using the Tower Developer REST APIs.
The 3-Step Swap Flow
mermaidsequenceDiagram autonumber actor Dev as Your Application / Wallet participant Tower as Tower Public API participant Arc as Arc Blockchain Node Dev->>Tower: POST /api/public/swap/quote (inputToken, outputToken, amount) Tower-->>Dev: Returns Optimal Quote & Route (dexName, outputAmount, minOut) Dev->>Tower: POST /api/public/swap/build-tx (quote, userAddress) Tower-->>Dev: Returns Unsigned Calldata (approval payload, swap payload) Dev->>Dev: Signs Unsigned Calldata locally with Private Key Dev->>Arc: Broadcasts Signed Transaction via /api/public/rpc/{chainId} Arc-->>Dev: Transaction Confirmed on-chain
Step 1: Request a Swap Quote
Send a POST request to /api/public/swap/quote with the input asset, output asset, input amount, and slippage tolerance.
Atomic Base Units: Token amounts must be supplied in base atomic integer units. Because USDC uses 6 decimals on Arc, 100 USDC equals 100000000 (i.e. $100 \times 10^6$).
Step 2: Build the Swap Transaction Payload
Take the quote object returned in Step 1 and pass it alongside your wallet address to POST /api/public/swap/build-tx.
Tower constructs the exact ABI-encoded transaction calldata for:
- ERC-20 Approval (
data.approval): Required if your wallet has not yet approved theTowerSwapExecutorcontract to spend the input token. If already approved, this field isnull. - Swap Execution (
data.swap): The atomic swap transaction targeting theTowerSwapExecutorcontract (to,data,value,gasLimit,chainId).
Step 3: Sign and Broadcast the Transaction
Your application signs the transaction payload using your private key or Web3 provider (such as Ethers.js, Viem, or RainbowKit) and broadcasts it to the Arc network through the RPC Proxy /api/public/rpc/5042002 or directly to an Arc RPC node.
Complete Code Samples
cURL
bash# Step 1: Request Quote for 100 USDC to EURC
curl -X POST "https://tower-devapi.up.railway.app/api/public/swap/quote" \
-H "Authorization: Bearer sk_live_********************" \
-H "Content-Type: application/json" \
-d '{
"inputToken": "USDC",
"outputToken": "EURC",
"inputAmount": "100000000",
"slippageTolerance": 50
}'
# Step 2: Build the unsigned transaction payload
curl -X POST "https://tower-devapi.up.railway.app/api/public/swap/build-tx" \
-H "Authorization: Bearer sk_live_********************" \
-H "Content-Type: application/json" \
-d '{
"quote": {
"inputToken": "0x3600000000000000000000000000000000000000",
"outputToken": "0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a",
"inputAmount": "100000000",
"outputAmount": "92450000000000000000",
"minOut": "91987750000000000000",
"priceImpact": 0.02,
"feeBps": 25,
"dexId": "tower-dex"
},
"userAddress": "0xa54FFd258815Ee711bA0d3Dbb7fA786AEA6095Fb"
}'
# Step 3: Broadcast signed raw transaction via RPC Proxy
curl -X POST "https://tower-devapi.up.railway.app/api/public/rpc/5042002" \
-H "Authorization: Bearer sk_live_********************" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_sendRawTransaction",
"params": ["0x02f873018203e8843b9aca008502540be400825208942de8906a641d65d490bc60a4179d961d59742bcb8084cd6267d5c080a0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f01234a056789abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345"],
"id": 1
}'
JavaScript / TypeScript (Fetch)
javascriptconst API_KEY = "sk_live_********************";
const BASE_URL = "https://tower-devapi.up.railway.app/api/public";
const WALLET_ADDRESS = "0xa54FFd258815Ee711bA0d3Dbb7fA786AEA6095Fb";
async function executeSwap() {
const headers = {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json"
};
// Step 1: Request a swap quote for 100 USDC to EURC
const quoteResponse = await fetch(`${BASE_URL}/swap/quote`, {
method: "POST",
headers: headers,
body: JSON.stringify({
inputToken: "USDC",
outputToken: "EURC",
inputAmount: "100000000",
slippageTolerance: 50
})
});
const quoteResult = await quoteResponse.json();
if (!quoteResult.success) {
throw new Error(`Quote failed: ${quoteResult.error}`);
}
const quote = quoteResult.data;
console.log("Expected output:", quote.outputAmount, "units on", quote.dexName);
// Step 2: Build the unsigned transaction payload
const buildTxResponse = await fetch(`${BASE_URL}/swap/build-tx`, {
method: "POST",
headers: headers,
body: JSON.stringify({
quote: quote,
userAddress: WALLET_ADDRESS
})
});
const txResult = await buildTxResponse.json();
if (!txResult.success) {
throw new Error(`Build transaction failed: ${txResult.error}`);
}
console.log("Approval payload required:", txResult.data.approval !== null);
console.log("Swap target contract:", txResult.data.swap.to);
console.log("Swap execution calldata:", txResult.data.swap.data);
return txResult.data;
}
executeSwap().catch(console.error);
Python (Requests)
pythonimport requests
API_KEY = "sk_live_********************"
BASE_URL = "https://tower-devapi.up.railway.app/api/public"
WALLET_ADDRESS = "0xa54FFd258815Ee711bA0d3Dbb7fA786AEA6095Fb"
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
# Step 1: Request a swap quote for 100 USDC to EURC
quote_payload = {
"inputToken": "USDC",
"outputToken": "EURC",
"inputAmount": "100000000",
"slippageTolerance": 50
}
quote_response = requests.post(f"{BASE_URL}/swap/quote", headers=headers, json=quote_payload)
quote_data = quote_response.json()
if not quote_data.get("success"):
raise Exception(f"Quote failed: {quote_data.get('error')}")
quote = quote_data["data"]
print(f"Optimal DEX: {quote['dexName']}, Expected Output: {quote['outputAmount']}")
# Step 2: Build unsigned transaction payload
build_payload = {
"quote": quote,
"userAddress": WALLET_ADDRESS
}
build_response = requests.post(f"{BASE_URL}/swap/build-tx", headers=headers, json=build_payload)
tx_data = build_response.json()
if not tx_data.get("success"):
raise Exception(f"Build TX failed: {tx_data.get('error')}")
swap_tx = tx_data["data"]["swap"]
print(f"Target Contract: {swap_tx['to']}")
print(f"Calldata: {swap_tx['data']}")
Expected Quote Response Shape
Here is an annotated breakdown of the payload returned by POST /api/public/swap/quote:
json{
"success": true,
"data": {
"inputToken": "0x3600000000000000000000000000000000000000", // Smart contract address of input token (USDC)
"outputToken": "0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a", // Smart contract address of output token (EURC)
"inputAmount": "100000000", // Atomic base units spent (100 USDC with 6 decimals)
"outputAmount": "92450000000000000000", // Expected output in 18-decimal base units (~92.45 EURC)
"minOut": "91987750000000000000", // Minimum guaranteed payout at 50 bps (0.5%) slippage
"priceImpact": 0.02, // Estimated percentage price movement relative to pool depth
"gasEstimate": "200000", // Gas units estimated for on-chain execution
"feeBps": 25, // Total liquidity provider and protocol fee in basis points (0.25%)
"platformFeeAmount": "250000000000000", // Protocol fee component in atomic base units
"dexId": "tower-dex", // Unique identifier of the DEX offering the best price
"dexName": "Tower", // Display name of the optimal DEX router
"route": {
"type": "single", // Routing topology (single hop, multi hop, or split)
"hops": [
{
"dexId": "tower-dex",
"amountIn": "100000000",
"amountOut": "92450000000000000000",
"priceImpact": 0.02
}
]
},
"routeOptions": [] // Ranked alternative routes across competing DEXes
}
}