Contract
The contract is intentionally minimal (~90 lines): it holds USDC per campaign, releases payouts, and refunds unspent budget. It holds no user data — no Telegram IDs, transcripts, or scores are written on-chain. Verification state lives off-chain in Postgres.
Functions
depositBudget(campaignId, amount)
Advertiser locks USDC into escrow for a campaign. Pulls amount via transferFrom, credits campaignBalance[campaignId], and records the caller as campaignDepositor only if not already set (the first-depositor guard). Anyone can add budget to a campaign; nobody can redirect the refund recipient after the first deposit.
Caller: any address (typically the advertiser wallet).
creditDirectDeposit(campaignId, depositor, amount)
Credits USDC that arrived by plain transfer (e.g. a Base Pay payment) to a campaign. Only credits funds that are actually unallocated in the contract (balance − totalHeld). Applies the same first-depositor guard.
Caller: Canvas relayer only.
releasePayout(campaignId, to, amount)
Releases amount USDC from a campaign balance to a single recipient. A passing verification produces two releasePayout calls — one for the group owner leg (90%) and one for the Canvas fee leg (10%) — each tracked separately.
Caller: Canvas relayer only.
refundUnusedBudget(campaignId, amount)
Returns unspent campaign budget to the recorded campaignDepositor. No lock period.
Caller: Canvas relayer only.
withdrawUnallocated(to, amount)
Recovers orphaned USDC not allocated to any campaign (balance − totalHeld).
Caller: Canvas relayer only.
setRelayer(newRelayer)
Rotates the authorized relayer address.
Caller: current relayer only.
campaignBalance(campaignId) · totalHeld()
Views. Remaining escrowed USDC for a campaign, and the sum of all campaign balances. Invariant: totalHeld == Σ campaignBalance.
Events
Payout split
The split is computed off-chain inpayout-batch.ts before the contract calls fire; the contract receives pre-calculated amounts. Per passing verification:
- Owner leg: 90% of the winning bid price → group owner wallet
- Fee leg: 10% of the winning bid price → Canvas platform wallet (
0x8ddD5c22c38d9641B23fe197869DA843fD209b49)
Security
- The relayer wallet (
0xbD5f911E8621Ec144681d17a8b59DcDd3f9356d9) is the only address authorized to move escrowed funds. Its key is held in the production environment only. - The first-depositor guard on
campaignDepositorcloses a dust-deposit refund-hijack vector. - The contract holds USDC only, and advertiser budget is refundable at any time.
- The contract is not yet audited — an external audit is planned before the permissionless marketplace opens (see Roadmap). It runs a whitelist phase with test-scale amounts in the meantime.
Onchain audit trail
Every passing verification is settled byreleasePayout transactions on Base. The owner-leg transaction hash is included in the completion payload delivered to the advertiser, giving a verifiable per-completion settlement record.