Demo Prompt: Rust
Generates a standalone, runnable Rust project (alloy, reqwest, tokio) demonstrating all four Ryze operations on Base Sepolia — four binaries, heavily commented so the code doubles as a tutorial.
What the agent will build:
- A fresh
ryze-demo-rscrate (alloy full, reqwest, tokio, serde, dotenvy) with.env.example - A Ryze library: config, quotes,
sol!EIP-712 structs, relayer client, chain reads - Four runnable binaries:
cargo run --bin swap,join_single,join_proportional,exit - A README with setup, wallet funding, and the mainnet switch
How to use
- Open your AI coding agent (Claude Code, Cursor, Windsurf, Copilot, Codex, … — any model works) in an empty directory (or an
examples/folder). The agent needs zero prior knowledge of Ryze; the prompt contains everything. - Hit the Copy prompt button below and paste the prompt as your message.
- When the agent finishes, create
.envfrom.env.example, add a funded Base Sepolia test key, and run each script. Hold the result to the acceptance criteria at the end of the prompt.
Prerequisites
Node/Go/Rust toolchain for the chosen stack, a Base Sepolia RPC URL, and a test wallet holding testnet USDC/WETH plus a little ETH for approvals. Everything else — endpoints, addresses, schemas — is embedded in the prompt.
The prompt
The prompt is fully self-contained — one click copies all of it.
Preview the full prompt text
md
Create a self-contained, runnable **Rust** demo project (using the **alloy** crate family:
`alloy` with features `full` — providers, signers, sol-types — plus `reqwest`, `tokio`,
`serde`/`serde_json`, `dotenvy`) that integrates the **Ryze protocol** (an intent-based AMM
on Base) end to end: swap (exact-in), single-asset liquidity join, proportional liquidity
join, and proportional liquidity exit. The demo's purpose is to *teach the integration* —
heavily commented, each step explicit, each operation runnable as its own binary.
## Protocol model (authoritative — do not deviate)
Ryze is intent-based. For every operation:
**quote → (ERC-20 approve `MultiHopRouter` if needed) → read per-type nonce → sign an
EIP-712 intent with the user's key → POST it to the Ryze execution relayer → poll status.**
The relayer batches intents, attaches the required oracle data (Pyth payloads + signed CEX
prices), executes on-chain, and pays gas. The demo must contain **no price/oracle code**,
**no `execute*` contract calls**, and **no raw swap calldata** — the only transaction the
demo wallet sends is `approve`.
## Configuration — `.env` (provide `.env.example`)
```bash
PRIVATE_KEY= # funded Base Sepolia test wallet (test USDC/WETH + a little ETH for approvals)
RPC_URL= # Base Sepolia RPC
RYZE_CHAIN_ID=84532
RYZE_RELAYER_URL=https://sepolia.relayer.ryze.pro/api/v1
RYZE_ROUTER_URL=https://sepolia.router.ryze.pro
RYZE_API_URL=https://sepolia.api.ryze.pro/api
RYZE_MULTIHOP_ROUTER=0x477A780Fab142F9289a01C9B22Ed322dE6c6af1A
RYZE_POOL_QUERIES=0x1a7B7D9071c30935448b6cC3d114c0c794d0a3C0
USDC=0x6aEB5326b5DcA2163e0995824DcB816194b157a6
WETH=0x6Dd0C6a2e058bc3F7D6AaCe23965180BcBbb2635
POOL_WETH_USDC=0x1aB1cC9923ed45F1C7F291B222CC3d0D2a578723
```
(Mainnet, for the README: chainId 8453, `mainnet.*.ryze.pro`, MultiHopRouter
`0x8e20A1534a204DE569E9d62D410c19795b81DF70`, PoolQueries
`0xD4b5DD638d09aB7b6Eb4Ec3490F02A96d0DD4100`.)
## API surface
**Quotes (Router):**
- Swap: `POST {RYZE_ROUTER_URL}/quote` body
`{"tokenIn","tokenOut","amountIn","slippageTolerance":<bps>,"maxHops":3,"userAddress"}` →
`output.amount` (base-unit string), `steps[{pool,tokenIn,tokenOut}]`, `priceImpact`.
Intent path = the steps' `{pool,tokenIn,tokenOut}` triples in order.
- Join: `POST {RYZE_ROUTER_URL}/join-quote` body
`{"poolAddress","tokensIn":[{"token","amount"},…]}` — one entry ⇒ `joinType:"single"`,
all pool tokens ⇒ `"proportional"` → `sharesOut` (18-dec string).
- Exit: `eth_call` `WeightedPoolQueries.queryProportionalExit(address pool, uint256 sharesIn)`.
⚠ The deployed contract returns a **struct-wrapped array** — ABI output is
`tuple(uint256[] amountsOut)`, NOT bare `uint256[]` (decoding as `uint256[]` fails; in
the `sol!` macro declare the return as a struct with an `amountsOut` array field).
Amounts are ordered like the pool's asset list (order from `GET {RYZE_API_URL}/pools/list`).
Slippage with `U256` only: `min = expected * (10000 - bps) / 10000`; must be > 0.
## 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.len()` 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 mins by the fee ratio before
slippage (exit: subtract `feeTok(asset0)` from `minAmountsOut[0]`, floor 1). Use
**$25-scale amounts** — at $1 the fee alone is 1% and intents revert `InvalidSlippage()`.
2. **LP shares are auto-staked in the pool's gauge vault.** `MultiHopRouter.poolToGaugeVault(pool)`
(view → `address`) is set for these pools: join shares go to the LPGaugeVault, and
`pool.balanceOf(user)` stays 0. LP position = `pool.balanceOf(user) +
gauge.getEligibleBalance(user)` (view → `uint256`). Exits burn directly from the gauge
balance — still no approval needed.
**Nonces** — per-user AND per-type view functions on `RYZE_MULTIHOP_ROUTER`:
`swapNonces(address)`, `joinNonces(address)`, `joinProportionalNonces(address)`,
`exitProportionalNonces(address)`. Submitted nonce must exactly equal the on-chain value;
read right before signing.
**EIP-712 with alloy** — define the intents with the `sol!` macro so `SolStruct` gives you
the correct struct hashing, and build the domain with `eip712_domain!`:
```rust
use alloy::sol;
use alloy::sol_types::{eip712_domain, SolStruct};
sol! {
struct Hop { address pool; address tokenIn; address tokenOut; }
struct SwapIntent {
address user; address tokenIn; address tokenOut; uint256 amountIn;
uint256 minAmountOut; Hop[] path; address recipient; uint256 deadline; uint256 nonce;
}
struct JoinIntent {
address user; address pool; address tokenIn; uint256 amountIn;
uint256 minSharesOut; address recipient; uint256 deadline; uint256 nonce;
}
struct JoinProportionalIntent {
address user; address pool; address[] tokensIn; uint256[] amountsIn;
uint256 minSharesOut; address recipient; uint256 deadline; uint256 nonce;
}
struct ExitProportionalIntent {
address user; address pool; uint256 sharesIn; uint256[] minAmountsOut;
address recipient; uint256 deadline; uint256 nonce;
}
}
let domain = eip712_domain! {
name: "MultiHopRouter", version: "1",
chain_id: chain_id, verifying_contract: multi_hop_router,
};
```
Sign with `PrivateKeySigner::sign_typed_data(&intent, &domain).await?` (or
`sign_hash(intent.eip712_signing_hash(&domain))`). Serialize the full signature as hex:
`format!("0x{}", hex::encode(sig.as_bytes()))` — 65 bytes `r‖s‖v` with `v` ∈ {27, 28}
(alloy exposes parity; `as_bytes()` already appends `27 + parity`). Low-`s` is guaranteed
by alloy signers and required by the relayer.
**Submit** — `POST {RYZE_RELAYER_URL}/intents/submit`, JSON. Common fields: `user`,
`recipient`, `deadline` (unix seconds ≈ now+1800, JSON number), `nonce` (JSON number),
`signature: "0x…"` (the 65-byte hex string). All amounts base-10 **strings** (serialize
`U256` via `.to_string()`). Type-specific:
- `{"type":"swap", "tokenIn","tokenOut","amountIn","minAmountOut","path":[{pool,tokenIn,tokenOut}]}`
- `{"type":"join", "pool","tokenIn","amountIn","minSharesOut","otherPoolToken"}` —
`otherPoolToken` = the pool's other asset; REQUIRED in body, NOT in the signed struct.
- `{"type":"joinProportional", "pool","tokensIn":[],"amountsIn":[],"minPoolTokensOut"}` —
body field `minPoolTokensOut`, signed field `minSharesOut` (same value).
- `{"type":"exitProportional", "pool","sharesIn","minAmountsOut":[],"poolTokens":[]}` —
`poolTokens` (ordered pool assets, aligned with `minAmountsOut`) REQUIRED in body, NOT
signed. No approval needed for exits.
Responses: 202 `{"success":true,"intentId":"0x…","status":"pending"}` (idempotent);
400 `{"success":false,"message":"…"}` (surface the message — includes nonce hints);
429 rate limit → exponential backoff.
**Poll** — `GET {RYZE_RELAYER_URL}/intents/{intentId}` every 2s until
`intent.status ∈ {confirmed, failed}` (typical 3–12s; 90s timeout printing the intentId).
Print `txHash`, `blockNumber`, `gasUsed`; on `failed` print the decoded revert in `error`.
## Project to generate
```
ryze-demo-rs/
├── Cargo.toml # alloy (full), reqwest (json), tokio (full), serde, serde_json, dotenvy, anyhow
├── .env.example
├── README.md # setup, funding, run instructions, mainnet switch
└── src/
├── lib.rs # re-exports
├── config.rs # env config + validation
├── quotes.rs # swap/join HTTP quotes, exit view-call quote, slippage math
├── eip712.rs # sol! structs, domain, builders, sign + hex encode
├── relayer.rs # submit + poll (serde types, error surfacing)
├── chain.rs # provider: nonce reads, allowance/ensure_approval, balances
└── bin/
├── swap.rs # USDC → WETH
├── join_single.rs # USDC-only join into WETH-USDC pool
├── join_proportional.rs
└── exit.rs # exit a fraction of the LP position
```
Each binary: load config → print quote (human-readable using decimals from
`GET {RYZE_API_URL}/assets`) → apply the execution-reality rules (fee-derated mins) → ensure approval (skip for exit) → read the correct nonce →
build + sign the intent → print the submit body → submit → poll with status transitions to
`confirmed`/`failed`. Use $25-scale amounts (e.g. 25 USDC).
## Acceptance
- `cargo build` clean; `cargo run --bin swap` (and the other three) complete end to end on
Base Sepolia with a funded key and a printed txHash.
- All execution-reality rules implemented: intent-fee derating, gauge-vault position reads.
- All amount math via `U256`; JSON amounts serialized as strings.
- Comments explain the intent model, per-type nonces, the flat intent fee, the gauge
auto-stake, and the three field traps
(`otherPoolToken`, `minPoolTokensOut` vs `minSharesOut`, `poolTokens`).
- README includes a flow diagram, prerequisites, and the mainnet env switch.