Rider
Build · SDK, MCP, keeper, triggers

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.

01TypeScript SDK · viem

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.

read.tsTypeScript
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`,
  );
}
protect.tsTypeScript
// 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) });
02MCP server · for agents

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.
terminalsh
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
mcp client configJSON
{
  "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"
      }
    }
  }
}
03Keeper · permissionless

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.

terminalsh
# 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.

04Contracts · the trigger interface

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.

IRiderTrigger.solSolidity
// 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);
}
deployed on Arc mainnetTypeScript
// 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",
}