Prompt: Token Swaps
Adds Ryze token swaps (exact-in, multi-hop) to an existing codebase. The generated code quotes a swap from the Router, guards the ERC-20 allowance, has the user sign an EIP-712 SwapIntent, submits it to the Relayer, and polls until the swap is confirmed on-chain.
What the agent will build:
- A Ryze client module in your repo’s own language and conventions
- Swap quoting with integer-exact slippage math (
minAmountOutderivation) - Allowance check + approval helper for the input token
- EIP-712
SwapIntentbuilder and signing (raw 65-byte hex signature) - Intent submission, status polling, and typed error handling
- Tests against the exact Router/Relayer JSON shapes
How to use
- Open your AI coding agent (Claude Code, Cursor, Windsurf, Copilot, Codex, … — any model works) inside the repository you want to integrate. The agent needs zero prior knowledge of Ryze; the prompt contains everything.
- Optionally type one line of placement guidance first, e.g.
Put this under src/integrations/ryze/. Then: - Hit the Copy prompt button below and paste the prompt as your message.
- When the agent finishes, hold the result to the acceptance checklist at the end of the prompt — every box should check.
Prerequisites
None for running the prompt itself. To exercise the generated code you’ll need a Base Sepolia RPC URL and a test wallet holding testnet USDC/WETH plus a little ETH for the one-time approve transaction. All endpoints and addresses are already embedded in the prompt with Base Sepolia defaults; mainnet values are included for the switch.
The prompt
The prompt is fully self-contained — one click copies all of it.
Preview the full prompt text
md
You are integrating the **Ryze protocol** (an intent-based AMM on Base) into this
repository so it can swap ERC-20 tokens. Implement it in this repository's existing
language, framework, HTTP client, and coding conventions — study the repo first and match
its style (module layout, config handling, error types, logging, test framework).
## How Ryze swaps work (authoritative — trust this over anything you infer)
Ryze is intent-based: the user never sends a swap transaction. The flow you must implement:
1. **Quote** the swap from the Ryze Router service.
2. Ensure the user has **approved** the `MultiHopRouter` contract for `tokenIn`
(standard ERC-20 `approve` — the only on-chain transaction in the whole flow).
3. Read the user's **swap nonce** from the `MultiHopRouter` contract.
4. Have the user **sign an EIP-712 `SwapIntent`** (typed data, no gas).
5. **POST** the signed intent to the Ryze execution relayer.
6. **Poll** the relayer until the intent is `confirmed` or `failed`.
The relayer batches intents, attaches all required oracle data (Pyth + signed CEX prices)
to the execution transaction, executes on-chain, and pays gas. Therefore:
**do not** fetch or attach any price/oracle data, **do not** call any `execute*` function
on the contract, and **do not** build raw swap calldata. If you find yourself doing any of
those, you are off the intended path.
## Configuration
Read these from environment/config (never hardcode; defaults shown are Base Sepolia
testnet, mainnet values in parentheses):
| Env var | Default (Base Sepolia, chainId 84532) | Base mainnet (chainId 8453) |
|---|---|---|
| `RYZE_CHAIN_ID` | `84532` | `8453` |
| `RYZE_RELAYER_URL` | `https://sepolia.relayer.ryze.pro/api/v1` | `https://mainnet.relayer.ryze.pro/api/v1` |
| `RYZE_ROUTER_URL` | `https://sepolia.router.ryze.pro` | `https://mainnet.router.ryze.pro` |
| `RYZE_MULTIHOP_ROUTER` | `0x477A780Fab142F9289a01C9B22Ed322dE6c6af1A` | `0x8e20A1534a204DE569E9d62D410c19795b81DF70` |
| `RPC_URL` | any Base Sepolia RPC | any Base mainnet RPC |
Reference tokens (native ETH is not supported — use WETH):
- Sepolia: USDC `0x6aEB5326b5DcA2163e0995824DcB816194b157a6`, WETH `0x6Dd0C6a2e058bc3F7D6AaCe23965180BcBbb2635`, cbBTC `0xa7BaE7da3E4950700BE9f107EB186e89a474d180`
- Mainnet: USDC `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`, WETH `0x4200000000000000000000000000000000000006`, cbBTC `0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf`
## Step 1 — Quote
`POST {RYZE_ROUTER_URL}/quote` with JSON:
```json
{
"tokenIn": "<address>",
"tokenOut": "<address>",
"amountIn": "<base-unit decimal string>",
"slippageTolerance": 50,
"maxHops": 3,
"userAddress": "<user address>"
}
```
- `slippageTolerance` is basis points (50 = 0.5%); make it a parameter of your API.
- `maxHops` must be 1–3.
- Response key fields: `output.amount` (expected out, base units), `steps[]` (each with
`pool`, `tokenIn`, `tokenOut`), `priceImpact`, `executionPrice`, `prices[]`.
Derive:
- `path = steps.map(s => ({ pool: s.pool, tokenIn: s.tokenIn, tokenOut: s.tokenOut }))`
- `minAmountOut` — see the deployed behavior below; use big-integer math, never
floats.
One deployed behavior (verified live) MUST be handled here:
1. **Flat intent fee.** `MultiHopRouter.intentFee()` (view → `uint256`) is a flat USD fee
in WAD (currently 1e16 = $0.01), converted to tokenIn units at oracle price
(`feeTok = ceilDiv(intentFee × 10^decimals(tokenIn), blendedPrice(tokenIn))`, prices
from the quote's `prices[]`) and deducted `feeTok × path.length` from `amountIn` before
the swap. Quotes exclude it, so derate:
`minAmountOut = floor(output.amount × (amountIn − feeTok×hops) / amountIn × (10000 − slippageBps) / 10000)`.
Warn (or refuse) when `amountIn` is worth < ~$10 — the fee alone then exceeds typical
slippage and the intent reverts `InvalidSlippage()`/`SlippageExceeded()`.
## Step 2 — Approval
Check `ERC20(tokenIn).allowance(user, RYZE_MULTIHOP_ROUTER)`; if below `amountIn`, the user
must send `approve(RYZE_MULTIHOP_ROUTER, amount)`. Expose this as a separate step in your
API (allowance check + approval tx builder), since in most products the user's wallet sends it.
## Step 3 — Nonce
Nonces are per-user and **per-intent-type** on the contract. For swaps, `eth_call`:
```solidity
function swapNonces(address user) external view returns (uint256);
```
on `RYZE_MULTIHOP_ROUTER`. The submitted nonce must EXACTLY equal this value. Read it fresh
right before signing, and never allow two in-flight (unconfirmed) swap intents for the same
user — serialize them.
## Step 4 — EIP-712 signature
Domain:
```json
{ "name": "MultiHopRouter", "version": "1", "chainId": <RYZE_CHAIN_ID>, "verifyingContract": "<RYZE_MULTIHOP_ROUTER>" }
```
Types (exact field order):
```text
SwapIntent(address user,address tokenIn,address tokenOut,uint256 amountIn,uint256 minAmountOut,Hop[] path,address recipient,uint256 deadline,uint256 nonce)
Hop(address pool,address tokenIn,address tokenOut)
```
Message values: `user` = signer address, `recipient` = who receives `tokenOut` (usually the
user), `deadline` = unix seconds ≈ `now + 1800`, `nonce` from step 3, the rest from the quote.
Sign with the user's key (`signTypedData` / equivalent) and submit the 65-byte
signature as one hex string (`r‖s‖v`, `v` = 27/28; low-`s` required — standard signers
comply automatically). ERC-1271 smart-contract wallets (e.g. Safe) submit their contract
signature bytes instead.
## Step 5 — Submit
`POST {RYZE_RELAYER_URL}/intents/submit`:
```json
{
"type": "swap",
"user": "0x…",
"tokenIn": "0x…",
"tokenOut": "0x…",
"amountIn": "1000000",
"minAmountOut": "412000000000000000",
"path": [ { "pool": "0x…", "tokenIn": "0x…", "tokenOut": "0x…" } ],
"recipient": "0x…",
"deadline": 1789000000,
"nonce": 0,
"signature": "0x…"
}
```
All amounts are base-10 **strings**. `deadline`/`nonce` are JSON numbers.
- Success → HTTP **202**: `{ "success": true, "intentId": "0x…", "status": "pending", … }`.
202 means *accepted for execution*, not executed. `intentId` is the EIP-712 digest;
resubmitting the identical signed intent is idempotent.
- HTTP **400** → `{ "success": false, "status": "error", "message": "…" }` — surface
`message` to the caller (covers bad signature, nonce mismatch with expected-value hint,
expired deadline, discontinuous path).
- HTTP **429** → rate limited (~100 req/min/IP); retry with backoff.
## Step 6 — Poll status
`GET {RYZE_RELAYER_URL}/intents/{intentId}` every ~2s until `intent.status` is `confirmed`
or `failed` (typical total: 3–12s; timeout after ~90s but return the `intentId` so callers
can resume polling). Response includes `txHash`, `blockNumber`, `gasUsed`, and on failure a
decoded revert reason in `error`. A `failed` intent had no on-chain effect. Treat transient
HTTP 500s from this endpoint as retryable.
## What to build
1. A **Ryze client module** in this repo's conventions exposing at minimum:
- `getSwapQuote(tokenIn, tokenOut, amountIn, slippageBps, user)` → quote + derived
`path`/`minAmountOut`
- `checkAllowance(user, token, amount)` / approval-tx helper
- `buildSwapIntent(quote, user, recipient, deadlineSecs)` → unsigned EIP-712 payload
(domain + types + message) ready for any signer
- `submitSwapIntent(intent, signature)` → `intentId`
- `waitForIntent(intentId, {pollMs, timeoutMs})` → final status
- a one-call convenience `swap(...)` composing all of the above when a signer is available
2. **Config** via the env vars above, wired into this repo's config system.
3. **Error handling**: typed/structured errors for quote failures, allowance-missing,
nonce conflicts, 4xx validation messages, timeouts.
4. **Tests** in this repo's framework: mock the Router and Relayer HTTP responses (use the
exact JSON shapes above), assert the EIP-712 payload (domain, type string, field order),
the big-integer slippage math, the signature hex encoding, and the polling state machine.
## Acceptance checklist
- [ ] No oracle/price code, no direct contract execution calls, no raw swap calldata.
- [ ] All amounts flow through big-integer types; JSON emits them as strings.
- [ ] Nonce read fresh per signing; concurrent same-user swaps are serialized or rejected.
- [ ] `minAmountOut > 0` always; slippage math is integer-exact and derates for
`intentFee()` (× hops).
- [ ] EIP-712 domain/type strings byte-match the spec above.
- [ ] 202/400/429 and `pending/confirmed/failed` all handled and tested.
- [ ] Everything configurable via env; defaults point at Base Sepolia.