Tessera
Try the demo
[D0]Docs · Base Mainnet

Build credit for AI agents on Base.

Tessera is a USDC credit protocol for AI agents. Settlement history goes in, programmable credit lines come out. This page is the engineering surface: how the underwriter scores agents, how to integrate the SDK, and how every product on the platform fits together.

Live on Base mainnet · v0.0.1 SDK · subgraph synced

[01]

What is Tessera

Tessera is the first credit card built for AI agents. At its core it’s a USDC line of credit on Base, with limits underwritten on-chain from the agent’s settlement history. Your agent settles invoices through the protocol, that history becomes its credit file, and the limit it can draw against scales with the volume and reliability of its settled work.

The protocol is permissionless on the borrower side — the underwriter is the chain. No KYB, no application, no human in the loop. Default and the limit tightens; pay back on time and it grows. All visible on-chain.

Tessera Credit is currently a simulated demo at /demo — try it to feel exactly how it’ll work the day it ships. The surrounding products (vault, pay-me, agent dashboard, public ledger) are in development and unlock product-by-product after the credit launch.
[02]

Quickstart

Three lines of config gets you on the rails. The SDK ships with all addresses, subgraph URL, and link generators preset for Base mainnet.

install.shbash
npm install @tessera/sdk viem
agent.tstypescript
import { Tessera, TESSERA_BASE_MAINNET } from "@tessera/sdk";
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base } from "viem/chains";

const wallet = createWalletClient({
  account: privateKeyToAccount(process.env.AGENT_PK as `0x${string}`),
  chain: base,
  transport: http(),
});

const tessera = new Tessera({
  ...TESSERA_BASE_MAINNET,
  walletClient: wallet,
});

// Read your agent's current credit picture
const profile = await tessera.getAgentProfile(wallet.account.address);
console.log("Lifetime settled:", profile.settledVolume);
console.log("Repayment rate:",  profile.repayRate);
console.log("Computed limit:",  profile.creditLimit);   // Phase 1

Need a richer starter? github.com/cmxdev1/Tessera/tree/main/examples has working agents for each common flow.

[03]

The product suite

Tessera is a protocol of four live surfaces and three planned ones. Everything reads from the same on-chain state.

Live · Phase 0

Tessera Credit

USDC credit line for AI agents. Limits underwritten on-chain from settlement history.

Try the demo
Building

Lender Vault

ERC-4626 vault. Deposit USDC, fund agent draws, earn the spread linearly toward maturity.

Live · Phase 1

Tessera Pay

Shareable USDC pay-me links. The on-ramp that builds credit history one settlement at a time.

Get a link
Building

Public Ledger

Every invoice, draw, and repayment on the protocol. Filterable, expandable, on-chain receipts.

Building

Agent Dashboard

Bill another agent. Repay invoices billed to you. One screen for both sides of agent settlement.

Phase 1

Escrow

Deliverable-locked payments for one-off agent hires. Buyer locks, seller delivers, release on confirmation.

Phase 1

Reputation credentials

Portable on-chain credentials built from settlement history. Citable across protocols.

Phase 2

MCP server

Drop-in MCP integration for Claude, Cursor, and other agent frameworks. Wraps the SDK.

[04]

Credit underwriting

Limits are computed from on-chain inputs only. No human in the loop, no application, no quarterly review. The formula is public and runs against the Tessera settlement subgraph. Limits update as new invoices settle.

underwriter.tstypescript
function computeCreditLimit(profile: AgentProfile): number {
  const { settledVolume, avgInvoice, repayRate } = profile;

  const raw =
    settledVolume * 0.40 +       // 40% of lifetime volume
    avgInvoice    * 8    +       // 8x the average ticket
    repayRate     * 5_000;       // up to $5k for perfect repayment

  return Math.min(50_000, raw);  // $50k initial cap
}

Signal weights — what the underwriter actually reads

SignalSourceWeightWhy it matters
settledVolumesubgraph× 0.40Bigger track record → bigger line. Linear.
avgInvoicederived× 8Bigger tickets → bigger working-capital ceiling.
repayRatesubgraph× $5,000Reliability premium. 100% on-time → +$5k.
capprotocol$50,000Initial ceiling, lifts as vault TVL and underwriting data deepen.

What happens when an agent defaults

A missed repayment is recorded on-chain and permanently dents the agent’s repayment rate — the same number the underwriter reads for every future draw. Lenders take the principal loss (no insurance pool in v0). Default events are public on the protocol ledger and shown on the agent’s profile page (both surfaces unlock in a future phase).

[05]

Settlement protocol

The substrate beneath the credit product. Three primitives feed the same on-chain state: invoice (retainer / project work), escrow (deliverable-locked one-offs, Phase 1), and reputation (the credential you build by using either).

Invoice lifecycle

  1. 01createInvoice
    Originator calls the SDK with the buyer’s address, face value in USDC, maturity timestamp, and discount in basis points. An invoice record mints on-chain.
  2. 02fundInvoice
    Any lender in the vault can fund the invoice in a single transaction. The discounted advance (e.g. 97% of face at 3% discount) flows directly to the originator’s wallet.
  3. 03Linear accrual
    Spread accrues to vault NAV linearly toward maturity. Lenders hold tsUSDCv1 ERC-4626 shares; NAV ticks up automatically — no claim transactions.
  4. 04repayInvoice
    On or before maturity, the buyer agent calls repayInvoice with face value in USDC. Funds flow back into vault NAV. Repayment rate ticks up; credit limit recomputes.
[06]

SDK reference

@tessera/sdk is a TypeScript client for any Node.js or Edge runtime. Accepts any viem WalletClient — bring your own signer.

New here? Start at the SDK quickstart at tesseracredit.com/sdk — installation, capabilities overview, and a 30-second integration walkthrough. This section is the full method reference.

On-chain writes

writes.tstypescript
// Originator side
await tessera.createInvoice({
  buyer: "0x…",
  faceValueUsdc: 1000n * 10n ** 6n,
  maturityUnix: Math.floor(Date.now() / 1000) + 14 * 86400,
  discountBps: 300,                        // 3%
});

// Buyer side
await tessera.repayInvoice(invoiceId);

// Vault side
await tessera.deposit(amountUsdc);
await tessera.redeem(shares);

// Phase 1: credit-line operations (simulated today)
await tessera.drawCredit(amountUsdc);
await tessera.repayCredit(amountUsdc);

Reads (subgraph)

reads.tstypescript
const profile = await tessera.getAgentProfile(address);
// { settledVolume, avgInvoice, repayRate, monthsOnBase,
//   settledInvoices, computedCreditLimit }

const score = await tessera.getReputationScore(address);
// 0..100, weighted blend of repayRate, age, volume

const recent = await tessera.recentInvoices({
  agent: address,
  limit: 25,
});

Helpers

helpers.tstypescript
// Link generators (use anywhere, no wallet needed)
tessera.getPayLink(invoiceId);             // /pay/<id>
tessera.getProfileLink(address);           // /a/<address>
tessera.getPayMeLink(address);             // /pay-me/<address>

// Async wait helpers — for agent automation
await tessera.waitForFunded(invoiceId,  { timeoutMs: 60_000 });
await tessera.waitForRepaid(invoiceId,  { timeoutMs: 60_000 });
await tessera.waitForDraw(drawId,       { timeoutMs: 60_000 });
New: the SDK also exports signInWithTessera and verifyTesseraSession — a SIWE-compatible auth primitive for dapps that want to ID their agent users in one call. See Sign in with Tessera below or try the live demo at /signin.
[07]

Sign in with Tessera

A SIWE-compatible auth primitive. One SDK call gets a dapp cryptographic proof an agent owns the wallet (standard EIP-4361 SIWE message) plus their full Tessera profile — ecosystem, score, credit tier, history. Drop-in identity for any dapp that wants to ID their agent users and price them by creditworthiness.

Try it live at tesseracredit.com/signin — connect a wallet, sign once, watch the session JSON + verify response render side-by-side.

1. Client — request a signed session

client.tstypescript
import { signInWithTessera } from "@tessera/sdk";
import { useWalletClient } from "wagmi";

const { data: walletClient } = useWalletClient();

const session = await signInWithTessera(walletClient, {
  statement: "Sign in to MyAgentApp to verify your Tessera identity.",
});

// session = {
//   address,                // lowercased wallet
//   domain, uri, chainId,
//   nonce,                  // anti-replay
//   issuedAt, expiresAt,    // ISO 8601
//   message,                // EIP-4361 SIWE message — drop-in for any SIWE consumer
//   signature,              // standard ECDSA hex
//   profile: {
//     ecosystem,            // "virtuals" | "openclaw" | "cdp" | "generic" | "new" | "unknown"
//     ecosystemLabel,
//     score?,               // 0–100 (Tessera Score, Phase 0 live)
//     tier?,                // New / Bronze / Silver / Gold / Platinum / Diamond
//     percentile?,          // 0–99 rank against the broader agent population
//     creditLineEstimate?,  // USDC whole dollars (Phase 0: capped at $25k)
//     hasPayHistory?,       // Phase 1+
//     hasSettlements?,      // Phase 1+
//   },
// }

2. Server — verify the session

Two options. Either verify locally with the SDK (zero network calls, pure crypto), or POST to the hosted endpoint if your runtime can’t bundle viem.

server-local.tstypescript
// Option A — verify with the SDK (recommended)
import { verifyTesseraSession } from "@tessera/sdk";

export async function POST(req: Request) {
  const session = await req.json();
  const result = await verifyTesseraSession(session, {
    expectedDomain: "myagentapp.com",  // pin to your domain
    maxAgeSeconds: 3600,               // reject sessions older than 1h
  });

  if (!result.valid) {
    return new Response(`unauthorized: ${result.reason}`, { status: 401 });
  }

  // result.address is the verified wallet — trust it.
  await mySession.create({ user: result.address });
  return new Response("ok");
}
server-remote.tstypescript
// Option B — POST to the Tessera hosted endpoint
const res = await fetch("https://www.tesseracredit.com/api/auth/verify", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    session,
    options: { expectedDomain: "myagentapp.com", maxAgeSeconds: 3600 },
  }),
});
const { valid, address, reason } = await res.json();
if (!valid) throw new Error(`unauthorized: ${reason}`);

Verification reasons

When valid: false, the response carries a machine-readable reason:

  • session_expired — current time is past expiresAt.
  • session_too_old — exceeded the caller’s maxAgeSeconds.
  • domain_mismatch — session was issued for a different domain than expected.
  • invalid_signature — signature does not recover to session.address.
  • signature_verification_error — verification threw (malformed signature or RPC failure for EIP-1271 smart accounts).
Drop-in compatible. The signed payload is a standard EIP-4361 message and the signature is a standard ECDSA hex — anything that already speaks SIWE will accept it. Even if a dapp stopped using Tessera tomorrow, existing sessions would still verify cryptographically.
[08]

x402 payments

Tessera ships paid endpoints under the x402 Payment Protocol — the HTTP-native standard for agent-to-API micropayments on stablecoins. Agents pay per-call in USDC on Base; we return the same data the free endpoints serve. The paid path is built for x402-native integrators that want a wallet-native consumption flow.

Dedicated product page → /x402 covers the full pitch, pricing table, standards + source, and the four-phase Private x402 roadmap with visual breakdowns. This section is the engineering reference — request / response shapes, settlement guarantees, error semantics.

Live endpoints — what’s on the rails today

Two endpoints are live on Base mainnet, both implementing the full x402 v2 wire format so any v2-aware client or discovery indexer can consume them.

  • GET /api/x402/score/[address] — paid Score lookup, $0.001 USDC. Same response shape as the free /api/score/[address].
  • GET /api/x402/agent/[address] — full agent profile in one call (Score + tier + percentile + credit-line estimate + five-input breakdown + Agent Directory record if listed), $0.002 USDC. One call replaces three.

The free /api/score and /api/agent endpoints stay free for SDK consumers; these paid paths are for agents that natively speak x402 and want a wallet-native consumption flow.

The 402 response (what discovery sees)

Tessera implements x402 v2 — the latest spec with CAIP-2 network identifiers and the renamed PAYMENT-SIGNATURE header. Required by modern x402 discovery indexers.

402-response.jsonjson
{
  "x402Version": 2,
  "error": "PAYMENT-SIGNATURE header is required",
  "accepts": [{
    "scheme": "exact",
    "network": "eip155:8453",
    "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "payTo": "0x...your-x402-recipient",
    "maxAmountRequired": "1000",
    "resource": "https://www.tesseracredit.com/api/x402/score/0xabc...",
    "description": "Tessera Score for any Base wallet — 0-100 credit score, tier, percentile, credit-line estimate, full input breakdown.",
    "mimeType": "application/json",
    "maxTimeoutSeconds": 60,
    "extra": { "name": "USD Coin", "version": "2" }
  }]
}

Calling it as an x402 client

Use the v2 @x402/fetch (or @x402/axios) wrapper — it intercepts 402 responses, signs an EIP-3009 payment authorization with your agent’s wallet, and retries with the PAYMENT-SIGNATURE header automatically.

agent-paying-x402.tstypescript
import { wrapFetchWithPayment } from "@x402/fetch";
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount(process.env.AGENT_PK as `0x${string}`);
const fetchWithPayment = wrapFetchWithPayment(fetch, account);

// One call. The wrapper handles the 402 + payment + retry under the hood.
const res = await fetchWithPayment(
  "https://www.tesseracredit.com/api/x402/score/0xd8dA6BF2..."
);
const { score, tier, percentile, creditLineEstimate, paidVia } =
  await res.json();

// paidVia: "x402" confirms the response came from the paid path.

Settlement model

Payment verification + settlement run through the public x402 Foundation facilitator on Base mainnet. The flow:

  • Client signs an EIP-3009 TransferWithAuthorization for the requested amount, recipient, and network (eip155:8453 for Base mainnet, CAIP-2 format).
  • Server forwards the signed authorization to the facilitator, which verifies the signature and submits the USDC transfer on-chain.
  • Settlement only happens AFTER the route handler returns successfully — so clients aren’t charged for indexer failures or invalid input.
  • Response includes a PAYMENT-RESPONSE header confirming settlement.
Why we built this. The free /api/score/[address] endpoint covers SDK consumers + dapp gating. The x402 variant exists so agents paying-as-they-go via the standard have a native way to consume Tessera Score, and so Tessera surfaces on every x402 discovery service. Revenue from paid lookups will eventually route to a FeeTreasury that buys + burns $TESSERA.

Privacy extension — encrypted responses (Phase 1, live)

Both paid endpoints support an opt-in Tessera Encrypted Response extension. The agent provides an ephemeral X25519 public key in a request header; the server encrypts the response body to that pubkey via ECIES (X25519 + HKDF + AES-256-GCM) before returning. Once sent, the server cannot decrypt its own output — only the agent’s wallet can.

The SDK ships wrapFetchWithEncryption — wrap any fetch (including wrapFetchWithPayment) and the encryption is invisible. Payment-outer, encryption- inner composition; your downstream code is unchanged.

agent-paying-with-encryption.tstypescript
import { wrapFetchWithPayment } from "@x402/fetch";
import { wrapFetchWithEncryption } from "@tessera/sdk";
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount(process.env.AGENT_PK as `0x${string}`);

const fetchPaying    = wrapFetchWithPayment(fetch, account);
const fetchEncrypted = wrapFetchWithEncryption(fetchPaying);

// One call — payment + encryption both invisible.
const res = await fetchEncrypted(
  "https://www.tesseracredit.com/api/x402/score/0xd8dA6BF2..."
);
const { score, tier } = await res.json();   // already decrypted

Want the raw wire format? See it in flight on /privacy/x402 — three panes showing your agent keypair, the encrypted envelope on the wire, and the cleartext after local decrypt. Or roll your own without the SDK:

raw-wire-format.shbash
# 1. Agent generates X25519 keypair locally (any X25519 lib)
# 2. Sends pubkey (base64url, 43 chars) in the request header

curl https://www.tesseracredit.com/api/x402/score/0xabc... \
  -H "PAYMENT-SIGNATURE: <signed-payment>" \
  -H "X-Tessera-Encrypt-Pubkey: <base64url X25519 pubkey>"

# Response:
#   X-Tessera-Encrypted: true
#   Content-Type: application/json
#   { "v": 1,
#     "scheme": "ecies-x25519-aes256gcm",
#     "ephemeralPubkey": "<server's per-request pubkey>",
#     "iv": "<base64url, 12 bytes>",
#     "ciphertext": "<base64url, body + AES-GCM auth tag>" }
#
# 3. Derive shared secret via X25519 with server's ephemeralPubkey
#    Run HKDF-SHA256 with salt = ephemeralPubkey || ourPubkey,
#    info = "tessera-pay-memo/v1/aes256gcm". Decrypt via AES-256-GCM.

Backwards compatible — clients that don’t send the header get the same cleartext response as before. The 402 discovery response itself is never encrypted (clients need to read accepts[] to construct a payment).

Phase 1 of 4. Tessera is building toward complete private x402 — encrypted responses (live), stealth recipient addresses (next), Pedersen amount commitments, full ZK shielded sender pool, plus a credit-gated overlay that restricts privacy access to wallets with a Tessera Score attestation. Roadmap in docs/private-x402.md.

Roadmap — more paid endpoints

Score + Agent are the first two. Building next:

  • POST /api/x402/attest — Tessera-signed attestation of a derived claim (e.g. “score ≥ 60”, “tier ≥ Gold”, “has zero defaults”). The agent attaches this attestation to other dapps without re-revealing their wallet history. Foundational primitive for Phase 5 credit- gated privacy.
  • POST /api/x402/score/batch — N addresses per request, priced per-address. For underwriters + dapp gating systems that need bulk lookup.
  • GET /api/x402/receipts/[txHash] — third-party verification of any Tessera Pay receipt. Compliance + audit use cases.
  • Pay-on-credit (Phase 1+): agents with high-enough Tessera Score can call paid endpoints on credit — Tessera underwrites; agent settles weekly. The credit identity layer becomes the credit substrate of x402 itself.
[09]

Subgraph

Every counter on every product page is derived from the Tessera subgraph. Public endpoint, no auth required.

endpoint.shbash
https://api.studio.thegraph.com/query/1749787/tessera-base/v0.0.1

Useful queries

queries.tstypescript
// Lifetime profile for a single agent
const QUERY_AGENT_PROFILE = `
  query AgentProfile($id: ID!) {
    agent(id: $id) {
      id
      settledInvoiceCount
      settledVolume
      repayedOnTime
      repayedLate
      defaulted
      firstInvoiceAt
    }
  }
`;

// Recent invoices, paginated
const QUERY_RECENT_INVOICES = `
  query Recent($limit: Int!, $skip: Int!) {
    invoices(
      first: $limit, skip: $skip,
      orderBy: createdAt, orderDirection: desc
    ) {
      id  faceValueUsdc  status
      originator { id }  buyer { id }
      createdAt  maturityAt  repaidAt
    }
  }
`;
[10]

Vault mechanics

The lender side. Capital flows from depositors into approved agent draws and invoices; spread accrues back to depositor share NAV.

  • ERC-4626 standard. Share token is tsUSDCv1. Deposit USDC, receive shares; redeem shares, get USDC back at current NAV.
  • Linear accrual. No claim transactions. NAV ticks up automatically as funded paper accrues toward maturity.
  • Withdrawal queue. If liquidity is fully deployed, redemption requests sit in an FIFO queue and clear as positions mature. No lockup — queue is the backstop, not a fixed term.
  • Manual underwriting (v0). Each invoice is approved by the protocol operator (a Gnosis Safe on Base) before lenders can fund it. Programmatic underwriting based on the formula above is a Phase 1 contract upgrade.
  • 25-30% target APY. Reflects the rate the market needs to clear at while the protocol is new and lenders are taking real risk on novel agent counterparties. Expect normalization toward 15-20% as repayment data accumulates.
[11]

Roadmap

  • Phase 0 · live

    Simulated credit demo

    The credit playground is live at /demo so you can preview the full experience. Surrounding products (vault, pay-me, agent dashboard, public ledger) are parked in 'Building' status and unlock product-by-product.

  • Phase 1

    Real credit draws + programmatic underwriting

    drawCredit / repayCredit on a live contract. The formula above replaces manual underwriting for sub-cap draws. Escrow product ships.

  • Phase 1.2

    Multi-tenor pools + reputation credentials

    Separate vault pools by tenor (7/14/30/60 day) so lenders can pick duration risk. Portable reputation credential surfaces as a standalone primitive.

  • Phase 2

    MCP + the broader integration surface

    Native MCP server for Claude / Cursor / etc. Multi-chain expansion if the unit economics hold. Insurance vault as a separate yield product.