End-to-end curl workflows for common ISCL operations. Each recipe shows the complete sequence of API calls from intent to confirmation.
Base URL: http://localhost:3100Prerequisites: ISCL Core running with at least one wallet imported and RPC configured.
Recipe 1: ERC-20 Token Transfer (Full Pipeline)
Transfer 100 USDC on Base chain.
Check the sender's balance
curl -s http://localhost:3100/v1/balance/0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913/0xYourWalletAddress?chainId= 8453 | jq
Request approval (build + preflight + approve)
curl -s -X POST http://localhost:3100/v1/tx/approve-request \
-H "Content-Type: application/json" \
-d '{
"version": "1",
"id": "550e8400-e29b-41d4-a716-446655440001",
"timestamp": 1700000000000,
"chain": { "type": "evm", "chainId": 8453 },
"wallet": { "address": "0xYourWalletAddress" },
"action": {
"type": "transfer",
"asset": {
"kind": "erc20",
"address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"symbol": "USDC",
"decimals": 6
},
"to": "0xRecipientAddress",
"amount": "100000000"
},
"constraints": {
"maxGasWei": "1000000000000000",
"deadline": 1700003600,
"maxSlippageBps": 0
},
"metadata": { "source": "curl-cookbook", "note": "Send 100 USDC" }
}' | jq
Sign and broadcast
curl -s -X POST http://localhost:3100/v1/tx/sign-and-send \
-H "Content-Type: application/json" \
-d '{
"intent": {
"version": "1",
"id": "550e8400-e29b-41d4-a716-446655440001",
"timestamp": 1700000000000,
"chain": { "type": "evm", "chainId": 8453 },
"wallet": { "address": "0xYourWalletAddress" },
"action": {
"type": "transfer",
"asset": {
"kind": "erc20",
"address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"symbol": "USDC",
"decimals": 6
},
"to": "0xRecipientAddress",
"amount": "100000000"
},
"constraints": {
"maxGasWei": "1000000000000000",
"deadline": 1700003600,
"maxSlippageBps": 0
},
"metadata": { "source": "curl-cookbook", "note": "Send 100 USDC" }
},
"approvalTokenId": "tok_abc123..."
}' | jq
Check the transaction receipt
curl -s http://localhost:3100/v1/tx/0xdef456... | jq
Recipe 2: Native ETH Transfer
Send 0.01 ETH on Base. This script extracts the approval token automatically and pipes it into the sign-and-send call.
Approve the transfer
RESPONSE = $( curl -s -X POST http://localhost:3100/v1/tx/approve-request \
-H "Content-Type: application/json" \
-d '{
"version": "1",
"id": "660e8400-e29b-41d4-a716-446655440002",
"timestamp": 1700000000000,
"chain": { "type": "evm", "chainId": 8453 },
"wallet": { "address": "0xYourWalletAddress" },
"action": {
"type": "transfer_native",
"to": "0xRecipientAddress",
"amount": "10000000000000000"
},
"constraints": {
"maxGasWei": "1000000000000000",
"deadline": 1700003600,
"maxSlippageBps": 0
},
"metadata": { "source": "curl-cookbook" }
}' )
echo " $RESPONSE " | jq
# Extract approval token
TOKEN = $( echo " $RESPONSE " | jq -r '.approvalTokenId' )
Sign and send using the extracted token
curl -s -X POST http://localhost:3100/v1/tx/sign-and-send \
-H "Content-Type: application/json" \
-d "{
\" intent \" : {
\" version \" : \" 1 \" ,
\" id \" : \" 660e8400-e29b-41d4-a716-446655440002 \" ,
\" timestamp \" : 1700000000000,
\" chain \" : { \" type \" : \" evm \" , \" chainId \" : 8453 },
\" wallet \" : { \" address \" : \" 0xYourWalletAddress \" },
\" action \" : {
\" type \" : \" transfer_native \" ,
\" to \" : \" 0xRecipientAddress \" ,
\" amount \" : \" 10000000000000000 \"
},
\" constraints \" : {
\" maxGasWei \" : \" 1000000000000000 \" ,
\" deadline \" : 1700003600,
\" maxSlippageBps \" : 0
},
\" metadata \" : { \" source \" : \" curl-cookbook \" }
},
\" approvalTokenId \" : \" $TOKEN \"
}" | jq
The transfer_native action type does not require an asset field — it always sends the chain’s native currency (ETH on Ethereum, Optimism, Arbitrum, and Base).
Recipe 3: DEX Swap (Uniswap V3)
Swap 0.1 WETH for USDC on Base using the Uniswap V3 router.
Submit the swap intent for approval
curl -s -X POST http://localhost:3100/v1/tx/approve-request \
-H "Content-Type: application/json" \
-d '{
"version": "1",
"id": "770e8400-e29b-41d4-a716-446655440003",
"timestamp": 1700000000000,
"chain": { "type": "evm", "chainId": 8453 },
"wallet": { "address": "0xYourWalletAddress" },
"action": {
"type": "swap_exact_in",
"router": "0x2626664c2603336E57B271c5C0b26F421741e481",
"assetIn": {
"kind": "erc20",
"address": "0x4200000000000000000000000000000000000006",
"symbol": "WETH",
"decimals": 18
},
"assetOut": {
"kind": "erc20",
"address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"symbol": "USDC",
"decimals": 6
},
"amountIn": "100000000000000000",
"minAmountOut": "250000000"
},
"constraints": {
"maxGasWei": "2000000000000000",
"deadline": 1700003600,
"maxSlippageBps": 100
},
"metadata": { "source": "curl-cookbook", "note": "Swap 0.1 WETH for USDC" }
}' | jq
Sign and send
Call POST /v1/tx/sign-and-send with the full intent and the returned approvalTokenId, following the same pattern as Recipe 1 Step 3.
The Uniswap V3 router address varies by chain:
Base (8453): 0x2626664c2603336E57B271c5C0b26F421741e481
Ethereum, Optimism, Arbitrum: 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45
Recipe 4: DEX Swap via 1inch Aggregator
Same swap as Recipe 3, but routed through 1inch for potentially better pricing across multiple DEXs. The only differences are adding "provider": "1inch" and using the 1inch router address.
curl -s -X POST http://localhost:3100/v1/tx/approve-request \
-H "Content-Type: application/json" \
-d '{
"version": "1",
"id": "880e8400-e29b-41d4-a716-446655440004",
"timestamp": 1700000000000,
"chain": { "type": "evm", "chainId": 8453 },
"wallet": { "address": "0xYourWalletAddress" },
"action": {
"type": "swap_exact_in",
"router": "0x111111125421cA6dc452d289314280a0f8842A65",
"provider": "1inch",
"assetIn": {
"kind": "erc20",
"address": "0x4200000000000000000000000000000000000006",
"symbol": "WETH",
"decimals": 18
},
"assetOut": {
"kind": "erc20",
"address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"symbol": "USDC",
"decimals": 6
},
"amountIn": "100000000000000000",
"minAmountOut": "250000000"
},
"constraints": {
"maxGasWei": "2000000000000000",
"deadline": 1700003600,
"maxSlippageBps": 100
},
"metadata": { "source": "curl-cookbook" }
}' | jq
1inch requirements and limitations:
The ONEINCH_API_KEY environment variable must be set on ISCL Core.
If 1inch is unavailable (no API key, rate limited, network error), ISCL automatically falls back to Uniswap V3 silently.
swap_exact_out is not supported by the 1inch API v6 — it always uses Uniswap V3.
The 1inch router address (0x111111125421cA6dc452d289314280a0f8842A65) is the same on all 4 supported chains.
Recipe 5: Web Approval Workflow
When ISCL_APPROVAL_MODE=web, the approve-request endpoint blocks until a user approves or denies via the web dashboard or HTTP API. This requires two terminals.
Terminal 1: Submit the intent (blocks until approved)
curl -s -X POST http://localhost:3100/v1/tx/approve-request \
-H "Content-Type: application/json" \
-d '{
"version": "1",
"id": "990e8400-e29b-41d4-a716-446655440005",
"timestamp": 1700000000000,
"chain": { "type": "evm", "chainId": 8453 },
"wallet": { "address": "0xYourWalletAddress" },
"action": {
"type": "transfer",
"asset": {
"kind": "erc20",
"address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"symbol": "USDC",
"decimals": 6
},
"to": "0xRecipientAddress",
"amount": "100000000"
},
"constraints": {
"maxGasWei": "1000000000000000",
"deadline": 1700003600,
"maxSlippageBps": 0
},
"metadata": { "source": "curl-cookbook" }
}' | jq
# This call blocks waiting for approval...
Terminal 2: List pending approvals
curl -s http://localhost:3100/v1/approvals/pending | jq
Terminal 2: Approve the request
curl -s -X POST http://localhost:3100/v1/approvals/req_abc123/decide \
-H "Content-Type: application/json" \
-d '{ "approved": true }' | jq
Terminal 1 unblocks and returns the approval token.
Alternative: Use the browser dashboard
Open http://localhost:3100/approval-ui in a browser. The dashboard automatically polls for pending requests and displays Approve / Deny buttons with risk color coding.
Pending approvals have a 300-second (5-minute) TTL. If not decided within that window, the request times out and the blocked approve-request call returns an error.
Recipe 6: Check Audit History
View recent transaction events from the append-only audit trail.
curl -s 'http://localhost:3100/v1/approvals/history?limit=5' | jq
The limit query parameter accepts values from 1 to 100. Events are returned in reverse chronological order (ORDER BY timestamp DESC).
Recipe 7: Skill Management
Register, inspect, and revoke skills through the Skill Registry API.
curl -s -X POST http://localhost:3100/v1/skills/register \
-H "Content-Type: application/json" \
-d '{
"version": "1",
"name": "my-rebalancer",
"publisher": {
"name": "Operator",
"address": "0xPublisherAddress",
"contact": "[email protected] "
},
"permissions": {
"actions": ["transfer"],
"chains": [8453],
"network": false,
"filesystem": false
},
"sandbox": {
"memoryMb": 128,
"timeoutMs": 30000,
"allowSpawn": false
},
"files": [
{ "path": "run.mjs", "sha256": "abc123..." }
],
"signature": "0x..."
}' | jq
curl -s http://localhost:3100/v1/skills | jq
curl -s http://localhost:3100/v1/skills/my-rebalancer | jq
curl -s -X DELETE http://localhost:3100/v1/skills/my-rebalancer | jq
Recipe 8: Health Check
Verify that ISCL Core is running and responsive.
curl -s http://localhost:3100/v1/health | jq
Common Patterns
Generating UUIDs for intent IDs
Every TxIntent requires a unique id field in UUID format.
# macOS / Linux
uuidgen | tr '[:upper:]' '[:lower:]'
Computing deadlines
The constraints.deadline field is a Unix timestamp in seconds. Set it to a future time to prevent stale intents from being executed.
# 1 hour from now
echo $(( $(date +%s ) + 3600 ))
Token amount conversion
All token amounts are specified as strings in their smallest unit (base unit). Use this table for reference:
Token Decimals 1 Unit in Base Example USDC 6 1000000100 USDC = "100000000" WETH 18 10000000000000000000.1 WETH = "100000000000000000" DAI 18 100000000000000000050 DAI = "50000000000000000000"
Always use strings for amount values (e.g., "100000000" not 100000000). Large numbers exceed JavaScript’s safe integer range and must be represented as strings to avoid precision loss.
Error handling
Check HTTP status codes to determine the outcome of each request:
Status Meaning 200 Success 400 Invalid intent (schema validation failure) 403 Policy denied the operation 502 RPC error (check RPC configuration)
For detailed error shapes and recovery guidance, see the Error Catalog .
References