SourcePay is a payment router for AI citations on Arc. Creators register wallet-signed source material, buyers route research requests across registered sources, SourcePay generates payable receipts, and creators are paid in Arc Testnet USDC. The web app settles payments as direct on-chain USDC transfers verified against the Arc Testnet ledger; the standalone CLI autonomous agent settles through Circle's x402 Gateway batching using signed EIP-3009 authorizations.
Live app: https://sourcepay.fly.dev/
SourcePay now separates public marketplace data from private wallet-owned workspace data.
- Public buyer marketplace: registered creator sources that can be routed.
- Private creator workspace: sources and earnings scoped to the connected payout wallet.
- Private buyer receipts: unpaid receipts recoverable only by the buyer wallet that created them, or by the private access-token receipt URL.
- Public receipts: only paid or settled receipts are listed publicly.
- Receipt proof: receipt proof can be downloaded and verified against stored route data.
- Payments: the web app pays creators with direct Arc Testnet USDC transfers (buyer wallet or agent wallet), verified against on-chain transaction receipts. The standalone CLI autonomous agent (
scripts/agent-runner.mjs) instead settles through Circle's x402 Gateway batching facilitator using signed EIP-3009 authorizations.
- Registers creator-owned sources with payout wallets, citation pricing, content fingerprints, and wallet-signed ownership proof.
- Routes buyer requests to relevant registered sources within a selected USDC budget. A keyword pre-filter narrows candidates, then an LLM call (
server/agent-brain.mjs) decides which of those candidates are actually worth citing for the specific objective and returns a rationale — routing is not pure keyword matching. See "Agent Citation Reasoning" below. - Stores the connected buyer wallet on newly routed receipts so buyers can recover their own unpaid receipts later.
- Generates receipt pages with selected sources, fingerprints, payout amounts, payment status, and proof actions.
- Protects private unpaid receipts with access tokens.
- Protects creator earnings and buyer receipt recovery with nonce-based wallet challenges.
- Guards wallet connection and payment signing against the Arc Testnet chain.
- Tracks creator citation activity and quoted/paid earnings by payout wallet.
- Adds rate limits and structured JSON logs for operational safety.
- React, TypeScript, Vite, Tailwind CSS
- Node HTTP server
- SQLite
- OpenAI (citation routing decisions)
- Direct on-chain USDC settlement (web app), Circle x402 Gateway batching (CLI autonomous agent)
- Arc Testnet
- viem
- Fly.io
- Creator source registration requires a payout-wallet signature.
- Creator source editing and archiving require the payout wallet.
- Creator Portal loads sources using
/api/sources?wallet=..., not the global marketplace list. - Creator earnings require a fresh signed wallet challenge.
- Buyer receipt recovery requires a fresh signed wallet challenge.
- Auth challenges include purpose, nonce, expiry, and one-time consumption.
- Unpaid receipts are not publicly listed.
- Public source detail only shows paid or settled citation history.
- Sensitive endpoints are rate limited.
- Logs mask wallet addresses and identifiers and do not log signatures, access tokens, or full source content.
SourcePay includes a small logo set in public/:
sourcepay-mark.svg: app header mark and favicon source.sourcepay-logo.svg: horizontal wordmark for presentations or docs.sourcepay-x-avatar.png: square profile image for X and other social accounts.sourcepay-social.png: social preview/banner image.sourcepay-social.svg: editable source for the social preview image.
npm install
cp .env.example .env
npm run server
npm run devFrontend: http://127.0.0.1:5173
Backend: http://127.0.0.1:8787
PORT=8787
SOURCEPAY_DB_PATH=./data/sourcepay.sqlite
SOURCEPAY_NETWORK="Arc Testnet"
ARC_RPC_URL=
SOURCEPAY_ARC_FAUCET_URL=
SOURCEPAY_USDC_FAUCET_URL=
SOURCEPAY_WALLETCONNECT_PROJECT_ID=
CONTENT_REGISTRY_ADDRESS=
SOURCEPAY_AUTO_DEPLOY_CONTENT_REGISTRY=0
AGENT_PRIVATE_KEY=
OPENAI_API_KEY=
OPENAI_MODEL=Optional rate-limit overrides:
SOURCEPAY_AUTH_LIMIT=30
SOURCEPAY_PREVIEW_LIMIT=12
SOURCEPAY_SOURCE_WRITE_LIMIT=20
SOURCEPAY_ROUTE_LIMIT=20
SOURCEPAY_PRIVATE_READ_LIMIT=60
SOURCEPAY_PAYMENT_REQUIREMENTS_LIMIT=60
SOURCEPAY_PAYMENT_SUBMIT_LIMIT=20
SOURCEPAY_PROOF_VERIFY_LIMIT=30ARC_RPC_URL should point to the Arc RPC endpoint used for the event. Faucet URLs should point to the official Arc testnet and USDC claim pages for the event. If faucet URLs are not configured, SourcePay falls back to Circle's faucet.
SOURCEPAY_WALLETCONNECT_PROJECT_ID is the Reown/WalletConnect project ID. It enables WalletConnect QR/app connections for mobile wallets such as OKX Wallet. Browser-injected wallets still work without it.
AGENT_PRIVATE_KEY is a 32-byte hex private key (with or without 0x prefix) for the autonomous agent's wallet. If provided, the server registers the agent's wallet address, allowing the agent to programmatically sign and pay creators using EIP-3009 TransferWithAuthorization payloads. If not provided, agent-wallet functions are disabled (returning null in config).
CONTENT_REGISTRY_ADDRESS is optional. When it points to a real deployed Arc Testnet content registry, SourcePay will write source fingerprints on-chain. If it is not configured, source registration remains wallet-signed and off-chain only; SourcePay does not create fake registry transaction hashes. SOURCEPAY_AUTO_DEPLOY_CONTENT_REGISTRY=1 can deploy the registry at startup only when AGENT_PRIVATE_KEY is configured and funded, but production deployments should prefer an explicit CONTENT_REGISTRY_ADDRESS.
SOURCEPAY_ENABLE_SOCIAL_PROOF_MOCK=1 is test-only. It accepts mock-x://handle/status/id?text=... URLs so automated tests can exercise X fingerprint verification without calling x.com. Leave this disabled (or unset) in production.
- Open SourcePay.
- Open
Creator portal. - Connect the creator payout wallet with
Browser walletorWalletConnect. - Enter a source URL or paste source material.
- Choose the source class:
Article,Social post, orTranscript. - Set the USDC citation price.
- Click
Prepare sourceif importing from a URL. - Click
Register source. - Sign the wallet message.
The source is registered only if the signature matches the payout wallet and the source fingerprint.
Anti-copy ownership rules:
- Exact content bodies (normalized text + class) can only be claimed once while registered. Another wallet cannot re-register the same article/post/transcript text.
- Social post requires a linked X handle and the public
x.com/.../status/...URL from that handle. Free-text paste is rejected. - Article requires a linked Medium handle and a Medium URL under that profile. Free-text paste is rejected.
- Transcript may be free-text (
contentTrust: unbound) but is not routing-eligible until the creator completes Verify X social proof (social_proven).
Optional but recommended for stronger real-world identity:
- In the creator portal Identity tab, link your X handle (wallet-signed).
- On a registered source, click Verify X.
- Copy the provided verification message (includes the source fingerprint).
- Post that message publicly on X from the linked account.
- Paste the tweet URL and submit proof (wallet challenge signature required).
SourcePay fetches the post, checks that the fingerprint appears in the text, and checks that the post author matches the linked X handle. Verified sources show a Socially Verified badge and get a slight routing preference.
- Open the
Requeststab. - Connect the buyer wallet with
Browser walletorWalletConnectif the buyer wants receipt recovery. - Enter the research objective.
- Set max spend.
- Choose eligible source types.
- Click
Route request.
SourcePay selects matching wallet-signed creator sources within budget and creates a private payable receipt.
- Open the
Paymentstab. - Click
My receipts. - Connect the buyer wallet with
Browser walletorWalletConnect. - Sign the buyer receipt challenge.
Only receipts created by that buyer wallet are returned. Private unpaid receipts include their access token so the buyer can reopen and pay them.
- Open a receipt.
- Connect the buyer wallet with
Browser walletorWalletConnect. - Switch to Arc Testnet if prompted.
- Review selected sources and total USDC amount.
- Click
Connect and pay/ payment action. - Approve the wallet signature.
After payment, the receipt updates to paid or records the failed payment attempt with a reason.
- Open
Creator portal. - Connect the payout wallet.
- Click
View earnings. - Sign the creator earnings challenge.
Only the signed payout wallet can view its private earnings.
On a receipt page, users can:
- See selected sources, ranks, fingerprints, and payout amounts.
- Copy or share the receipt URL.
- Download receipt proof.
- Verify receipt proof against stored SourcePay data.
- Review payment status and payment history.
Routing a request runs in two stages:
- Keyword pre-filter (
server/index.mjs,routeSources): tokenizes the objective, scores every eligible source by keyword/phrase overlap and trust signals, and narrows to candidates above a relevance threshold. This stage is cheap and deterministic — it's a candidate filter, not the citation decision. - Agent decision (
server/agent-brain.mjs,selectCitationsWithAgent): the pre-filtered candidates (title, kind, price, content excerpt), the objective, and the budget are sent to OpenAI with a strict JSON schema. The model returns, for every candidate, whether it's actually worth citing and why, plus an overall rationale for the selection. Only candidates the model marksworthCitingare added to the receipt (still capped by the buyer's budget in code). The rationale and per-source reasons are stored on the run and returned on the receipt.
/api/route requires OPENAI_API_KEY and fails loudly (no keyword-only fallback) if it's missing or the call errors — a broken agent call should surface as an error, not silently degrade to string matching. See .env.example for OPENAI_API_KEY / OPENAI_MODEL.
SourcePay supports fully autonomous, server-side and CLI agent payments using an agent wallet. When the environment variable AGENT_PRIVATE_KEY is configured, SourcePay enables:
- Auto-payouts via Server API: The app exposes the agent's public address at
/api/config. Users or scripts can request the server-hosted agent wallet to settle receipt payments directly by clicking Pay with Agent Wallet on the receipt UI (provided the agent wallet is funded with Testnet USDC). - CLI Autonomous Agent Runner: A standalone script (
scripts/agent-runner.mjs) demonstrates an autonomous AI agent flow. It:- Takes a search query/objective and budget.
- Queries the router endpoint
/api/routeto find matching sources. - Programmatically signs individual EIP-3009 transfer authorizations using its private key.
- Submits the signed authorizations to the SourcePay settlement endpoint for Gateway validation and execution.
To run the CLI agent locally:
# Set the agent private key (replace with a 32-byte hex key)
$env:AGENT_PRIVATE_KEY="0x..." # Windows Powershell
# or: export AGENT_PRIVATE_KEY="0x..." # Linux/macOS
# Run the agent with an objective and max budget (in USDC)
node scripts/agent-runner.mjs "Arc citation licensing note" 10For the agent to pay creators on Arc Testnet, fund the agent's address with Arc Testnet USDC from the faucet:
- Visit
https://faucet.circle.com - Select Arc Testnet (Chain ID:
5042002) - Input the Agent Wallet address shown when launching the server or running the CLI script.
Holding USDC in the wallet is not enough for x402 Gateway settlement. Circle's Gateway draws from a separate Gateway-internal balance, funded by a one-time onchain deposit into the Gateway Wallet contract (approve() then deposit()) — not just an EOA balance. Without this deposit, agent-runner.mjs reaches Circle's real Gateway API and gets a genuine insufficient_balance rejection. Run this once per wallet before expecting the CLI agent to settle:
$env:AGENT_PRIVATE_KEY="0x..." # Windows Powershell
node scripts/fund-gateway.mjs 1 # deposits 1 USDC into the Gateway Wallet contractnpm test
npm run build
npm audit --omit=devSourcePay runs as one Fly app. The Node server serves the API and the built Vite frontend from dist/.
- Install and sign in with
flyctl. - Create the app, or change the
appname infly.tomlifsourcepayis taken:
fly apps create sourcepay- Create the persistent SQLite volume in the same region as
primary_region:
fly volumes create sourcepay_data --size 1 --region iad- Set Arc/Circle configuration:
fly secrets set ARC_RPC_URL="https://..."
fly secrets set SOURCEPAY_ARC_FAUCET_URL="https://..."
fly secrets set SOURCEPAY_USDC_FAUCET_URL="https://..."
fly secrets set SOURCEPAY_WALLETCONNECT_PROJECT_ID="..."
fly secrets set CONTENT_REGISTRY_ADDRESS="0x..."- Deploy:
npm run deploy:flyThe deploy script uses flyctl deploy --smoke-checks=false because Fly's deploy-time socket smoke scan can report a false listener warning even while the configured HTTP health check passes. The real Fly health check remains enabled in fly.toml.
- Check the deployed service:
fly status
fly checks list
fly logsThe Fly config mounts /app/data and stores SQLite at /app/data/sourcepay.sqlite, so source registrations, receipts, auth challenges, and payment attempts survive deploys and restarts.
Run these checks on https://sourcepay.fly.dev/ after each production deploy:
- Open the app in a clean browser or incognito window.
- Confirm no old wallet appears connected.
- Open Creator Portal without connecting a wallet.
- Confirm it does not show another user's creator inventory.
- Connect Wallet A as creator.
- Register one source and sign the source registration.
- Disconnect Wallet A.
- Connect Wallet B in Creator Portal.
- Confirm Wallet B does not see Wallet A's creator source.
- Return to Requests.
- Connect buyer wallet.
- Route a request that should match the registered source.
- Confirm a private receipt is created.
- Open Payments and click
My receipts. - Sign with the buyer wallet.
- Confirm the private receipt appears.
- Try
View earningsin Creator Portal with the wrong wallet. - Confirm it is rejected.
- Connect the creator payout wallet and click
View earnings. - Confirm earnings load only after signing.
- Open the receipt page.
- Confirm receipt proof downloads and verifies.
- Try the full buyer-usdc payment with funded Arc Testnet USDC (direct on-chain transfer, verified against the transaction receipt).
- Confirm payment status updates and payment history is recorded.
- Confirm Fly logs show structured events without raw signatures or access tokens.
- Real funded-wallet direct USDC payment on Arc Testnet (web app path) and a real funded-wallet x402 Gateway payment via the CLI autonomous agent.
- Circle testnet USDC faucet availability for the buyer wallet.
- Wallet popup behavior across the wallet extension used in the demo.
- Two-browser, two-wallet isolation test.
We refactored and enhanced the codebase based on a full architectural audit:
-
Modular Codebase Structure:
src/types.ts: Holds all TypeScript interface and type declarations.src/utils.ts: Contains Web3 provider setup, helper methods, and API integration layers.src/components/Common.tsx: Holds reusable layout and feedback widgets.- Page-level views (
LandingPage.tsx,PlatformPage.tsx,CreatorPage.tsx,SourcePage.tsx,ReceiptPage.tsx) are now isolated, clean sub-components. src/App.tsxhas been slimmed down to only serve as the main console state machine and navigation router.
-
Fly.io Persistent SQLite Mounting:
- Volume target updated to
/app/datato match the application data directory correctly. - Server dynamically prepares target directories recursively, preventing failures during initial database mounts.
- Volume target updated to
-
On-Chain Settlement Verification & UI Polling:
- Added
checkTransactionSettled(txHash)using native JSON-RPCeth_getTransactionReceiptchecks directly from the Node backend to Arc Testnet. - When a receipt transitions to
paidstatus,ReceiptPage.tsxpolls the server status every 4 seconds. The backend verifies all EIP-3009 transactions are confirmed on-chain, automatically settling the receipt state in the DB and updating the frontend UI dynamically.
- Added
-
Spend Limit Enforcements:
- Budget selection sliders and text inputs are dynamically capped by the buyer's saved policy limit (
maxSpendLimit) on the Requests tab. - Backend
routeSourcesvalidates requests against user policy configuration, returning error messages if the client sends query budgets exceeding their policy limits.
- Budget selection sliders and text inputs are dynamically capped by the buyer's saved policy limit (