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/:intentIdSwap 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 JoinProportional 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 JoinKey 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
signTypedDatareturns a hex string. Split it intor(bytes 0-32),s(bytes 32-64),v(byte 64).
For complete examples with full EIP-712 types, see the Relayer Reference.