Prompt: Full Integration
The complete Ryze integration in one prompt: swaps, single-asset joins, proportional joins, and exits, structured as a layered client (config → transport → quotes → intents → execution → facade). Use this when your product needs more than one operation — it shares plumbing instead of duplicating it.
What the agent will build:
- Env-driven config with Base Sepolia defaults and mainnet values documented
- HTTP clients for Router + Relayer with backoff, plus minimal contract reads
- Quote layer for all four operations, returning ready-to-sign intent inputs
- EIP-712 builders for all four intent types, usable with any signer (backend key or browser wallet)
- A
RyzeClientfacade:swap(),joinSingle(),joinProportional(),exit() - A test suite covering every documented pitfall
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 with ALL four operations: token swap (exact-in), single-asset liquidity join,
proportional liquidity join, and proportional liquidity exit. Implement it in this
repository's existing language, framework, HTTP client, and conventions — study the repo
first and match its style.
## How Ryze works (authoritative — trust this over anything you infer)
Ryze is intent-based. Users never send pool transactions. For every operation the flow is:
**quote → (ERC-20 approve `MultiHopRouter` if needed) → read per-type nonce → sign EIP-712
intent → POST to relayer → poll status.**
The relayer batches intents, attaches all required oracle data (Pyth price payloads +
ECDSA-signed CEX prices) to the execution transaction, executes on-chain, and pays gas.
Hard rules:
1. **Never** fetch, sign, or attach oracle/price data — the relayer does it.
2. **Never** call `executeSwapIntents`/`executeJoinIntents`/etc. on the contract.
3. The only user transaction is ERC-20 `approve(RYZE_MULTIHOP_ROUTER, amount)` for input
tokens (NOT needed for exits — LP shares are burned, not pulled).
4. All `uint256` values travel as base-10 strings in JSON; use big-integer math everywhere.
5. Every `min…Out` must be > 0 and derived from a quote with integer slippage math:
`min = floor(expected × (10000 − slippageBps) / 10000)`.
## Configuration (env vars — never hardcode)
| 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_API_URL` | `https://sepolia.api.ryze.pro/api` | `https://mainnet.api.ryze.pro/api` |
| `RYZE_MULTIHOP_ROUTER` | `0x477A780Fab142F9289a01C9B22Ed322dE6c6af1A` | `0x8e20A1534a204DE569E9d62D410c19795b81DF70` |
| `RYZE_POOL_QUERIES` | `0x1a7B7D9071c30935448b6cC3d114c0c794d0a3C0` | `0xD4b5DD638d09aB7b6Eb4Ec3490F02A96d0DD4100` |
| `RPC_URL` | any Base Sepolia RPC | any Base mainnet RPC |
Discovery endpoints: `GET {RYZE_API_URL}/assets` (tokens + decimals),
`GET {RYZE_API_URL}/pools/list` (pools with ordered `tokens[{address,weight,decimals}]`,
`totalSupplyLP`). Native ETH is not supported — use WETH.
Reference addresses — Sepolia: USDC `0x6aEB5326b5DcA2163e0995824DcB816194b157a6`,
WETH `0x6Dd0C6a2e058bc3F7D6AaCe23965180BcBbb2635`, cbBTC `0xa7BaE7da3E4950700BE9f107EB186e89a474d180`,
WETH-USDC pool `0x1aB1cC9923ed45F1C7F291B222CC3d0D2a578723`, cbBTC-USDC pool `0xaC522dD3B13c8b2F4b765d100b1Ea13493Cf55bA`.
Mainnet: USDC `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`, WETH `0x4200000000000000000000000000000000000006`,
cbBTC `0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf`, WETH-USDC pool `0x22f902cEfcF8b0bEc6489Cb8ac11FdDa9B2aF125`,
cbBTC-USDC pool `0x40F3DAaE59BfE03f9Fb019Bb089Bb0C381DE27Cf`.
## Quotes
- **Swap**: `POST {RYZE_ROUTER_URL}/quote` —
`{ "tokenIn", "tokenOut", "amountIn", "slippageTolerance": <bps>, "maxHops": 1..3, "userAddress" }`
→ `output.amount`, `steps[{pool,tokenIn,tokenOut,…}]`, `priceImpact`, `prices[]`.
Intent `path = steps.map(s => ({pool, tokenIn, tokenOut}))`.
- **Joins (both kinds)**: `POST {RYZE_ROUTER_URL}/join-quote` —
`{ "poolAddress", "tokensIn": [{token, amount}, …] }`. One token → `joinType:"single"`
(fees apply); all pool tokens → `joinType:"proportional"` (fee-free). → `sharesOut`
(18-decimal string), `tvl`, `shareOfPool`, `fees{…}`, `prices[]`.
- **Exit**: no HTTP endpoint — `eth_call`
`WeightedPoolQueries.queryProportionalExit(pool, sharesIn)`. ⚠ The deployed contract
returns a **struct-wrapped array**: declare the ABI output as
`tuple(uint256[] amountsOut)`, NOT bare `uint256[]` (decoding as `uint256[]` throws).
Amounts are ordered like the pool's asset list; also available:
`queryProportionalExitForUser(pool, user)`.
## Execution-reality rules (verified against the live deployment — build ALL of these in)
1. **Flat intent fee.** `MultiHopRouter.intentFee()` (view → `uint256`) is a flat **USD fee
in WAD** (currently 1e16 = $0.01) per execution, converted to token units at oracle
price: `feeTok(t) = ceilDiv(intentFee × 10^decimals(t), blendedPrice(t))` using the
quote's `prices[]`. Deducted: swap — `feeTok(tokenIn) × path.length` off `amountIn`;
single join — `feeTok(tokenIn)` off `amountIn`; proportional join — `feeTok(tokensIn[0])`
off `amountsIn[0]`; exit — `feeTok(poolAsset0)` off the outputs (contract enforces
`out[0] ≥ min[0] + fee`). Quotes exclude this fee → derate every min by the fee ratio
before slippage (exit: subtract `feeTok(asset0)` from `minAmountsOut[0]`, floor 1), and
warn when amounts are worth < ~$10 (the fee alone then exceeds typical slippage and
intents revert `InvalidSlippage()`).
2. **LP shares are auto-staked in the pool's gauge vault.** When
`MultiHopRouter.poolToGaugeVault(pool)` is non-zero (true for the listed pools), join
shares go to the LPGaugeVault: `pool.balanceOf(user)` stays 0. LP position =
`pool.balanceOf(user) + gauge.getEligibleBalance(user)`; exits burn from the gauge
balance (no approval).
## EIP-712 signing
One domain for all types:
```json
{ "name": "MultiHopRouter", "version": "1", "chainId": <RYZE_CHAIN_ID>, "verifyingContract": "<RYZE_MULTIHOP_ROUTER>" }
```
Exact type definitions (field order matters):
```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)
JoinIntent(address user,address pool,address tokenIn,uint256 amountIn,uint256 minSharesOut,address recipient,uint256 deadline,uint256 nonce)
JoinProportionalIntent(address user,address pool,address[] tokensIn,uint256[] amountsIn,uint256 minSharesOut,address recipient,uint256 deadline,uint256 nonce)
ExitProportionalIntent(address user,address pool,uint256 sharesIn,uint256[] minAmountsOut,address recipient,uint256 deadline,uint256 nonce)
```
`deadline` = unix seconds ≈ `now + 1800`. Submit the 65-byte signature as one hex
string: `"signature": "0x…"` (`r‖s‖v`, `v` = 27/28; low-`s` enforced). ERC-1271
smart-contract wallets (e.g. Safe) submit their contract signature bytes instead.
## Nonces — per-user AND per-type, on `RYZE_MULTIHOP_ROUTER`
```solidity
function swapNonces(address) view returns (uint256);
function joinNonces(address) view returns (uint256);
function joinProportionalNonces(address) view returns (uint256);
function exitProportionalNonces(address) view returns (uint256);
```
The submitted `nonce` must EXACTLY equal the on-chain value for that type. Read fresh
right before signing, and serialize intents per (user, type): never two unconfirmed
intents of the same type for the same user.
## Relayer submit bodies — `POST {RYZE_RELAYER_URL}/intents/submit`
Common fields all types: `user`, `recipient`, `deadline` (number), `nonce` (number),
`signature` (hex string, 65-byte `r‖s‖v` or ERC-1271 contract signature). Type-specific:
```jsonc
// swap
{ "type": "swap", "tokenIn": "0x…", "tokenOut": "0x…", "amountIn": "…",
"minAmountOut": "…", "path": [{ "pool": "0x…", "tokenIn": "0x…", "tokenOut": "0x…" }], … }
// single-asset join — otherPoolToken (the pool's other asset) is REQUIRED in the body
// but NOT part of the signed message
{ "type": "join", "pool": "0x…", "tokenIn": "0x…", "amountIn": "…",
"minSharesOut": "…", "otherPoolToken": "0x…", … }
// proportional join — signed field is named minSharesOut but the BODY field is
// minPoolTokensOut (same value). tokensIn/amountsIn: aligned, ≥2, no duplicates, all >0.
{ "type": "joinProportional", "pool": "0x…", "tokensIn": ["0x…","0x…"],
"amountsIn": ["…","…"], "minPoolTokensOut": "…", … }
// exit — poolTokens (pool's ordered asset list, same order/length as minAmountsOut) is
// REQUIRED in the body but NOT part of the signed message. No approval needed for exits.
{ "type": "exitProportional", "pool": "0x…", "sharesIn": "…",
"minAmountsOut": ["…","…"], "poolTokens": ["0x…","0x…"], … }
```
Responses:
- HTTP **202** `{ "success": true, "intentId": "0x…", "status": "pending", … }` — accepted,
not yet executed. `intentId` = EIP-712 digest; identical resubmission is idempotent.
- HTTP **400** `{ "success": false, "status": "error", "message": "…" }` — bad signature,
nonce mismatch (message hints the expected value), expired deadline, path/array errors.
- HTTP **429** — rate limit (~100 req/min/IP); back off with jitter.
## Status polling — `GET {RYZE_RELAYER_URL}/intents/{intentId}`
`{ "success": true, "intent": { "intentId", "status": "pending|confirmed|failed",
"txHash", "batchId", "blockNumber", "gasUsed", "error" } }`
Poll ~2s; typical completion 3–12s; timeout ~90s but always return `intentId` so callers
can resume. `failed` → decoded revert in `error`, no on-chain effect. Transient 500s are
retryable. Health check for circuit breaking: `GET {RYZE_RELAYER_URL}/health` (503 when
degraded).
## What to build
Design it in layers, in this repo's idiom:
1. **`RyzeConfig`** — env-driven config (URLs, chainId, addresses) with Sepolia defaults.
2. **Transport layer** — thin HTTP clients for Router + Relayer (timeouts, 429 backoff,
structured error extraction from `message`) and a minimal contract-read helper for the
four nonce functions + `queryProportionalExit` + `allowance`/`balanceOf`.
3. **Quote layer** — `getSwapQuote`, `getJoinQuote` (single + proportional),
`getExitQuote`, each returning expected output AND the derived intent inputs
(`path`, `min…Out`, `otherPoolToken`, `poolTokens`).
4. **Intent layer** — `buildXxxIntent(...)` for all four types returning the unsigned
EIP-712 payload `{domain, types, primaryType, message}` so ANY signer (backend key,
browser wallet) can sign it; the raw hex signature is submitted as-is.
5. **Execution layer** — `submitIntent(body)` → `intentId`;
`waitForIntent(intentId, {pollMs, timeoutMs})` → terminal status; per-(user,type)
in-flight serialization.
6. **Facade** — `RyzeClient` with `swap()`, `joinSingle()`, `joinProportional()`, `exit()`
composing quote → allowance guard → nonce → sign → submit → wait, plus the granular
methods exposed for UIs that split signing from submission.
7. **Tests** — mock all HTTP + eth_call responses with the exact JSON shapes above.
Must cover: EIP-712 domain/type-string byte-equality for all four types, array
encoding (proportional join, exit), the `minSharesOut`/`minPoolTokensOut` naming
mapping, `otherPoolToken`/`poolTokens` present in body but absent from signed message,
integer slippage math edge cases, signature hex encoding, 400 nonce-mismatch surfacing, 429
backoff, polling state machine incl. timeout and `failed` with revert reason.
## Acceptance checklist
- [ ] Four operations working through quote → approve → nonce → sign → submit → poll.
- [ ] Zero price/oracle code; zero direct `execute*` contract calls; zero raw calldata.
- [ ] All execution-reality rules implemented: intent-fee derating on every min and
gauge-aware LP position reads.
- [ ] Per-type nonces used correctly and read fresh; per-(user,type) serialization.
- [ ] Big-integer amounts as strings end to end; all mins > 0.
- [ ] The three field traps handled: `otherPoolToken` (join), `minPoolTokensOut`
(proportional join body) vs `minSharesOut` (signed), `poolTokens` (exit).
- [ ] Exits skip approval; joins validate approval for every input token.
- [ ] All URLs/addresses/chainId from env with Base Sepolia defaults.
- [ ] Test suite passes; add a short README section documenting usage of the facade.