Skip to content

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 RyzeClient facade: swap(), joinSingle(), joinProportional(), exit()
  • A test suite covering every documented pitfall

How to use

  1. 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.
  2. Optionally type one line of placement guidance first, e.g. Put this under src/integrations/ryze/. Then:
  3. Hit the Copy prompt button below and paste the prompt as your message.
  4. 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.

View raw
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.