Documentation
GlassPay issues scoped, revocable spending cards from your wallet. Any agent plugs one in over MCP and pays within your limits, holding no keys and no funds, dead the moment you revoke. Here is how it works, end to end.
Overview
Agents need to spend money. Handing an agent your private key is reckless; funding a standalone agent wallet loses both your custody and your limits. GlassPay takes the model the card industry settled on decades ago and applies it to agents: the wallet stays the account, and the agent gets a card, a scoped authority to draw from it.
- Your wallet is the account. Funds never leave it until the moment of payment.
- The card is a delegation. A scoped ERC-7710 delegation, signed by your wallet, wrapped in caveats: budget per period, per-transaction cap, merchant allowlist, expiry, usage count, contract scope.
- The agent holds the card, not the money. What the agent gets is an MCP endpoint URL. Behind it, the card can spend only what its terms allow, signed by an agent key that holds nothing.
- Revoke kills it instantly. Freeze or revoke a card (or its whole sub-card tree) and every payment stops, server-side immediately and on-chain underneath.
GlassPay runs on Base mainnet with real USDC. The only simulated leg is the Visa rail (Stripe test-mode Issuing), labeled honestly wherever it appears.
How a Payment Works
- You sign in to the dashboard (Privy embedded wallet, Google or email) and issue a card with terms, set by hand in the composer or drafted from plain language by the Venice-powered compiler.
- The dashboard compiles those terms into on-chain caveats, your wallet signs the delegation in the browser, and the server stores it alongside a fresh agent key that holds nothing.
- You hand the card URL to any agent (one
claude mcp add, a Cursor deeplink, a pasted connector URL). - When the agent calls
pay, the server validates the terms, then redeems the delegation through the 1Shot Public Relayer: gasless, on Base mainnet, settled in USDC from your wallet. - Every charge lands in the card's ledger with memo, fee, and tx hash, attributed to the agent key that spent it.
The agent never sees a private key, never holds a balance, never needs ETH. The first spend even deploys your wallet's EIP-7702 smart-account code automatically, attached to the same redemption as an authorization list.
Issuing a Card
A card is born from a CardTerms object: a pay budget, a contract scope, or both, plus lifecycle limits (expiry, max uses, per-charge cap, merchant lock, sub-cards on/off). You can write the terms by hand in the composer, or describe the card in plain language and let the compiler draft them.
The plain-language compiler
The dashboard's issue modal sends your sentence to Venice AI, which returns a plan of named entities ("USDC", "Uniswap", "aave") and numbers. The server then resolves every name against its own verified registry (or Basescan, or your own pasted address), so the model output can never place a raw address into a draft. The result is a CardTerms draft you review and sign; nothing is issued until you do.
The compiler only names tokens, protocols and merchants. Addresses come exclusively from the trusted resolvers or your own text, with provenance shown on each chip (registry, Basescan, or your input). A draft cannot smuggle a poisoned address even if the model tries.
The client-signed ceremony
Issuance is a three-step prepare / sign / finalize so the server never holds your key:
- prepare: the server compiles the caveats, mints the agent key, and returns the exact unsigned delegation struct.
- sign: your embedded wallet signs the EIP-712 delegation in the browser.
- finalize: the server verifies the signature recovers to your wallet, then persists the card and returns its URL.
Card Terms
Each term compiles to a delegation-framework enforcer caveat at the root of the delegation, so the chain enforces the same limits the server checks. Below is the exact mapping (engine/src/compiler.ts).
| Term | Meaning | On-chain enforcer |
|---|---|---|
pay.period | Budget per rolling window (amount + seconds, min 60s) | ERC20PeriodTransfer |
pay.lifetime | Total USDC the card may ever move | ERC20TransferAmount |
contract.targets | Contracts the card may call | AllowedTargets |
contract.selectors | Method signatures the card may call | AllowedMethods |
expiry | Unix time after which nothing redeems | Timestamp |
maxUses | Redemption count (scaled to executions on-chain; server is the binding limit) | LimitedCalls |
| revocation nonce | Always present; bumping it nukes every card from this wallet | Nonce |
| pay + contract | A composite card; one group governs each redemption | LogicalOrWrapper |
perTxMax and merchantsare not root caveats; they collide with the mandatory fee leg there. They are server-side carve policy applied at redemption: the per-transaction max is backstopped on-chain by the carved leaf's amount scope, while the merchant allowlist is enforced server-side. contract.tokens and contract.perTradeMax additionally pin each ERC-20 allowance to an exact spender and amount via byte-window AllowedCalldata caveats on that leaf.
Connecting an Agent
The card is served over MCP (Streamable HTTP). There are three connection lanes. The first two carry a per-card credential directly; the third is OAuth, where the agent never holds the card secret.
Works everywhere, including credential-free clients like claude.ai web. The URL is the password, treat it like one.
claude mcp add --transport http glasspay \ https://<host>/c/<card-secret>/mcp
For clients that send an Authorization header. The bare endpoint, secret in the header.
claude mcp add --transport http glasspay \ https://<host>/mcp \ --header "Authorization: Bearer <card-secret>"
Add the bare endpoint with no credential. The client discovers the OAuth lane (RFC 9728 metadata on the 401), registers itself (dynamic client registration), and opens a browser; you sign in and pick which card to grant. The agent receives a short-lived, card-scoped, independently revocable token, never the raw secret. This is the lane OAuth-only clients such as ChatGPT require. Clients that finish OAuth out of band read the code off the consent screen: OpenClaw completes with openclaw mcp login glasspay --code <code>, and headless Hermes uses the same paste-back.
claude mcp add --transport http glasspay https://<host>/mcp
Per-harness one-liners (Lane A)
codex mcp add glasspay --url https://<host>/c/<secret>/mcp openclaw mcp add glasspay --url https://<host>/c/<secret>/mcp --transport streamable-http # flag required: omitting it defaults to SSE hermes mcp add glasspay --url "https://<host>/c/<secret>/mcp" gemini mcp add -t http glasspay https://<host>/c/<secret>/mcp goose session --with-streamable-http-extension "https://<host>/c/<secret>/mcp" amp mcp add glasspay https://<host>/c/<secret>/mcp droid mcp add glasspay https://<host>/c/<secret>/mcp --type http
Lanes A and B work in Cursor, VS Code, Gemini CLI, Windsurf, claude.ai custom connectors, or any MCP client that speaks Streamable HTTP. For claude.ai web, paste the card URL under Customize → Connectors → Add custom connector; for ChatGPT Developer Mode, add it as a No Authentication connector (or use Lane C for a real auth story). The dashboard's connect panel renders a prefilled install affordance per harness. Rotate the secret any time from the dashboard; the old URL dies instantly.
MCP Tools
The tool list a card exposes is its permission surface: a pay-only card never sees execute; a contract-only card never sees pay; a sub-cards-off card never sees issue_subcard. The server is stateless, a fresh instance per request, identity = the card credential.
| Tool | On | Purpose |
|---|---|---|
card | Every card | Live state: remaining budget, terms, expiry, recent charges, sub-cards, and the card's on-chain account (the root delegator that holds the USDC and receives contract-call output). Call it first. |
pay | pay cards | Send USDC on Base within limits; blocks until confirmed on-chain. |
paid_fetch | pay cards | Fetch a URL; on HTTP 402 (x402), pay automatically and return the content. |
fiat_pay | pay + Stripe | Buy over Visa rails (simulated) against the same budget; with settlement on, the receipt carries the on-chain tx. |
card_credentials | pay + Stripe | Reveal the test-mode virtual Visa for a merchant checkout; every card auto-links one on first need. |
execute | contract cards | Run scoped contract calls (approve + swap, stake, mint) atomically in one redemption. |
issue_subcard | sub-cards on | Mint a tighter child card for a sub-agent; omitted money terms inherit the parent's remaining budget; returns its URL. |
revoke_subcard | sub-cards on | Instantly kill a sub-card and its descendants (server-side). |
Typed refusals
Refusals come back as isError with structured JSON naming the violated term, so an agent can relay them honestly instead of guessing. The codes include:
over_period_limit,merchant_not_allowed,price_exceeds_max(pay and paid_fetch)target_not_allowed,method_not_allowed,per_trade_exceeded,token_not_allowed,spender_not_allowed(execute)exceeds_parent_terms(issue_subcard);not_your_subcard(revoke_subcard)card_frozen,no_fiat_card(the fiat leg);invalid_terms(bad input)
Contract Cards
A card can be scoped to specific contract targets and method selectors instead of (or alongside) a USDC budget. The agent calls execute with either {target, method, args} (the server ABI-encodes the calldata) or {target, data} raw calldata for tuple/array/ multicall methods like Uniswap exactInputSingle.
- Targets and selectors outside the card's declared scope are refused before anything reaches the chain; the on-chain
AllowedTargets/AllowedMethodsenforcers check the same scope again at redemption. - Method signatures are normalized to canonical form (
uint→uint256) so the encoder, the raw-data selector check, and the on-chain enforcer all agree. - A contract card can carry an allowance token list (
contract.tokens: the only tokens it mayapprove, each approval pinned on-chain to an exact spender and amount; the listed tokens are auto-unioned into the card's targets) and a per-trade ceiling (contract.perTradeMax, capping each USDC approval; v1 enforces the ceiling on the USDC / settlement leg only, while non-USDC approvals stay exact-amount pinned). - For a call that needs a recipient (e.g.
exactInputSingle'srecipient), thecardtool surfaces the card's on-chainaccount(the root delegator that holds the USDC and receives any output tokens), so the agent routes a swap's output there itself rather than guessing or asking the user. - Contract calls carry no native ETH value in v1 (the carved leaf caps value at 0 on-chain); up to 5 calls batch atomically into one redemption.
Contract calls are not USDC-metered. Safety on a contract card is the target/method allowlist plus maxUses and expiry. Pair contract scope with a pay cap in one composite card when you want both rails under one delegation.
Sub-Cards & Revocation
Sub-cards are ERC-7710 redelegations. An agent holding a card can mint a tighter child for a sub-agent with issue_subcard; every term must fit inside the parent's (caps only narrow downward, contract scope is subset-only, never silently inherited). exceeds_parent_terms names the violating field. The chain enforces the same subset via the delegation chain.
Three layers of off-switch
| Layer | Effect | Where |
|---|---|---|
| Freeze | Reversible pause; the card still answers card but refuses spends | Server-side, instant |
| Revoke | Permanent; the card and its whole sub-card subtree die | On-chain disableDelegation, signed by your wallet |
| Nuke | Kills every card and sub-card this wallet ever issued | One on-chain NonceEnforcer bump |
All three are user-operable from the dashboard. On-chain revoke and nuke are signed by your own embedded wallet in the browser (an admin leaf delegation) and ride the relayer gaslessly. Revoking a parent kills the subtree; the cascade is the demo money-shot, revoke the root and the whole tree dies on screen.
An agent's own revoke_subcardis a server-side kill: instant, and the sub-card's URL dies, but a sub-card cannot be disabled on-chain on its own (its on-chain delegator is the parent's agent key). On-chain permanence for a whole branch comes from revoking the root card or nuking.
Payment Rails
Two payment rails run off one delegation, metered by the same enforcers.
x402 (real, live)
paid_fetchanswers an HTTP 402 challenge by paying through the card's 7710 delegation: real x402 v2 flows on Base mainnet, USDC settled from your wallet through the 1Shot Public Relayer (gasless, fee in USDC). GlassPay also ships the first ERC-7710 x402 facilitator (/facilitator/verify, /settle, /supported advertising assetTransferMethod: erc7710) and a demo seller at /demo/premium-data whose 402 points back at it.
Stripe Issuing Visa (simulated)
fiat_pay and card_credentialsdrive a test-mode virtual Visa. When a charge is authorized, Stripe calls GlassPay's real-time auth webhook, which answers approve/decline from the card's on-chain delegation state inside Stripe's hard 2-second window (read from a cached snapshot, never an RPC call in the handler). A decline comes back typed, from the card's terms, not the merchant.
With settlement enabled, an approved Visa charge then settles as a real delegated USDC transfer on Base, through the same enforcers that meter the crypto rail. One budget, two rails. A charge whose settlement cannot land parks settlement_unconfirmed and freezes the card rather than ever releasing its budget.
The Visa leg is simulated by design: Stripe test-mode Issuing, no real merchant, no KYC required in test. It is labeled honestly everywhere it appears. The crypto rail and the on-chain settlement move real USDC on Base mainnet.
The demo merchant, s0nder supply co. at /shop, is a real storefront that accepts the cards' Visas. The catalog is priced at $5 or less because approved purchases move real USDC.
Security
- Custody.Your funds stay in your wallet. The per-card agent key signs redelegations only; it holds no assets and is encrypted at rest. You can export your wallet's private key from the account menu at any time (through Privy's secure modal, rendered in a separate-domainiframe GlassPay never reads) and walk away to any client.
- Dashboard auth. Per-user Privy sessions, verified server-side against the app JWKS. At onboard, the embedded wallet signs
glasspay-onboard:v1:<did>to bind the wallet to that login; every card route is then scoped to the authenticated user's own cards. - Issuance integrity. The server verifies the delegation signature recovers to the delegator before persisting a card.
- Card secrets. 256-bit, stored as a hash for auth and AES-256-GCM-encrypted at rest for the reveal/rotate feature. The URL is a credential; rotate it like a password.
- Limits enforced twice.Server-side at call time (typed refusals) and on-chain at redemption. Period, lifetime, expiry, usage count and contract target/method have dedicated on-chain enforcers; the per-transaction max and merchant allowlist are server-side policy, backstopped on-chain by the carved leaf's amount scope.
- MCP surface hardening. Host allowlist (DNS-rebinding guard), per-card and bad-secret rate limits, a 1 MiB body cap, an SSRF guard on
paid_fetchtargets, secrets never echoed in errors or logs. - OAuth tokens. Opaque, card-scoped, hash-stored beside the card secrets, audience-pinned (RFC 8707), and revoked the instant the card is, cascading to the subtree.
SigNoz Observability
GlassPay is fully instrumented with OpenTelemetry and ships traces, metrics, and logs to SigNoz Cloud (and can self-host locally via the included casting.yaml). Every card issuance, every payment, every refusal, every API call — all visible in SigNoz.
Architecture
Env vars (already set on Railway)
OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.us2.signoz.cloud OTEL_EXPORTER_OTLP_HEADERS=signoz-ingestion-key=YOUR_KEY OTEL_TRACES_EXPORTER=otlp OTEL_METRICS_EXPORTER=otlp OTEL_LOGS_EXPORTER=otlp
The OTel SDK initializes early via Bun --preload (packages/server/src/otel.ts) so auto-instrumentation wraps every module from boot. The engine package (packages/engine/src/telemetry.ts) declares all custom metrics and structured log functions — 5 counters, 2 log emitters, available for any SigNoz dashboard panel.
Traces — Distributed Tracing
Every API request is wrapped in a root span by Hono middleware (app.ts) with attributes for route pattern, HTTP method, URL, and response status code. Inside those (and running on their own intervals), six custom business-logic spans carry domain-specific attributes.
Route-level spans (every request)
The Hono app.use("*", ...) middleware creates a span for every request, named HTTP {METHOD} {ROUTE}. Navigate to SigNoz → Traces, filter by service.name = glasspay-server, and see every API call with its duration, status, and route pattern.
stripe_webhook_auth
Fires when Stripe calls the real-time auth webhook. Attributes: decision (approve/decline), card_id, amount, merchant. Traces the full auth decision flow inside Stripe's 2-second window.
1shot_relayer_redeem
Fires on every on-chain payment. Attributes: usdc_amount (string), gas_fee_usdc, memo, tx_hash, card_id. Shows the full lifecycle of a USDC redemption through the 1Shot Public Relayer.
nl_compile
Fires when Venice AI compiles a plain-language card request. Attributes: prompt_tokens, completion_tokens, model. Tracks AI usage for cost monitoring.
reconcile_sweep
Runs on a configurable interval (default 5 min). Attributes: reconciled (count), still_pending (count). Resolves stuck pending charges against chain truth.
fiat_settle_sweep
Runs on a configurable interval (default 60s). Attributes: settled (count), left (count). Settles approved Visa charges as on-chain USDC transfers.
Metrics — Product KPIs
Five custom counters are available in SigNoz Metrics. Navigate to SigNoz → Metrics and search for any of the following metric names to build dashboard panels.
| Metric Name | Type | Description |
|---|---|---|
glasspay_cards_issued_total | Counter | Total cards issued across all users (root + sub-cards). Increments on issue, finalize, and sub-card mint. |
glasspay_usdc_spent_total | Counter | Total USDC spent across all confirmed redemptions and fiat settlements. The dollar volume metric. |
glasspay_active_cards | UpDownCounter | Current live cards (issued − revoked). A gauge: add 1 on issue, subtract 1 on revoke/nuke. |
glasspay_charges_total | Counter | Total charges processed (confirmed + pending + failed). Payment throughput metric. |
glasspay_errors_total | Counter | Total API-level errors (403 refusals, 422 validation errors, 502 relay failures, 500 exceptions). |
These metrics are created in packages/engine/src/telemetry.ts using the OpenTelemetry Metrics API and exported via OTLP HTTP to SigNoz. They appear in the Metrics explorer under their metric names, prefixed by the engine package.
Building a metric panel
In SigNoz, create a new dashboard, click New Panel, choose Time Series, then switch to the ClickHouse query tab. The SigNoz metrics storage uses the signoz_metrics.distributed_samples_v2 table. Example query for cards issued:
SELECT toStartOfInterval(
toDateTime(intDiv(timestamp_ms, 1000)),
INTERVAL 5 MINUTE) AS ts,
sum(value) AS value
FROM signoz_metrics.distributed_samples_v2
WHERE metric_name = 'glasspay_cards_issued_total'
AND ts BETWEEN $start_datetime AND $end_datetime
GROUP BY ts
ORDER BY tsStructured Logs — Event-Driven Observability
GlassPay emits structured logs for every significant card lifecycle event. Navigate to SigNoz → Logs and filter by card_event, charge_event, refusal_reason, or operation to see exactly what happened.
Card lifecycle events
| Attribute | Events | Context |
|---|---|---|
card_event | issued, frozen, unfrozen, revoked, nuked, url_revealed, secret_rotated, onboarded | Every card lifecycle transition with card_id and extra attributes (k_agent_address, address, has_auth7702 for onboarded). |
charge_event | confirmed | Successful payments with amount (string), kind (crypto/fiat), and card_id. |
refusal_reason | over_period_limit, merchant_not_allowed, price_exceeds_max, card_frozen, … | Typed refusal with attempted_amount and card_id. Every declined payment leaves a structured trace. |
operation | HTTP {METHOD} {ROUTE} | API error logs with status code, error_message, and route info. Every 403/422/502/500 is logged. |
Example log searches
card_event: "issued"— see every card being created in real-timecard_event: "revoked"— track card revocationsrefusal_reason: *— all payment refusals with typed reasonscharge_event: "confirmed"— successful payments with amountsoperation: *— all API errors grouped by operation
Logs are emitted using the OpenTelemetry Logger API (@opentelemetry/api-logs) through the logger.emit() function. Each log carries its severityText (INFO, WARN, ERROR) and structured attributes that SigNoz indexes automatically.
SigNoz Dashboard & ClickHouse Queries
Create a GlassPay dashboard in SigNoz with panels for every metric and trace attribute. Below are the ClickHouse queries for each panel type.
Panel 1: Cards Issued Over Time
Panel Type: Time Series. Shows the rate of card issuances over time.
SELECT toStartOfInterval(
toDateTime(intDiv(timestamp_ms, 1000)),
INTERVAL 5 MINUTE) AS ts,
sum(value) AS value
FROM signoz_metrics.distributed_samples_v2
WHERE metric_name = 'glasspay_cards_issued_total'
AND ts BETWEEN $start_datetime AND $end_datetime
GROUP BY ts
ORDER BY tsPanel 2: Active Cards (Gauge)
Panel Type: Value (big number). Shows the current live card count.
SELECT sum(value) AS active_cards FROM signoz_metrics.distributed_samples_v2 WHERE metric_name = 'glasspay_active_cards' AND timestamp_ms > toUnixTimestamp(now()) * 1000 - 60000
Panel 3: USDC Spent
Panel Type: Time Series. Tracks cumulative USDC volume.
SELECT toStartOfInterval(
toDateTime(intDiv(timestamp_ms, 1000)),
INTERVAL 5 MINUTE) AS ts,
sum(value) AS value
FROM signoz_metrics.distributed_samples_v2
WHERE metric_name = 'glasspay_usdc_spent_total'
AND ts BETWEEN $start_datetime AND $end_datetime
GROUP BY ts
ORDER BY tsPanel 4: API Error Rate
Panel Type: Time Series. Track error spikes.
SELECT toStartOfInterval(
toDateTime(intDiv(timestamp_ms, 1000)),
INTERVAL 5 MINUTE) AS ts,
sum(value) AS errors
FROM signoz_metrics.distributed_samples_v2
WHERE metric_name = 'glasspay_errors_total'
AND ts BETWEEN $start_datetime AND $end_datetime
GROUP BY ts
ORDER BY tsPanel 5: API Request Duration by Route
Panel Type: Time Series. Uses trace data to show P99 latency per API route.
SELECT toStartOfInterval(timestamp, INTERVAL 5 MINUTE) AS ts,
attributes_string['http.route'] AS route,
avg(durationNano) / 1000000 AS avg_ms
FROM signoz_traces.distributed_signoz_index_v2
WHERE resources_string['service.name'] = 'glasspay-server'
AND ts BETWEEN $start_datetime AND $end_datetime
GROUP BY ts, route
ORDER BY tsThe bodyAttributes access pattern varies by SigNoz version. In newer versions, use bodyAttributes['http.route']. The $start_datetime and $end_datetime variables are automatically provided by the SigNoz dashboard panel builder.
SigNoz MCP — AI-Agent Observability
The included casting.yaml ships a SigNoz MCP server alongside the SigNoz stack. This lets your AI agent query traces, logs, and metrics directly — and even create dashboards and alerts autonomously.
Connect your agent to SigNoz MCP
# Self-hosted (from casting.yaml): claude mcp add signoz http://localhost:8000 \ --header "Authorization: Bearer glasspay_mcp_dev" # SigNoz Cloud (API-based): claude mcp add signoz https://signoz.io/api/mcp \ --header "Authorization: Bearer $YOUR_SIGNOZ_API_KEY"
Available MCP tools
Once connected, your agent can use SigNoz MCP tools for observability workflows:
- signoz_search_docs — Search SigNoz documentation for guides and references.
- signoz_create_dashboard — Create new dashboards with panels for GlassPay metrics.
- signoz_modify_dashboard — Update existing dashboard panels and configurations.
- signoz_create_alert — Set up alerts for error rate spikes, card issuance stalls, etc.
- signoz_investigate_alert — Deep-dive into alert-triggered incidents with neighbor signals.
- signoz_generate_query — Generate ClickHouse queries for GlassPay observability data.
- signoz_explain_dashboard — Understand existing dashboard layouts and panel semantics.
- signoz_manage_views — Create and manage saved views for quick data exploration.
Example: ask your agent "Create a SigNoz dashboard for GlassPay showing cards issued, USDC spent, and API error rate" — it will use the MCP tools to build the entire dashboard without you touching the SigNoz UI.
Alerts & Self-Hosting
Recommended alerts
Set up these alerts in SigNoz to monitor GlassPay health:
| Alert | Condition | Severity |
|---|---|---|
| High Error Rate | glasspay_errors_total rate > 10/min for 5 min | Critical |
| No Cards Issued | glasspay_cards_issued_total has no new value for 30 min | Warning |
| High API Latency | P99 HTTP duration > 5000ms for 5 min | Warning |
| Refusal Spike | Log count with refusal_reason:* > 20/min | Warning |
| Charge Failure | charge_event:confirmed rate drops by 50% vs previous hour | Critical |
Self-hosted SigNoz (local development)
The repo includes a casting.yaml for deploying SigNoz locally using Foundry. This is perfect for development and testing without sending telemetry to the cloud.
# Deploy the full SigNoz stack: foundryctl cast -f casting.yaml --locked # SigNoz UI: http://localhost:3301 # OTLP HTTP: http://localhost:4318 # SigNoz MCP: http://localhost:8000 # Then set the server env: OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 OTEL_TRACES_EXPORTER=otlp OTEL_METRICS_EXPORTER=otlp OTEL_LOGS_EXPORTER=otlp
The casting.yaml.lock pins every Docker image to its content digest, ensuring reproducible deployments. Judges can run foundryctl cast -f casting.yaml --locked to reproduce the exact SigNoz environment used during development.
Stack components
| Service | Image | Purpose |
|---|---|---|
| ClickHouse | clickhouse/clickhouse-server:24.12 | Time-series database storing all traces, metrics, and logs |
| OTel Collector | signoz/signoz-otel-collector:0.119.3 | Receives OTLP from GlassPay and writes to ClickHouse |
| Query Service | signoz/query-service:0.81.0 | SigNoz backend: API for dashboards, alerts, and queries |
| Frontend | signoz/frontend:0.81.0 | SigNoz Web UI at port 3301 |
| MCP Server | signoz/mcp-server:latest | AI-agent observability: expose SigNoz tools to your agent |
The self-hosted stack requires 4 GB RAM, 2 CPU cores, and 20 GB disk for ClickHouse data. For production, use SigNoz Cloud at signoz.io — the same OTLP exporter configuration works with just a different endpoint and ingestion key.
API Reference
The server is one Hono process. The dashboard API lives under /api; the MCP endpoint, OAuth lane, x402 facilitator and demo surfaces sit at the root.
Auth lanes (every /api route)
- Admin (
Authorization: Bearer <GLASSPAY_ADMIN_TOKEN>): full access, server-side scripts only, never shipped to a browser. - Privy (
Authorization: Bearer <Privy access token>): verified offline against the app JWKS; every route scoped to the authenticated user.
Dashboard API (/api)
| Method · Path | Purpose |
|---|---|
POST /onboard | Register the embedded wallet + its 7702 auth + onboard proof |
POST /cards/prepare | Compile caveats, mint the agent key, return the unsigned delegation |
POST /cards/finalize | Attach the browser signature, persist the card, return its URL |
POST /cards/compile | Venice NL → draft CardTerms (never issues) |
GET /cards | List the user's cards |
GET /cards/:id | Card detail + charge ledger |
GET /tree | The card → sub-card tree |
GET /cards/:id/url | Reveal the card URL |
POST /cards/:id/rotate | Rotate the card secret (old URL dies) |
GET /cards/:id/fiat | The linked test-mode Visa (owner view) |
POST /cards/:id/freeze · /unfreeze | Reversible server-side pause / resume |
POST /cards/:id/revoke/prepare · /finalize | Client-signed on-chain revoke (sub-cards die server-side) |
POST /nuke/prepare · /finalize | Client-signed cascade nuke of every card |
DELETE /cards/:id | Bookkeeping removal of a dead card + its subtree |
GET /oauth/request · POST /oauth/approve · /deny | The card-picker consent backend |
OAuth 2.1 (self-hosted authorization server)
Public clients, PKCE S256, auth-code + rotating refresh, dynamic client registration. Tokens are opaque (glsp_at_ access, glsp_rt_ refresh), audience-pinned, and die with the card.
| Endpoint | Spec |
|---|---|
GET /.well-known/oauth-protected-resource/mcp | RFC 9728 protected-resource metadata |
GET /.well-known/oauth-authorization-server | RFC 8414 AS metadata |
POST /register | RFC 7591 dynamic client registration |
GET /authorize | Validates, then 302s to the dashboard card-picker |
POST /token | authorization_code + refresh_token grants, PKCE S256 |
POST /revoke | RFC 7009 revocation (kills the whole token family) |
MCP, facilitator + demo
| Endpoint | Purpose |
|---|---|
ALL /c/:secret/mcp | Lane A: secret in the path |
ALL /mcp | Lane B (bearer) + Lane C (OAuth token) |
GET /supported · POST /verify · /settle | The ERC-7710 x402 facilitator (under /facilitator) |
GET /demo/premium-data | x402-protected demo seller (0.01 USDC) |
GET /shop/products · POST /shop/checkout | The demo merchant API |
GET /health | Liveness + engine version |
Self-Hosting
A Bun monorepo, three packages: engine (the pure card engine), server (Hono: REST + MCP + facilitator + Stripe webhook + demo shop) and dashboard (Next.js). Real money moves on Base mainnet, so use small budgets.
bun install cp .env.example .env # then set the two required vars below bun dev # server on :4070 bun run --cwd packages/dashboard dev # dashboard on :4071
Required environment
| Var | Purpose |
|---|---|
GLASSPAY_MASTER_KEY | 32-byte hex key; encrypts agent keys + card secrets at rest |
GLASSPAY_ADMIN_TOKEN | Ops bearer token for the management API (server-side only) |
GLASSPAY_PRIVY_APP_ID | Dashboard lane: enables per-user Privy auth against the app JWKS |
Common optional environment
| Var | Purpose |
|---|---|
GLASSPAY_PUBLIC_MCP_BASE | Public origin for card URLs (also arms the MCP Host allowlist) |
GLASSPAY_CORS_ORIGINS | Comma-separated allowed origins for the API + shop |
STRIPE_SECRET_KEY | Stripe TEST-mode key (sk_test_/rk_test_ only); enables the fiat leg |
GLASSPAY_STRIPE_WEBHOOK_SECRET | Real-time auth webhook secret; unset = fiat leg disabled (503) |
GLASSPAY_FIAT_SETTLEMENT | 1 = approved Visa charges settle on-chain as real USDC |
VENICE_API_KEY · VENICE_MODEL | Enables /cards/compile; pin the model id |
GLASSPAY_DASHBOARD_BASE | Dashboard origin hosting the OAuth consent page |
GLASSPAY_RPC_URL · NEXT_PUBLIC_BASE_RPC | Base RPC for server + client reads (default mainnet.base.org) |
GLASSPAY_DB_PATH | SQLite path (default .dev/glasspay.sqlite) |
GLASSPAY_ALLOWED_HOSTS | Extra Host headers accepted on the MCP endpoint (e.g. a platform fallback domain) |
BASESCAN_API_KEY | Verified-contract labels from Basescan when resolving compiled drafts |
GLASSPAY_TRUST_PROXY_HOPS | Trusted proxy hops for client-IP rate limiting (default 1 = Railway edge) |
GLASSPAY_MCP_RATE_LIMIT · GLASSPAY_MCP_BAD_SECRET_LIMIT | Per-card and per-IP-bad-secret request ceilings per minute (240 / 30) |
GLASSPAY_OAUTH_ACCESS_TTL · GLASSPAY_OAUTH_REFRESH_TTL | OAuth access / refresh token lifetimes in seconds (3600 / 2592000) |
GLASSPAY_OAUTH_REDIRECT_HOSTS | If set, restricts OAuth https redirect-URI hosts to this allowlist (recommended in prod) |
Contracts (Base mainnet · chain 8453)
| Contract | Address |
|---|---|
DelegationManager | 0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3 |
Stateless7702 delegator impl | 0x63c0c19a282a1B52b07dD5a65b58948A07DAE32B |
USDC | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
The Cook Off
GlassPay was built for the MetaMask Smart Accounts Kit × 1Shot API × Venice AI Dev Cook Off. The hard gate, Smart Accounts Kit in the main flow, is the product itself: every card is a SAK delegation, signed by a Privy-provisioned embedded smart account, and every spend redeems it on-chain.
| Track | What GlassPay does |
|---|---|
| x402 + ERC-7710 | paid_fetch pays HTTP 402 through the card's 7710 delegation; real x402 v2 on Base mainnet |
| Best Agent experience | One URL is the whole integration; typed refusals; an OAuth lane for consent UX |
| Agent-to-agent | issue_subcard redelegates narrower authority; revoke + nuke kill whole subtrees in one signature |
| Venice AI | The issue modal compiles plain language into signed card terms (model names, registry resolves) |
| 1Shot Relayer | Every redemption rides the 1Shot Public Relayer, gasless, fees in USDC |
GlassPay uses programmatic ERC-7710 Delegations, not ERC-7715 Advanced Permissions: the 7710 caveat set is richer than the 7715 grant catalog allows, so there is no wallet_requestExecutionPermissions path.
Where in the code
Direct links into the public repo for each track, following the MetaMask DevRel submission guideline.
- Delegations. Create:
issueRootCard(caveats viacompileCard). Redeem:spend.ts. - Redelegation (A2A).
issueSubCardattenuates the parent and builds a child delegation bound to the parent's hash. - x402. Server:
facilitatorRoutes. Client (erc7710 asset transfer):buildX402Payload. - 1Shot. Every redemption rides the relayer:
Relayer.send(relayer_send7710Transaction). - Venice.
veniceChatorchestrated bycompileIntent.
Ready to issue one? Open the dashboard, sign in, and your first card takes about a minute.