Docs

Rill is continuous USDC payment streams on Arc. A sender locks USDC that vests to a recipient linearly, per second, via the on-chain StreamPay contract.

Overview

Three actions make up the whole protocol:

  • Create a stream: recipient, amount, and a start/stop window. The deposit is pulled into the contract.
  • Withdraw: the recipient pulls whatever has vested so far, at any time.
  • Cancel: either party ends it. Vested-but-unwithdrawn funds go to the recipient, the rest returns to the sender.

Network

NetworkArc Testnet
Chain ID5042002
RPChttps://rpc.testnet.arc.network
StreamPay0xd981229808c89e1689e025E7c5367d1154F1899D
USDC (ERC-20, 6 decimals)0x3600000000000000000000000000000000000000

USDC is Arc's native asset; amounts are in base units (6 decimals), so 1000000 is 1 USDC.

Integrate

The stream helpers live in this repo at site/lib/stream(network config, the StreamPay ABI, and typed client + math functions). Copy that folder into your app, or call the contract directly with viem and the address below. Set up clients against Arc:

import { createPublicClient, createWalletClient, http } from "viem";
import { arcTestnet, defineArcChain } from "@/lib/stream";

const chain = defineArcChain(arcTestnet);
const publicClient = createPublicClient({ chain, transport: http(arcTestnet.rpcUrl) });
const walletClient = createWalletClient({ chain, transport: http(arcTestnet.rpcUrl), account });

Create a stream

Approve (handled for you) and open a stream. Returns the new stream id.

import { createStream, arcTestnet } from "@/lib/stream";

const now = Math.floor(Date.now() / 1000);
const { streamId } = await createStream(
  { net: arcTestnet, publicClient, walletClient, account },
  {
    recipient: "0x…",
    deposit: 10_000_000n,   // 10 USDC
    startTime: now,
    stopTime: now + 3600,   // over 1 hour
  },
);

Withdraw & cancel

import { withdraw, cancelStream } from "@/lib/stream";

// recipient pulls a specific amount of vested USDC
await withdraw({ net: arcTestnet, publicClient, walletClient, account }, streamId, 500_000n);

// either party ends the stream and splits the balance
await cancelStream({ net: arcTestnet, publicClient, walletClient, account }, streamId);

Stream math

The SDK mirrors the contract exactly, so a UI can tick the streamed value locally without an RPC call per frame.

import { readStream, streamedAt, withdrawableAt } from "@/lib/stream";

const stream = await readStream({ net: arcTestnet, publicClient }, streamId);
const now = Date.now() / 1000;
streamedAt(stream, now);       // total vested at 'now'
withdrawableAt(stream, now);   // vested minus already withdrawn

Ready to try it? Open a stream on the app.