Skip to content

Submit Transactions

The Ryze Relayer executes transactions on behalf of users. Instead of submitting on-chain transactions directly, users sign an intent and the relayer handles gas, oracle data, and batching.

Flow Overview

1. Get a quote        → Router (/quote or /join-quote)
2. Approve tokens     → ERC-20 approve to MultiHopRouter
3. Read nonce         → Contract swapNonces/joinNonces/etc.
4. Sign intent        → EIP-712 typed data with wallet
5. Submit intent      → Relayer POST /intents/submit
6. Poll for status    → Relayer GET /intents/:intentId

Swap Example

Swap 100 USDC for WETH:

javascript
const MULTIHOP_ROUTER = '0x662F15226f5b6Bf8aA10512374Af3115412C04bA';

// 1. Get quote
const quote = await fetch('https://mainnet.router.ryze.pro/quote', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    tokenIn: USDC_ADDRESS,
    tokenOut: WETH_ADDRESS,
    amountIn: '100000000', // 100 USDC
    slippageTolerance: 50,
    maxHops: 3,
    userAddress: account.address,
  }),
}).then(r => r.json());

// 2. Approve (if needed)
await walletClient.writeContract({
  address: USDC_ADDRESS,
  abi: erc20Abi,
  functionName: 'approve',
  args: [MULTIHOP_ROUTER, BigInt(quote.input.amount)],
});

// 3. Read nonce
const nonce = await publicClient.readContract({
  address: MULTIHOP_ROUTER,
  abi: routerAbi,
  functionName: 'swapNonces',
  args: [account.address],
});

// 4. Sign intent
const deadline = BigInt(Math.floor(Date.now() / 1000) + 1800);
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,
  path: quote.steps.map(s => ({ pool: s.pool, tokenIn: s.tokenIn, tokenOut: s.tokenOut })),
  recipient: account.address,
  deadline,
  nonce,
};

const sig = 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. Submit
const r = sig.slice(0, 66);
const s = '0x' + sig.slice(66, 130);
const v = parseInt(sig.slice(130, 132), 16);

const result = await fetch('https://mainnet.relayer.ryze.pro/api/v1/intents/submit', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    type: 'swap', ...intent,
    amountIn: intent.amountIn.toString(),
    minAmountOut: intent.minAmountOut.toString(),
    deadline: Number(intent.deadline),
    nonce: Number(intent.nonce),
    signature: { v, r, s },
  }),
}).then(r => r.json());

// 6. Poll for confirmation
let status = 'pending';
while (status === 'pending') {
  await new Promise(r => setTimeout(r, 2000));
  const res = await fetch(
    `https://mainnet.relayer.ryze.pro/api/v1/intents/${result.intentId}`
  ).then(r => r.json());
  status = res.intent.status;
  if (status === 'confirmed') console.log('Tx:', res.intent.txHash);
  if (status === 'failed') throw new Error(res.intent.error);
}

Single-Asset Join Example

Deposit 100 USDC into a pool:

javascript
// Get join quote
const quote = await fetch('https://mainnet.router.ryze.pro/join-quote', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    poolAddress: POOL_ADDRESS,
    tokensIn: [{ token: USDC_ADDRESS, amount: '100000000' }],
  }),
}).then(r => r.json());

// Read join nonce
const nonce = await publicClient.readContract({
  address: MULTIHOP_ROUTER, abi: routerAbi,
  functionName: 'joinNonces', args: [account.address],
});

// Sign JoinIntent and submit with type: "join"
// See full example in Relayer Reference → Submit Join

Proportional Join Example

Deposit both tokens in ratio:

javascript
// Get proportional join quote (pass all pool tokens)
const quote = await fetch('https://mainnet.router.ryze.pro/join-quote', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    poolAddress: POOL_ADDRESS,
    tokensIn: [
      { token: WETH_ADDRESS, amount: wethAmount },
      { token: USDC_ADDRESS, amount: usdcAmount },
    ],
  }),
}).then(r => r.json());

// Read joinProportional nonce
const nonce = await publicClient.readContract({
  address: MULTIHOP_ROUTER, abi: routerAbi,
  functionName: 'joinProportionalNonces', args: [account.address],
});

// Sign JoinProportionalIntent and submit with type: "joinProportional"
// See full example in Relayer Reference → Submit Proportional Join

Key Points

  • Token approval must be done before submitting any intent. Approve the MultiHopRouter contract.
  • Each intent type has its own nonce: swapNonces, joinNonces, joinProportionalNonces, exitProportionalNonces. Use the right one.
  • Deadlines should be set ~30 minutes in the future. Expired intents are rejected.
  • minAmountOut / minSharesOut provide slippage protection. Use the Router quote output with a small buffer (e.g., 0.5%).
  • Signature splitting: viem's signTypedData returns a hex string. Split it into r (bytes 0-32), s (bytes 32-64), v (byte 64).

For complete examples with full EIP-712 types, see the Relayer Reference.