Skip to content

Submit Swap

POST /intents/submit (type: "swap")

Submit a signed swap intent for execution. The relayer batches the intent with others and executes it on-chain.

Full Example (viem)

javascript
import { parseUnits } from 'viem';

const MULTIHOP_ROUTER = '0x662F15226f5b6Bf8aA10512374Af3115412C04bA';
const RELAYER_URL = 'https://mainnet.relayer.ryze.pro/api/v1';

// 1. Get a quote from the Router
const quoteRes = await fetch('https://mainnet.router.ryze.pro/quote', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    tokenIn: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC
    tokenOut: '0x4200000000000000000000000000000000000006', // WETH
    amountIn: parseUnits('100', 6).toString(),
    slippageTolerance: 50,
    maxHops: 3,
    userAddress: account.address,
  }),
});
const quote = await quoteRes.json();

// 2. Read nonce from contract
const nonce = await publicClient.readContract({
  address: MULTIHOP_ROUTER,
  abi: [{ name: 'swapNonces', type: 'function', inputs: [{ type: 'address' }], outputs: [{ type: 'uint256' }], stateMutability: 'view' }],
  functionName: 'swapNonces',
  args: [account.address],
});

// 3. Build the intent
const deadline = BigInt(Math.floor(Date.now() / 1000) + 1800); // 30 min
const path = quote.steps.map(step => ({
  pool: step.pool,
  tokenIn: step.tokenIn,
  tokenOut: step.tokenOut,
}));

const intent = {
  user: account.address,
  tokenIn: quote.input.token,
  tokenOut: quote.output.token,
  amountIn: BigInt(quote.input.amount),
  minAmountOut: BigInt(quote.output.amount) * 995n / 1000n, // 0.5% slippage
  path,
  recipient: account.address,
  deadline,
  nonce,
};

// 4. Sign EIP-712
const signature = await walletClient.signTypedData({
  domain: {
    name: 'MultiHopRouter',
    version: '1',
    chainId: 8453,
    verifyingContract: MULTIHOP_ROUTER,
  },
  types: {
    SwapIntent: [
      { name: 'user', type: 'address' },
      { name: 'tokenIn', type: 'address' },
      { name: 'tokenOut', type: 'address' },
      { name: 'amountIn', type: 'uint256' },
      { name: 'minAmountOut', type: 'uint256' },
      { name: 'path', type: 'Hop[]' },
      { name: 'recipient', type: 'address' },
      { name: 'deadline', type: 'uint256' },
      { name: 'nonce', type: 'uint256' },
    ],
    Hop: [
      { name: 'pool', type: 'address' },
      { name: 'tokenIn', type: 'address' },
      { name: 'tokenOut', type: 'address' },
    ],
  },
  primaryType: 'SwapIntent',
  message: intent,
});

// 5. Split signature and submit
const r = signature.slice(0, 66);
const s = '0x' + signature.slice(66, 130);
const v = parseInt(signature.slice(130, 132), 16);

const res = await fetch(`${RELAYER_URL}/intents/submit`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    type: 'swap',
    user: intent.user,
    tokenIn: intent.tokenIn,
    tokenOut: intent.tokenOut,
    amountIn: intent.amountIn.toString(),
    minAmountOut: intent.minAmountOut.toString(),
    path: intent.path,
    recipient: intent.recipient,
    deadline: Number(intent.deadline),
    nonce: Number(intent.nonce),
    signature: { v, r, s },
  }),
});
const result = await res.json();
console.log('Intent ID:', result.intentId);

Request Body

json
{
  "type": "swap",
  "user": "0xYourAddress",
  "tokenIn": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  "tokenOut": "0x4200000000000000000000000000000000000006",
  "amountIn": "100000000",
  "minAmountOut": "62187500000000000",
  "path": [
    {
      "pool": "0x7B41aA91947398CD9244AD4e314C253D9B1B5206",
      "tokenIn": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "tokenOut": "0x4200000000000000000000000000000000000006"
    }
  ],
  "recipient": "0xYourAddress",
  "deadline": 1713200000,
  "nonce": 0,
  "signature": {
    "v": 27,
    "r": "0x...",
    "s": "0x..."
  }
}

Fields

FieldTypeDescription
typestringMust be "swap"
useraddressSender address (must match signature)
tokenInaddressInput token
tokenOutaddressOutput token
amountInstringInput amount (raw units, decimal string)
minAmountOutstringMinimum output (slippage protection)
pathHop[]Swap route from Router quote (max 5 hops)
recipientaddressAddress to receive output
deadlinenumberUnix timestamp expiry
noncenumberFrom swapNonces(user) on contract
signatureobjectEIP-712 signature {v, r, s}

Path

The path comes directly from the Router quote's steps array:

javascript
const path = quote.steps.map(step => ({
  pool: step.pool,
  tokenIn: step.tokenIn,
  tokenOut: step.tokenOut,
}));

For multi-hop swaps (e.g., cbBTC -> USDC -> WETH):

json
"path": [
  { "pool": "0xPoolA", "tokenIn": "0xcbBTC", "tokenOut": "0xUSDC" },
  { "pool": "0xPoolB", "tokenIn": "0xUSDC", "tokenOut": "0xWETH" }
]

Response (202 Accepted)

json
{
  "success": true,
  "intentId": "0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
  "status": "pending",
  "message": "Intent submitted successfully"
}

EIP-712 Types

typescript
const types = {
  SwapIntent: [
    { name: 'user', type: 'address' },
    { name: 'tokenIn', type: 'address' },
    { name: 'tokenOut', type: 'address' },
    { name: 'amountIn', type: 'uint256' },
    { name: 'minAmountOut', type: 'uint256' },
    { name: 'path', type: 'Hop[]' },
    { name: 'recipient', type: 'address' },
    { name: 'deadline', type: 'uint256' },
    { name: 'nonce', type: 'uint256' },
  ],
  Hop: [
    { name: 'pool', type: 'address' },
    { name: 'tokenIn', type: 'address' },
    { name: 'tokenOut', type: 'address' },
  ],
};