Integration Guide

Gate access to your product based on active TrustFlow subscriptions — for human subscribers and autonomous agents alike.

1. Create a Plan

Go to /plans, set a monthly price, and click Create Plan. You'll get a Plan ID (e.g. 1).

Grace period is how long a subscriber keeps access after their deposit runs dry — the window in which a top-up restores service without an interruption. Set it to 0 to cut access the moment the deposit is spent. It's enforced by /api/check (section 3).

2. Add a Subscribe Button

Link your users to the hosted subscribe page:

<a href="https://trustflowonarc.vercel.app/subscribe/YOUR_PLAN_ID">
  Subscribe
</a>

The page handles wallet connection, USDC approval, and stream creation automatically.

If a wallet already has an active stream on this exact plan, checkout is blocked — they can't double-pay themselves. If they have an active stream on a different plan of yours, checkout switches mode: subscribing cancels that stream (refunding its unused deposit) and starts this one, so a subscriber is only ever billed for one of your plans at a time.

Redirect back after payment — without this, the subscriber is just shown a "Streaming started" confirmation and stays on TrustFlow. Add a success query param (must be an https:// URL) pointing back at your own site, and TrustFlow auto-redirects there 5 seconds after the stream is created (with a "Go now" button to skip the wait):

<a href="https://trustflowonarc.vercel.app/subscribe/YOUR_PLAN_ID?success=https%3A%2F%2Fyoursite.com%2Fwelcome">
  Subscribe
</a>

Set this once from /plans — each plan card has a "Redirect after payment" field with a Save button that appends the param to your Copy-link output automatically, so you don't have to hand-build the URL yourself. It's optional per plan and only accepts https:// links (plain http:// or other schemes are ignored).

3. Check Subscription on Your Server

After a user subscribes, verify their status server-side before granting access. Pass their wallet address and your plan ID:

// Node.js / TypeScript
const res = await fetch("https://trustflowonarc.vercel.app/api/check", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ planId: "1", address: "0xUSER_WALLET" }),
});
const { active, remaining } = await res.json();

if (!active) {
  return res.status(403).json({ error: "No active subscription" });
}

// remaining — USDC micro-units left unconsumed in the deposit

4. Let Subscribers Manage Billing On Your Own Site

Subscription management — cancel, top-up, dispute — happens on your site, not TrustFlow's. These are plain onchain writes on the StreamManager and DisputeResolver contracts — call them directly from your own UI with wagmi/viem. No TrustFlow backend involved for writes; reads come from the same indexer/API you're already using. Subscribers never leave your domain.

// Contract addresses (Arc testnet) — same for every merchant
const ADDRESSES = {
  StreamManager: "0xf576f7aF812298B95bB440d6718A8b1d96d54395",
  DisputeResolver: "0xF87B65f0bFe749b0BDd0834D3a808B04c241714F",
  USDC: "0x3600000000000000000000000000000000000000",
};

Cancel — instantly refunds the subscriber's unconsumed deposit, no approval needed:

await writeContractAsync({
  address: ADDRESSES.StreamManager,
  abi: streamManagerAbi, // needs just "cancel(uint256)"
  functionName: "cancel",
  args: [BigInt(streamId)],
});

Top up — approve USDC, then top up (two transactions, standard ERC-20 pattern):

await writeContractAsync({
  address: ADDRESSES.USDC,
  abi: usdcAbi, // needs just "approve(address,uint256)"
  functionName: "approve",
  args: [ADDRESSES.StreamManager, amountWei],
});
await writeContractAsync({
  address: ADDRESSES.StreamManager,
  abi: streamManagerAbi, // needs just "topUp(uint256,uint256)"
  functionName: "topUp",
  args: [BigInt(streamId), amountWei],
});
// amountWei — USDC micro-units (1,000,000 = 1 USDC)

Open a dispute — freezes the disputed amount plus a 1-day-rate bond, refundable if the subscriber's claim is upheld:

const bondWei = BigInt(Math.floor(
  (Number(plan.ratePerSecond) / 1_000_000) * 86400 * 1_000_000
));

await writeContractAsync({
  address: ADDRESSES.USDC,
  abi: usdcAbi,
  functionName: "approve",
  args: [ADDRESSES.DisputeResolver, amountWei + bondWei],
});
await writeContractAsync({
  address: ADDRESSES.DisputeResolver,
  abi: disputeResolverAbi, // needs just "openDispute(uint256,uint256)"
  functionName: "openDispute",
  args: [BigInt(streamId), amountWei],
});
// merchant then has 7 days to respond before it auto-resolves

To find which stream(s) belong to the connected wallet before showing these actions, query the indexer for that address (see Direct GraphQL below) or call /api/check if you already know the plan ID.

5. Autonomous Agents as Subscribers

An AI agent subscribes to your plan the same way a person does — it's just another payer address calling createStream. There is no separate agent API, no allowlist, and nothing for you to build: if your plan works for humans, it already works for agents.

This is the case existing rails can't serve. Agents can't hold credit cards; prepaid credits strand capital, and per-request payment doesn't cover continuous consumption — a six-hour job, a persistent data feed, a long-running service dependency. A per-second stream bills exactly the consumption, and the agent tops it up or cancels it without a human signing anything.

The subscriber loop — what a well-behaved agent does on your plan:

adoptStream    on start, resume an existing live stream if one exists
openStream     approve USDC, deposit a runway, start consuming
monitor        read position each tick, compute remaining runway
topUp          restore runway to target before it expires
assessService  detect degradation → freeze funds → open dispute
cancelStream   stop, reclaim the unspent deposit instantly

State is read fresh from chain each tick, so a restarted agent adopts its existing stream instead of opening a duplicate — worth noting, since your plan blocks a second concurrent stream anyway (see section 2).

Opening a stream — identical to the hosted checkout, minus the UI:

const runway = BigInt(plan.ratePerSecond) * BigInt(runwaySeconds);

await walletClient.writeContract({
  address: ADDRESSES.USDC,
  abi: usdcAbi,
  functionName: "approve",
  args: [ADDRESSES.StreamManager, runway],
});
await walletClient.writeContract({
  address: ADDRESSES.StreamManager,
  abi: streamManagerAbi,
  functionName: "createStream",
  args: [BigInt(planId), runway],
});

Recourse without a human — the part that makes handing an agent a budget safe. An agent receiving degraded service would otherwise just keep paying. Instead it polls a health endpoint each tick and, on failure, freezes what's accrued and escalates onchain itself using the same openDispute call from section 4. No human decides to dispute — the agent does.

Custody — an agent signs one of two ways. With a Circle Agent Wallet, MPC key shares never enter the agent process and Circle enforces a spending policy before any transaction is submitted — that policy is the budget you hand it. With a raw key, the agent holds the key directly; fine for local development, not for a funded agent. On Arc the same USDC balance pays gas, so an agent can never end up funded-but-stuck holding the wrong asset.

A complete reference agent — dual-mode wallet, runway maths, poll loop, and autonomous dispute — ships in the repo at trustflow/agent.

API Reference

POST/api/check

Body (form-encoded or JSON)

planIdstring — your Plan ID from the dashboard
addressstring — subscriber's wallet address (0x…)

Response

{
  "active": true,
  "inGrace": false,
  "streamId": "3",
  "rate": "9000000",
  "consumed": "412336",
  "remaining": "8587664",
  "graceEndsAt": 1785416153,
  "canceledAt": null
}

consumed / remaining / rate are USDC micro-units (1,000,000 = 1 USDC), computed live from onchain state at request time — not a cached snapshot.

active goes false once the deposit is spent and the plan's grace period has elapsed — gate on it alone. inGrace is true while the deposit is exhausted but grace still covers the subscriber: still serve them, and it's the right moment to prompt a top-up. graceEndsAt is the unix second access actually ends (null on an unmetered zero-rate plan).

Direct GraphQL (Advanced)

Query the indexer directly for richer data. Endpoint:

POST https://trustflow-production.up.railway.app/graphql
query {
  streams(where: { planId: "1", payer: "0x...", status: "Active" }, limit: 1) {
    items {
      id deposited claimed consumed status createdAt
    }
  }
}