Put cover inside your own deposit flow.
Everything Rider's app does is open to integrators: read the markets, build a protected deposit in one transaction, let an agent buy cover under a hard budget, run the keeper, or propose a new trigger. MIT licensed.
Read the markets, then deposit with cover in one transaction.
packages/sdk depends only on viem. createRiderClient reads a market or a pool in one multicall round trip; protectCalls builds the four calls of a protected deposit; multicall3FromTx wraps them for an EOA, and asCalls hands the same calls to a smart account.
import { createPublicClient, http, type PublicClient } from "viem";
import { arc, arcMainnet, createRiderClient, formatBps, formatUsdc, liveMarkets } from "@rider/sdk";
const client = createPublicClient({ chain: arc, transport: http("https://rpc.mainnet.arc.io") }) as PublicClient;
const rider = createRiderClient(client, arcMainnet);
// Every live market: its reading against its line, and how much cover it can still back.
for (const address of liveMarkets(arcMainnet)) {
const m = await rider.market(address);
console.log(
m.name,
m.status,
m.health.ok ? `loss ${formatBps(m.health.lossBps)} of ${formatBps(m.thresholdBps)}` : "no trusted reading",
`${formatUsdc(m.capacity)} USDC of room`,
);
}// continues from read.ts: arc, arcMainnet and rider are in scope
import { createWalletClient, custom } from "viem";
import { ARC, asCalls, liveMarkets, multicall3FromTx, parseUsdc, protectCalls } from "@rider/sdk";
const wallet = createWalletClient({ chain: arc, transport: custom(window.ethereum!) });
const [account] = await wallet.requestAddresses();
// Find the market whose trigger watches the vault, then price the cover.
const vault = ARC.vaults.galaxyUsdc;
const market = await rider.marketForVault(vault, liveMarkets(arcMainnet));
if (!market) throw new Error("no Rider market watches this vault");
const amount = parseUsdc("250");
const duration = 30n * 86_400n;
const { premium } = await rider.quote(market, amount, duration);
// approve → deposit → approve → buyCover
const calls = protectCalls({
vault,
market,
amount,
coverage: amount,
duration,
holder: account, // receives the vault shares and the policy NFT
maxPremium: premium + premium / 10n + 1n, // headroom; the market refuses anything above it
});
// From an EOA: one Multicall3From transaction. Every call keeps the depositor as msg.sender,
// so no router ever holds the USDC.
await wallet.sendTransaction({ account, ...multicall3FromTx(calls) });
// Or, from a smart account (Circle SCA / MSCA, ERC-4337, EIP-7702): the same four calls, batched natively.
await wallet.sendCalls({ account, calls: asCalls(calls) });- Custody never moves. Arc's Multicall3From runs each call through the CallFrom precompile, so the vault and the market both see the depositor as the sender.
- The contract has the last word. If the quote moves past
maxPremium, the trigger is firing or the pool cannot back the cover, the whole batch reverts: no deposit without its cover. - Also in the SDK:
coverCallsfor an existing position,marketCallsandpoolCallsencoders,policiesOf,claimableIds, andplanDrill, which drives the live drill. - Typed from the ABIs.
riderMarketAbi,riderPoolAbi,riderPolicyAbiandcanaryVaultAbiare exported as const, so viem infers every read and write;arcMainnetcarries every deployed address.
An agent can protect its own deposits, inside a budget it cannot raise.
packages/mcp speaks MCP over stdio. It only ever touches the four live markets; the drill market and any other address are refused.
list_markets—Live markets with readings, capacity and pricing, plus the pool's free capital.quote_covermarket, coverage_usdc, daysPremium in USDC and the exact expiry.protect_depositvault, amount_usdc, days, holder?Deposit into a covered vault and buy matching cover in one transaction.buy_covermarket, coverage_usdc, days, holder?Cover for a position the holder already has.my_policiesholder?Policies held, with state and any payout owed.incident_statusmarketWhen an incident opened, whether it is confirmed, and the claim deadline.claimpolicy_idPush a payout. Anyone can; the money always goes to the policy holder.git clone https://gitlab.com/RaYYeR220/rider && cd rider
pnpm install && pnpm --filter @rider/sdk build
# unsigned mode: returns calls for the agent's own wallet
npx tsx packages/mcp/src/index.ts{
"mcpServers": {
"rider": {
"command": "npx",
"args": ["tsx", "/path/to/rider/packages/mcp/src/index.ts"],
"env": {
"RPC_URL": "https://rpc.mainnet.arc.io",
"RIDER_AGENT_KEY": "0x…",
"RIDER_MAX_PREMIUM_USDC": "1",
"RIDER_DAILY_PREMIUM_USDC": "5",
"RIDER_MAX_COVERAGE_USDC": "500"
}
}
}
}- Signing is optional. Without
RIDER_AGENT_KEYthe server signs nothing and returns the batch, both as a Multicall3From transaction and as plain calls, for the agent's own wallet to send. - Budget-capped, fail-closed. With a key, every purchase is checked against
RIDER_MAX_PREMIUM_USDC,RIDER_DAILY_PREMIUM_USDC(rolling 24 hours) andRIDER_MAX_COVERAGE_USDCbefore anything is signed. If any limit is missing or malformed, it signs nothing. Spend is reserved before sending, so two calls cannot share the same headroom;RIDER_BUDGET_FILEkeeps the tally across restarts. - The holder is pinned. When the server signs, shares and policies always go to the signing wallet. A tool call, or an instruction injected into one, cannot send the agent's money anywhere else.
- Simulated first. Every signed batch is simulated against Arc before it is sent; a batch that would revert is refused and its budget released.
Anyone can run the keeper. The contracts decide what it may do.
packages/keeper reads every live market every few seconds and sends the one action each one needs. Every action is permissionless and simulated first; without KEEPER_KEY it only reports what it would send.
# watch the four live markets and act whenever the contracts allow it
RPC_URL=https://rpc.mainnet.arc.io KEEPER_KEY=0x… pnpm --filter @rider/keeper start
# dry run: no key, prints what it would send and exits
pnpm --filter @rider/keeper once- Active, reading past the line
openIncident()- Pending, confirmation delay over
resolveIncident()- Triggered, claim window open
claimMany(claimable ids)- Triggered, claim window closed
settle()- Settled, reading trusted again
rearm()
Options: KEEPER_INTERVAL_MS (default 5000), KEEPER_INCLUDE_DRILL=1 to also drive the canary market, RIDER_DEPLOYMENT_FILE to point at another deployment. Claims are pushed to the policy holder; the keeper cannot redirect them.
A new risk is one small contract.
A market is generic; what makes it cover a vault, a peg or a reserve is its trigger. Implement IRiderTrigger, add tests beside Triggers.t.sol and open a pull request.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
/// @notice A parametric trigger turns an on-chain reading into a loss ratio.
/// @dev Every reading is "higher is healthier" (share price, peg ratio, reserve backing), so a
/// conservative observation is always the larger of two readings.
interface IRiderTrigger {
/// @notice Value recorded on a policy at purchase. Vault triggers return the current share price;
/// triggers measured against a fixed target return that target (1e18).
function referenceValue() external view returns (uint256);
/// @notice Current reading and the loss it implies relative to `ref`.
/// @return ok false when the data cannot be trusted (stale, non-positive, call reverted).
/// Callers must fail closed on `ok == false`: no sales and no incidents.
/// @return lossBps loss in basis points, 0..10_000.
/// @return observed the raw reading the loss was computed from.
function read(uint256 ref) external view returns (bool ok, uint256 lossBps, uint256 observed);
/// @notice Loss implied by a past reading, used to price claims at the confirmed observation.
function lossOf(uint256 ref, uint256 observed) external view returns (uint256 lossBps);
/// @notice Short machine-readable kind: "vault-drawdown", "peg", "reserve".
function kind() external view returns (string memory);
/// @notice The asset, vault or feed the trigger watches.
function subject() external view returns (address);
}- Higher is healthier. Share price, peg ratio, reserve backing: losses are measured down from a reference, and claims are priced at the healthier of two readings.
- Fail closed. When
readreturnsok == falsethe market neither sells nor opens incidents. A broken feed must never look like a loss. - Notice before it sells. Markets are created through the factory. One added to a pool that already has underwriters waits a 4-day notice, longer than the 3-day exit, so capital that disagrees can leave first.
- Self-describing.
kind()andsubject()let the app, indexers and agents describe a market without knowing its trigger in advance; the SDK'smarketForVaultfinds cover for a vault by its subject.
// import { arcMainnet } from "@rider/sdk"
{
policy: "0xe1c34dC766499a379Ec7c05ee0E8F1c28C9c1AdB",
factory: "0xcBCAF27Ce7B90c630e838D5bF47B1888487436FD",
pool: "0x0cE73d0DFB401eB62498c2a49994B40a48c2Cd52",
galaxyMarket: "0xdFC4922A0498f97Dcd3788c701a38084847Ec2f1",
keyrockMarket: "0x00b489A45d6922FcE8fa559C2747F31a5d541743",
eurcMarket: "0xeBf07458fd00cD0ecAfF5B3816975a29c18E376D",
cirbtcMarket: "0xf5E377960A9c690d85d9ab4831Edc7F88C7667f9",
drillPool: "0xe0c1fe5A321FD8C8b228Fe59AB84BcE07Ba21c98",
canary: "0x94981CddA8EFD8Aef008DfD8aF650B938613fc44",
canaryMarket: "0x0A223bf13D5FFcA9F056af57169D88fAa34058fB",
}