Skip to content

feat(payments): Bind referrals through invites and attribute clients via signed metadata - #1080

Open
kotevcode wants to merge 47 commits into
mainfrom
feat/referral-metadata-attribution
Open

kotevcode wants to merge 47 commits into
mainfrom
feat/referral-metadata-attribution

Conversation

@kotevcode

@kotevcode kotevcode commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

An invite-only, two-sided referral program, plus per-client recognized-usage attribution (the builders program). Both ride on the settlement metadata buyers already sign. Specified in AIP-6. Supersedes #1015.

  • Metadata tail (packages/protocol): five words after the services array in SpendingAuth and FreeUsage metadata: clientId, then an invite's issuedEpoch, index and EIP-2098 (r, vs). When r and vs are zero there is no invite. A bare referrer address binds nothing. Metadata versions are unchanged, and the encoding matches the contract test vectors byte for byte.

  • AntseedStatsV2: decodes the tail, forwards the client id to AntseedAttributionUsage and the invite to AntseedReferrals, and emits InviteForwarded. Every forward is wrapped in try/catch, so a rejected invite never blocks a settlement.

  • AntseedAttributionUsage: credits recognized weighted points (from AntseedUsageAccounting, never volume) per epoch to:

    • the client's ERC-8004 agent;
    • the referrer;
    • the referee, during its 12-epoch bonus window.

    It also records each buyer's first-usage epoch.

  • AntseedReferrals (emissions-gate controller):

    • Invites: single use, signed off-chain as EIP-712 Invite(issuedEpoch, index). The referrer is recovered from the signature.
    • Quota: earned from the referrer's buyer and seller activity in the previous epoch. Below 1 USDC there are no invites; otherwise 3, plus 1 per 10 USDC, up to 20.
    • Validity: an invite is valid for 4 epochs, and only new buyers (first usage at most 2 epochs ago) can redeem one.
    • Self-referral: rejected, including a wallet that shares the buyer's operator.
    • Rewards: the buyer's share of the bucket is split 50/50 between referrer and referee for 12 epochs, then goes to the referrer alone. Referees are paid only to their Deposits operator; referrers are paid to their operator if one is set, otherwise to the referrer.
    • previewInvite: returns the failure reason for an invite.
  • AntseedClientRewards (emissions-gate controller): pays the builders bucket pro rata to client points, to the agent's owner.

  • Apps: referral lists, invite counts, payable amounts and binding status come from Antscan (antscan#11), not RPC log scans. They are hidden when no explorer is configured.

    • CLI: antseed referral invite | redeem | status. The buyer daemon carries a pending invite until it binds.
    • Desktop: invite code field on the setup screen, in Preferences, and in a "Have an invite?" card in Rewards; antseed://invite/<invite> deep link; "Invite someone" with the remaining count; invite bonus with Claim; per-buyer invite table.
    • ANTS dashboard: invites, invite bonus, per-buyer table with Earned and Pending columns, and the Builders program card.
    • Website: /invite/<invite> landing page and a Referrals and Invites guide. The CLI reference, FAQ and recognized-usage docs are updated too.
  • Removed: the earlier network-matched attribution: the download-proxy match and visit endpoints with their KV store and secret, the website ?ref= capture, and the Desktop confirmation popup.

Deployment

  1. DeployStatsV2 (writers: Channels and FreeUsage), then AntseedRegistry.setStats(statsV2).
  2. DeployAttributionUsage, then DeployReferrals and DeployClientRewards against it.
  3. Wire them together with statsV2.setReferrals, statsV2.setAttributionUsage and ledger.setReferrals.
  4. Register both controllers as minters with gate setMinter. Their bucket shares are an open issue in AIP-6.
  5. Set chain-config referralsAddress, clientRewardsAddress and clientAgentIds, and set the website's REFERRALS_ADDRESS.

Antscan deploys once the contracts are on Base (blue/green schema).

No mainnet addresses are added here.

Test Plan

  • Foundry: 908 of 909 pass. The one failure is the existing mainnet fork test testFork_m001BaselineMatchesCanonicalDeployment.
  • Node 24 test suites:
    • protocol: 24 pass
    • buyer-core: 13 pass
    • node: 1193 pass
    • CLI: 574 pass
    • Desktop: 12 script, 388 main and 421 renderer pass
    • ANTS: 715 pass
    • download-proxy: 25 pass
  • Website: pnpm run typecheck is clean across the repo, and docusaurus build succeeds.
  • Manual flow: the referrer and new-user invite flows were run end to end in Desktop, the CLI, the dashboard and the website against a local contract and a mock Antscan, including real clicks on the setup-screen invite field.

…errers

Buyers append the referrer wallet and a client id to the settlement
metadata they already sign (SpendingAuth v3, FreeUsage v1). AntseedStats
decodes the tail, records per-client usage and forwards the binding to
the new AntseedReferrals contract on the buyer's first settlement, so the
binding is buyer-signed, gasless and covers free usage. Referrers earn
2% of referred buyers' finalized ANTS usage rewards, accrued and claimed
from the ANTS dashboard.

Desktop asks for confirmation during first-run setup and stores the
answer in referral.json; the buyer daemon carries it until the chain
shows it bound. Download links carry ?ref=<wallet> and the download
proxy keeps a 48-hour HMAC-keyed network match.
@kotevcode kotevcode changed the title feat(payments): Bind referrals through signed metadata feat(payments): Bind referrals and attribute clients via signed metadata Sep 27, 2026
@kotevcode

Copy link
Copy Markdown
Contributor Author

@reviewant re-review

Addressed all findings in 211a950:

  • P1 invite scoped to buyer identity: the node keeps the default identity's attribution separately; addBuyerIdentity() and setBuyerAttribution() give extra identities the client id only (paid and free-usage). Test: packages/node/tests/buyer-identity-attribution.test.ts.
  • Burn cap in sweep tests: EmissionsGateMock.claimRemainder now models the shared per-epoch burn cap; new AntseedReferralsGateSweepTest runs against the real AntseedEmissionsGate + ANTSToken for unused / partial / exhausted caps; client-rewards sweep asserts exact amounts.
  • Explorer bound → verified: Antscan is advisory; bound is persisted and the invite dropped only after referrerOf(buyer) on the configured contract confirms it. RPC failure keeps the invite.
  • CLI test glob: added dist/cli/commands/*.test.js; fixed the no-explorer remaining count (checks every not-yet-known index once), deterministic random source in tests.
  • Created-invite allowance: snapshot applies only to its own epoch (inviteAllowance), referrals refetched after create.
  • Builder removal across remounts: handled registration jobs tracked at module scope + localStorage (takeRegisteredAgents).
  • Pending invite replacement: Desktop shows Change / Remove while invited; new referral:clear-invite IPC; only bound is final.
  • Clearing attribution: both sanitizers strip prior attribution before applying the current one (incl. hydration).
  • Referee claim auth: classified as a connection-scoped action (selected buyer or its operator). Strict buyer scope would reject the selected wallet itself; the claim is permissionless and always pays the operator.
  • Referee claim RPC fallback: unavailable/failed explorer → scan pendingRefereeReward over the last 26 claimable epochs (claim action only; reads stay explorer-only). Same fallback added for the referrer claim on explorer failure.
  • Invite allocation serialized: new withFileLock (in-process queue + exclusive lock file with stale-owner recovery) around read/select/check/sign/persist in issueInvite; concurrency test with one free slot.
  • Referral state scoping: referral.json records keyed by chainId:referralsAddress:wallet for redemptions/bindings and issued indices; legacy unscoped records are treated as unverified.
  • Builder RPC fallback: available:false or rejected explorer → client.pendingRewards(agentId).

Full build + typecheck clean; node 1437, buyer-core 47, cli 1391 (+ new command tests), desktop 379 main / 518 renderer, ants 743, contracts 907 — all passing. (One pre-existing flaky gateway test, favorites.test.js ordering, is unrelated to this branch.)

@reviewant

reviewant commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Superseded by a newer review: #1080 (comment) (previously reviewed 211a950a8b)

@kotevcode

Copy link
Copy Markdown
Contributor Author

@reviewant re-review

Addressed round 2 in ea21c5f:

  • P1 referrer's operator can claim: referral-claim is connection-scoped like referee-claim (server), and /api/referrals/claim is an operator-enabled action in Confirm.tsx. Tests: selected referrer + distinct operator allowed, unrelated wallet rejected.
  • Live lock owner never evicted: the lock moved to packages/node/src/utils/file-lock.ts (exported); the holder heartbeats the lock file's mtime while its task runs, so a live owner is never broken by age and contenders time out (FileLockTimeoutError). Only a dead owner's lock, or one whose heartbeat stopped for staleMs (hung, or pid recycled after a crash), is broken. Cross-process test: holder beyond the stale interval, contender times out.
  • All referral-file mutations serialized: write/save/clear/sync/recordIssuedInvite run under one short referral.lock, with unique temp filenames. Invite allocation keeps its own referral-invite.lock, always taken before the file lock and never inside it (no nesting/deadlock). Regression test overlaps 12 recordIssuedInvite calls with save/clear/write; all survive.
  • Epoch and quota verified on-chain at issuance: issueInvite always reads currentEpoch() and inviteQuota() from the configured contract; Antscan's used indices are used only when their epoch matches, plus the per-index inviteUsed check. Tests: exhausted previous epoch at rollover, explorer response older than the validity window.
  • Saved invite applied before the proxy starts: watchReferralAttribution reads the scoped local state (no explorer/RPC) and applies it after node.start() and before proxy.start(); explorer/chain sync runs afterward in the background with a sequence guard. Test holds the explorer response open and checks the invite is applied on return.
  • Invite bonus refresh: summary, bonus and invites refresh together on entry, window focus and the 60s poll, with latest-response-wins and unmount guards.
  • Create another invite: Copy stays for the current link, plus a "Create another" link while invites remain; the allowance reloads after each create.
  • P3 friendly rewards errors: cachedRead's catch uses friendlyNetworkError (moved to main/payments/rewards-summary.ts so it is testable without Electron); tests for timeout and 429.

Full build + typecheck clean; node 1445, cli 1394/1395 (only the pre-existing flaky favorites.test.js ordering test, untouched by this branch), desktop 381 main / 522 renderer, ants 744, contracts 907.

@reviewant

reviewant commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Superseded by a newer review: #1080 (comment) (previously reviewed ea21c5f05b)

@kotevcode

Copy link
Copy Markdown
Contributor Author

@reviewant re-review

Addressed round 3 in 8d5310f:

  • Builder 20-agent limit: MAX_BUILDER_AGENTS is shared via api-types.ts; the route dedupes and keeps the first 20 in request order (sorting only for the cache key), so a later smaller id can't displace an earlier one. The card disables Add at 20 with a one-line hint, reports a registration that finishes on a full list as "registered but not tracked", and renders every persisted id beyond the response as a row ("—", data unavailable) with Remove. Tests cover a 21st id.
  • Mutually exclusive stale-lock reclamation: reclaiming requires an exclusive <lock>.recover (wx, holds the pid; broken only when its owner is dead, never by age). Under it the reclaimer re-reads the main lock and removes it only if the content is unchanged and still abandoned; otherwise it retries. Multi-process test pauses two reclaimers after inspecting the same dead-owner lock and asserts their critical sections never overlap (fails against the previous code).
  • Chain fallback without an explorer: syncReferralState reads referrerOf when Antscan is absent, failing or reports unbound, on an in-memory backoff (immediately, then 10 min doubling to 1 h, only while an invite is pending, reset on a new redemption). Tests: chain-only binding, explorer outage, the backoff schedule over 131 polls.
  • Invite creation recheck: Desktop and the ANTS dashboard treat the explorer allowance as advisory — when it says nothing is left, a "Check again" link calls the create path, which verifies epoch/quota on-chain. Failures are scoped to their epoch and cleared when a newer allowance arrives; successful creates carry their on-chain epoch so a recheck ahead of Antscan shows the right count. Tests for a stale previous-epoch response and no-quota → refreshed positive allowance.
  • Watcher mtime: the mtime is captured before the state read and stored only with the result it installed (current generation only); a write during the read leaves a newer mtime and the next poll re-reads. Regression test saves an invite between the read and the stat.

Full build + typecheck clean; node 1449, cli 1395/1396 (only the pre-existing flaky favorites.test.js), ants 750, desktop 381 main / 524 renderer, contracts 907.

@reviewant

reviewant commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Superseded by a newer review: #1080 (comment) (previously reviewed 8d5310fdc3)

@kotevcode

Copy link
Copy Markdown
Contributor Author

@reviewant re-review

Addressed the recovery-lock race in 9d00246 by removing compare-and-unlink from claim handling entirely:

  • Only the process that exclusively creates <lock>.reclaim-<sha256 of the abandoned lock's content> may remove that lock. The claim name is unique to that one abandoned lock, so no claim ever has to be unlinked for another reclaimer to proceed — losers of the wx create wait and re-read.
  • If a claimant died holding its claim, that claim is abandoned in turn and claimed the same way one level deeper (<claim>-<sha256 of the claim>), again via exclusive create only (bounded depth).
  • The winner re-reads the main lock and removes it only if it is still the content it claimed, then cleans up the claim chain; a late reclaimer that recreates a claim finds a different lock and leaves it alone.
  • New multi-process test: an abandoned main lock and a dead claimant's claim; both contenders pause after judging the claim abandoned, then are released in turn — critical sections never overlap and no claim files are left. The earlier two-reclaimer test still passes.

node 1450, ants 750, cli 1396 passing; typecheck clean.

@kotevcode

Copy link
Copy Markdown
Contributor Author

@reviewant re-review (latest commit 9d00246 — recovery-lock race fix)

@reviewant

reviewant commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Superseded by a newer review: #1080 (comment) (previously reviewed 9d0024620a)

@kotevcode

Copy link
Copy Markdown
Contributor Author

@reviewant re-review

Addressed round 5 in c47a74c:

  • Inviter-only rewards claim: ReferralInvites now carries the inviter's payable and claimableEpochs (from the referral card's payable view, not totals.ants), and Your invites shows " $ANTS payable · From your invites" with its own Claim ↗ to the rewards dashboard whenever epochs are claimable. Test: zero buyer rewards, no invitee bonus, positive inviter payable.
  • Recheck abandonment after claiming: the claimant removes the lock only if it still has the claimed content and is still abandoned, so a slow holder that refreshed its heartbeat meanwhile keeps it. Test refreshes a stale live holder inside beforeReclaim; the contender times out and the lock is untouched.
  • Canonical lock paths: withFileLock resolves the path to absolute with the directory's symlinks resolved (synchronously, so callers still queue in call order) and uses that identity for the queue, acquisition and release. Test: concurrent relative + absolute spellings run strictly one after the other.
  • Builder claims skip claimed epochs: epochs listed by Antscan are rechecked with pendingReward(agentId, epoch) before sending, so a lagging explorer or someone else's permissionless claim no longer stops the loop at AlreadyClaimed. Test: claimed first epoch is skipped, the payable one is claimed.
  • Invite command wrapping: a CommandChip with a token now wraps at every width via .chip.wrap (two-class specificity, so minifier ordering can't undo it); short commands are unchanged. Checked the built invite page at 768 and 1280 px.

Full build + typecheck clean; all workspace test suites and 907 contract tests pass.

@reviewant

reviewant commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Superseded by a newer review: #1080 (comment) (previously reviewed c47a74c8c1)

@kotevcode

Copy link
Copy Markdown
Contributor Author

@reviewant re-review

Addressed both round-6 findings:

  • Live-holder reclamation (packages/node/src/utils/file-lock.ts): heartbeat-age eviction is gone, along with the heartbeat. A lock whose recorded owner is still running is never broken; contenders time out. A lock is reclaimed only when its owner is dead, when it was taken before the current boot, or (on POSIX, once it is staleMs old) when ps shows the pid's process started after the lock was taken, i.e. the pid was recycled. New multi-process test: the holder is SIGSTOPped inside its critical section past the stale window, the contender times out, then after SIGCONT the sections run strictly one after the other (child:start, child:end, parent:start, parent:end). Invite allocation uses the same lock, so it inherits the fix.
  • Client agent ID (apps/cli/src/config/loader.ts): strings must be a full decimal integer, and the result must be Number.isSafeInteger and positive; anything else warns and falls back. Tests cover 12.5, "12.5", "12junk", "0x0c", "1e3", "" and values above Number.MAX_SAFE_INTEGER.

@reviewant

reviewant commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Superseded by a newer review: #1080 (comment) (previously reviewed 7889f564e5)

@kotevcode

Copy link
Copy Markdown
Contributor Author

@reviewant re-review

Addressed all three round-7 findings:

  • Atomic ownership publication (packages/node/src/utils/file-lock.ts): lock and claim files are now written in full under a private dot-prefixed name and hard-linked into place (link fails with EEXIST when taken), so a lock path never appears without its owner record. A creator paused before publishing has published nothing; after resuming it either links into a free path or waits. Only on filesystems without hard links (EPERM/ENOTSUP/ENOSYS/EXDEV) does it fall back to create-then-write. New multi-process test: the child SIGSTOPs itself after staging and before publishing, the parent enters past the stale window and resumes the child inside its section; the log is strictly parent:start, parent:end, child:start, child:end and no staged files remain.
  • Env client agent ID (apps/cli/src/cli/referral-state.ts): ANTSEED_CLIENT_AGENT_ID now goes through the same parseClientAgentId as the config loader (full decimal string, safe positive integer); invalid values warn and fall back to the configured or chain-default id. Tests cover 12.5, 12junk, 1e3, 0x0c, -4, 0 and 9007199254740993.
  • Platform guards (packages/node/tests/file-lock.test.ts): the SIGSTOP/SIGCONT tests and the recycled-PID (ps) test are it.skipIf(process.platform === 'win32'); dead-owner and pre-boot reclamation stay portable in their own test.

@reviewant

reviewant commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Superseded by a newer review: #1080 (comment) (previously reviewed fed17f8743)

@kotevcode

Copy link
Copy Markdown
Contributor Author

@reviewant re-review

Addressed all four round-8 findings:

  • No create-then-write fallback (packages/node/src/utils/file-lock.ts): when link fails with EPERM/ENOTSUP/ENOSYS/EXDEV, createExclusive now throws FileLockUnsupportedError ("this drive does not support hard links. Move the Antseed data directory to a local disk.") instead of exposing an empty lock. This applies to lock and claim files alike. New tests/file-lock-no-links.test.ts mocks link to fail with ENOTSUP and checks that the task never runs and no lock or staged file is left.
  • Recycled claim owners: claimAbandoned now judges a claim with the same abandoned() as the lock (dead, this process, taken before this boot, or a pid whose process started after the claim). New tests: an abandoned lock plus a claim that predates boot (portable), and one whose pid's process started after it (POSIX).
  • PID 1 assertion: moved into the POSIX-only (it.skipIf(process.platform === 'win32')) test; the dead-owner and pre-boot test stays portable.
  • Invite "Checking…" (InviteCodeField.tsx): the value effect now resets checking along with check before its empty-value return. The desktop renderer tests render static markup only (no DOM environment), so there is no mounted test for this; adding jsdom just for it seemed out of scope.

@reviewant

reviewant commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Superseded by a newer review: #1080 (comment) (previously reviewed 8e3cae33da)

…emoval failures, and count legacy used indexes
@kotevcode

Copy link
Copy Markdown
Contributor Author

@reviewant re-review

Addressed all three round-9 findings:

  • Deep-link readiness (apps/desktop/src/main/deep-links.ts, new deep-link-queue.ts): delivery no longer looks at isLoading(). The renderer now subscribes to referral:deep-link first and then calls referral:take-deep-link; that call marks its event.sender ready and drains the held invite. Links are pushed only to the ready WebContents and held otherwise. Readiness resets on did-start-loading (navigation or reload) and on destroyed, so the next page asks again. The handshake lives in an Electron-free InviteLinkQueue with tests for: load before subscription (held, then taken once), push to a ready renderer, subscription then reload (held until it asks again), and no window or a window that never asked.
  • Removal failures (ReferralStatusRow.tsx): removal goes through removePendingInvite, which catches the rejection and returns "Could not remove it. Try again." The row stays pending and shows it as role="alert"; it is cleared on retry, and the loading state always resets. Tested on both the rejection and success paths.
  • Legacy used arrays (apps/ants/src/service/indexer.ts): an array used is never read as a count; the count comes from the normalized indices. There is a regression test for { quota: 5, used: [4] } → used: 1, usedIndices: [4].

@reviewant

reviewant commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Superseded by a newer review: #1080 (comment) (previously reviewed aa44825e2f)

@kotevcode

Copy link
Copy Markdown
Contributor Author

@reviewant re-review

Addressed both round-10 findings:

  • Desktop referrer while its operator connects (apps/ants/src/service/referrals.ts): the referrer is the buyer account whose wallet signed the invites, so referral, referredBuyers and claimReferralRewards now target ctx.buyerAddress. This is the same originating-buyer target the referee bonus uses, and it stays on the desktop buyer when its authorized operator is the connected wallet, or the wallet itself when that is unrelated. claimEpochs is permissionless and pays the referrer's Deposits operator, so the operator can send it. The issued-invite scope follows the same account. createInvite refuses unless the signer is that buyer wallet, because an operator signature would hand out the operator's invites. A new server test uses a Desktop-style session with distinct buyer and operator: after the operator connects, /api/referrals shows the buyer's payable, and /api/referrals/claim calls claimEpochs(…, buyer, [3]). Every Antscan read targets the buyer. The service tests were updated, and a new one covers the operator-signing refusal.
  • Pending-invite RPC failure test (packages/node/tests/referral-state.test.ts): it now injects a clock, advances it by REFERRAL_CHAIN_RECHECK_MIN_MS before the failing lookup, and asserts through counted lookups that both the denying and the throwing referrerOf were actually called.

@reviewant

Copy link
Copy Markdown
Contributor

Antseed Review Summary

This PR introduces invite-only referrals and client attribution through additional buyer-signed metadata on paid and free usage. New contracts bind referrals, account for attributed usage, and distribute epoch rewards to referral operators and client-agent owners. Shared persistence, CLI commands, desktop deep links, dashboard services, and a website landing page connect invite issuance and redemption to settlement and reward claims.

  • No issues found in the reviewed changes.

Confidence Score: 5/5

Safe to merge; no verified issues remain across the eight passes and the cross-package interface review.

The merge review verified metadata layouts, invite encoding and signature domains, contract/client claim interfaces, attribution credit finality, desktop-to-CLI persistence, and originating-buyer versus operator targeting without finding a concrete mismatch. The supplied passes report successful contracts (907), node (1,457), buyer-core (47), protocol (47), ANTS (753), CLI (1,396 with one skipped), desktop, and network-stats suites; initial build and invocation artifacts were not identified as PR regressions. No tests were rerun during this merge review, as instructed. Live deployment, external Antscan interoperability, mixed-version peer integration, and operating-system/browser deep-link delivery remain unexercised.

Important Files Changed

Filename Overview
packages/contracts/test/AntseedAttributionRewards.t.sol Adds real-contract integration coverage for late-credit finality, invite forwarding, two-sided rewards, and operator payouts; no verified issue found.
packages/contracts/test/AntseedAttributionUsage.t.sol Tests attribution cursors, client switching, late credits, pause behavior, referral windows, and self-referral exclusions consistently with the ledger implementation.
packages/contracts/test/AntseedClientRewards.t.sol Covers proportional client rewards, owner recipients, claim finality, duplicate claims, and empty-epoch sweeps; no verified issue found.
packages/contracts/test/AntseedReferrals.t.sol Adds invite validation and signature-vector tests alongside reward-routing and real-gate burn-cap coverage; no verified issue found.
packages/contracts/test/AntseedStatsV2.t.sol Tests supported metadata layouts, attribution vectors, unconditional cursor forwarding, rejected invites, and disabled sinks; no verified issue found.
packages/contracts/test/RegisterAttributionMinters.t.sol Checks the verification-bucket split, next-epoch budgets, conserved total share, and repeat-registration rejection; no verified issue found.
packages/node/tests/buyer-identity-attribution.test.ts Adds coverage ensuring additional buyer identities receive the client ID without inheriting the default wallet's invite, including subsequent attribution updates.
packages/node/tests/buyer-payment-manager.test.ts Checks initial attributed metadata hashes, attribution replacement and clearing, and removal of persisted attribution when restarting without configuration.
packages/node/tests/client-rewards-client.test.ts Verifies claimable-epoch scanning, zero-reward filtering, early-epoch behavior, and the client rewards contract configuration override.
packages/node/tests/file-lock-no-links.test.ts Verifies unsupported hard links fail without entering the critical section or leaving staged lock files.
packages/node/tests/file-lock.test.ts Adds coverage for serialization, abandoned-owner recovery, publication safety, paused holders, and competing cross-process reclaimers.
packages/node/tests/free-usage-lifecycle.test.ts Exercises attribution replacement, clearing, metadata hashing, and restart behavior through encoded free-usage protocol messages.
packages/node/tests/invites.test.ts Tests invite signature vectors, encoding and links, malformed input rejection, preview results, and available-index selection.
packages/node/tests/referral-state.test.ts Covers scoped persistence, authoritative chain confirmation, backoff, legacy migration, and concurrent updates; the revised RPC failure case now reaches and counts the throwing lookup.
packages/node/tests/seller-cooperative-close.test.ts Checks that re-signed metadata at the same cumulative amount is retained and submitted during cooperative close.
apps/desktop/electron-builder.yml Registers the antseed URL scheme in packaged applications, including the inheriting Linux configuration.
apps/desktop/src/main/deep-link-queue.ts Buffers an invite until the renderer subscribes and resets readiness on navigation or destruction.
apps/desktop/src/main/deep-link-queue.test.ts Tests delivery before subscription, live delivery, reloads, and replacement windows.
apps/desktop/src/main/deep-links.ts Handles startup arguments and Electron URL events, forwarding supported invite links through the readiness queue.
apps/desktop/src/main/main.ts Registers deep links before application readiness and connects delivery to the main-window lifecycle.
apps/desktop/src/main/ipc/app.ts Exposes referral operations and the pending-link handshake through IPC consistently with the preload bridge.
apps/desktop/src/main/referrals.ts Validates and persists buyer invites, signs new invites, and adapts referral rewards and allowance data for desktop consumers.
apps/desktop/src/main/preload.cts Adds referral operations and a removable deep-link listener to the renderer API.
apps/desktop/src/renderer/types/bridge.ts Defines referral response types and optional bridge methods matching the main-process implementation.
apps/desktop/src/main/runtime/process-manager.ts Defaults child-process client attribution to desktop while preserving explicit environment overrides.
apps/desktop/src/main/payments/credits.ts Carries referral deployment and explorer configuration into the cached crypto configuration.
apps/desktop/src/main/payments/buyer-channels.ts Preserves existing rewards-summary exports while moving their definitions to a dedicated module.
apps/desktop/src/main/payments/rewards-summary.ts Extracts wallet-and-chain-scoped rewards caching without changing freshness or error-retry behavior.
apps/desktop/src/main/payments/rewards-summary.test.ts Verifies friendly network errors, preservation of other errors, and successful-result caching.
apps/desktop/src/main/ipc/payments.ts Uses the extracted rewards-cache helper and retains the existing IPC response shape.
apps/desktop/src/main/utils.ts Maps recognized RPC and explorer failures to a short retry message while retaining other error messages.
apps/desktop/src/main/utils.test.ts Tests network-error normalization and preservation of non-network failure messages.
apps/desktop/src/renderer/core/format.ts Adds grouping separators to display amounts while preserving two decimal places and tiny-balance indicators.
apps/cli/src/cli/commands/buyer/start.ts Installs client and pending-invite attribution before proxy startup, then watches referral state and stops the watcher during shutdown; no issue found.
apps/cli/src/cli/commands/referral.ts Adds invite creation, redemption and status commands using shared validation, allocation and persistence helpers; no issue found.
apps/cli/src/cli/commands/referral.test.ts Covers invite allocation, stale explorer data, redemption, deployment scoping and command output.
apps/cli/src/cli/referral-state.ts Resolves client attribution and synchronizes pending invites without blocking startup on network lookups; no issue found.
apps/cli/src/cli/referral-state.test.ts Tests attribution precedence, pending-to-bound transitions and state writes racing watcher reads.
apps/cli/src/cli/payment-utils.ts Propagates referral and client-reward configuration into resolved CLI payment settings.
apps/cli/src/cli/program.ts Registers the new referral command group without changing existing command registration.
apps/cli/package.json Extends test discovery to top-level command tests and tunnel tests; the full suite passed.
apps/cli/README.md Documents referral commands, invite eligibility, persistence and reward claims.
apps/network-stats/src/store.ts Adds channel-level reporting-contract tracking and subtracts previously indexed usage from the first cumulative report after a contract switch.
apps/network-stats/src/store.test.ts Tests contract-switch deduplication and continued delta accounting after upgrading an existing database.
apps/desktop/src/renderer/ui/AppShell.tsx Subscribes to invite deep links before retrieving a queued link and routes established users to Rewards.
apps/desktop/src/renderer/ui/components/InviteCodeField.tsx Provides shared debounced invite validation, deep-link prefilling and explicit redemption.
apps/desktop/src/renderer/ui/components/ReferralStatusRow.tsx Displays pending or bound referrals and supports changing or removing pending invites while preserving final bindings.
apps/desktop/src/renderer/ui/components/SetupScreen.tsx Offers invite entry during setup when referrals are configured and no invite or binding is already present.
apps/desktop/src/renderer/ui/components/views/VprPreferencesView.tsx Adds referral settings using the shared status component and reloads state after changes.
apps/desktop/src/renderer/ui/components/views/VprRewardsView.tsx Adds invite creation, invitee earnings and bonus claims with refreshed reward data and protection against stale asynchronous responses.
apps/desktop/src/renderer/ui/components/views/VprRewardsView.test.tsx Covers reward aggregation, refresh ordering, successive invite creation, allowance rollover and claim destinations.
apps/desktop/src/renderer/ui/components/ReferralStatusRow.test.tsx Tests pending-invite controls, removal failures and final binding presentation.
apps/desktop/src/renderer/ui/components/views/VprReferralSetting.test.tsx Tests referral settings across unconfigured, unbound, pending and bound states.
apps/ants/src/service/referrals.ts Adds referral views, serialized invite issuance, and reward claims with on-chain fallbacks; buyer/operator targeting is consistent with the contract.
apps/ants/src/service/referrals.test.ts Covers invite allocation, concurrent issuance, deployment-scoped persistence, explorer fallbacks, and refusal to sign buyer invites with an operator.
apps/ants/src/service/builders.ts Adds agent ownership views, registration, and per-epoch claims with on-chain rechecks of indexed rewards.
apps/ants/src/service/builders.test.ts Tests ownership filtering, stale indexed claims, RPC failures, fallback scans, and registration.
apps/ants/src/service/context.ts Adds optional referral and client-reward clients and exposes their configured addresses consistently with existing clients.
apps/ants/src/service/index.ts Exports the new referral and builder services for dashboard, CLI, and desktop callers.
apps/ants/src/service/indexer.ts Adds referral, referee, and builder response adapters using the existing bounded HTTP and caching infrastructure.
apps/ants/src/service/indexer.test.ts Tests invite-count compatibility and referee response normalization.
apps/ants/src/api-types.ts Defines the new API views and builder request limit consistently with service responses.
apps/ants/src/routes.ts Connects cached reads and reward actions, permitting selected buyers or their operators to submit referral claims.
apps/ants/src/routes.test.ts Tests builder query normalization, referral caching, and invite endpoint success and refusal paths.
apps/ants/src/server.test.ts Adds account-selection coverage, including an originating buyer's referral reads and claims while its distinct operator is connected.
packages/contracts/script/DeployAttributionUsage.s.sol Deploys the attribution ledger with the expected constructor dependencies and documents subsequent wiring.
packages/contracts/script/DeployClientRewards.s.sol Deploys client rewards against the gate, attribution ledger, and identity registry.
packages/contracts/script/DeployReferrals.s.sol Deploys referrals with the expected accounting, recipient lookup, attribution, and binder dependencies.
packages/contracts/script/DeployStatsV2.s.sol Deploys and authorizes the new stats sink while documenting sink wiring before registry cutover.
packages/contracts/script/RegisterAttributionMinters.s.sol Splits the verification allocation into verification, builder, and referral buckets with ownership and existing-state checks.
packages/contracts/test/mocks/EmissionsGateMock.sol Provides controller budgets and shared burn-cap behavior consistent with the real gate paths used by the new tests.
packages/contracts/rewards/AntseedReferrals.sol Adds activity-derived invite quotas, immutable referral bindings, and permissionless reward claims paid to Deposits operators; no actionable issue found.
packages/node/src/utils/file-lock.ts Adds process-local queues and cross-process hard-link locks with abandoned-owner reclamation; reviewed publication and cleanup paths without finding a confirmed defect.
packages/node/src/payments/invites.ts Implements compact invite serialization, EIP-712 signing, validation helpers, and available-index selection consistent with the contract.
packages/node/src/payments/referral-state.ts Persists wallet-and-deployment-scoped state under a shared lock and requires chain confirmation before dropping pending invites.
packages/node/src/payments/evm/referrals-client.ts Exposes invite checks, referral reads, and permissionless reward claims through ABI methods matching the new contract.
packages/node/src/payments/evm/client-rewards-client.ts Adds client reward reads and claims using the existing EVM transaction machinery and settlement-grace convention.
packages/node/src/payments/seller-payment-manager.ts Adopts verified replacement metadata at the same cumulative authorization amount and persists it for subsequent settlement.
packages/buyer-core/src/buyer-payment-manager.ts Applies current attribution when signing metadata and computes opening authorization hashes from the actual transmitted metadata.
packages/buyer-core/src/buyer-free-usage-manager.ts Applies current attribution to free-usage authorizations while removing obsolete persisted attribution.
packages/buyer-core/src/channel-usage-accounting.ts Preserves attribution when advancing cumulative usage metadata.
packages/buyer-core/src/attribution.test.ts Tests attribution preservation through metadata advancement and unchanged behavior when attribution is absent.
packages/node/src/node.ts Propagates attribution into buyer managers and runtime updates while excluding the default wallet's invite from additional identities.
packages/node/src/buyer-identity-context.ts Extends additional identities' free-usage configuration to accept client attribution.
packages/node/src/payments/chain-config.ts Adds optional referral and client-reward deployment addresses plus first-party client identifiers.
packages/node/src/payments/index.ts Exports the new referral state, invite, and reward-client APIs without removing existing exports.
packages/node/src/index.ts Exposes attribution, referral, reward-client, and file-lock APIs through the public package entry point.
scripts/generate-contract-chain-config.mjs Maps referral and client-reward deployment records into generated chain configuration.
CHANGELOG.md Documents the referral and attribution behavior, client integration, and required contract deployment changes.
packages/contracts/emissions/AntseedAttributionUsage.sol Adds per-buyer cumulative usage cursors, client and referral point attribution, permissionless flushing, and late-credit rollover consistent with reward finality.
packages/contracts/emissions/AntseedClientRewards.sol Distributes client epoch rewards to ERC-8004 agent owners through permissionless, guarded claims.
packages/contracts/emissions/AntseedEpochShareRewards.sol Provides shared epoch-share claims with a grace period, frozen totals, duplicate-claim protection, and empty-epoch remainder settlement.
packages/contracts/stats/AntseedStatsV2.sol Preserves the stats writer interface while decoding signed attribution and forwarding client and invite data before token-counter filtering.
packages/protocol/src/signatures.test.ts Adds attribution round-trip, cross-language vector, legacy-decoder compatibility, and retired-tail rejection tests; all pass.
packages/protocol/src/signatures.ts Adds invite typed data and domain helpers plus optional attribution encoding and decoding without changing metadata bytes when attribution is absent.
apps/ants/web/src/api.ts Adds typed referral, invite, referred-buyer, and builder API calls matching the service routes.
apps/ants/web/src/components/Confirm.tsx Allows referral claims through the buyer-action wallet gate while retaining signing-wallet readiness and gas checks.
apps/ants/web/src/jobs.tsx Exposes the job list for registration tracking and supplies labels for the new reward actions.
apps/ants/web/src/pages/Rewards.tsx Adds referral and builder cards, bounded agent tracking, invite allowance reconciliation, and the buyer invite-bonus row; no verified correctness issue was found.
apps/ants/web/src/rewards-page.test.ts Covers reward rendering, claim eligibility, invite allowance rollover, and registration tracking; all 57 tests passed.
apps/ants/web/src/action-button.test.ts Extends operator-funded buyer-action coverage to referral claims; all 17 tests passed.
apps/ants/web/src/dashboard.test.ts Updates the job mock for the new passive job-list consumer; all 61 tests passed.
apps/website/src/invite/InvitePage.tsx Displays invite expiry and offers desktop download, deep-link opening, and a copyable CLI redemption command.
apps/website/src/lib/invite.ts Implements browser-side invite decoding and signer recovery consistent with the SDK format, with signer display disabled until a referrals deployment is configured.
apps/website/plugins/invite-route.ts Registers the dynamic invite route, relying on the static host's fallback document for direct visits.
apps/website/docusaurus.config.ts Registers the invite plugin, excludes invite URLs from the sitemap, and adds the referral documentation redirect.
apps/website/src/components/CommandChip.tsx Adds optional wrapping of long invite tokens while preserving the complete command for copying.
apps/website/docs/provide/referrals.md Documents invite issuance, redemption, quotas, expiry, and reward claims consistently with the inspected implementation.
apps/website/docs/protocol/recognized-usage.md Documents referral and client attribution buckets and their emissions-gate activation.
apps/cli/src/config/loader.ts Preserves clientAgentId through configuration merging and rejects values that are not positive safe integer agent IDs.
apps/cli/src/config/loader.test.ts Adds configuration coverage for valid numeric and decimal-string IDs and invalid inputs; the compiled test passed.
apps/cli/src/config/types.ts Adds optional client attribution and referral/client reward contract configuration fields.

Diagram

sequenceDiagram
    participant App as CLI or Desktop
    participant State as Scoped referral state
    participant Buyer
    participant Seller
    participant Stats
    participant Ledger
    participant Referrals
    participant Accounting
    participant Rewards
    App->>Referrals: Preview invite for buyer
    App->>State: Save validated pending invite
    State->>Buyer: Install invite and client attribution
    Buyer->>Seller: Sign usage metadata containing attribution
    Seller->>Stats: Forward metadata through settlement
    Stats->>Ledger: Credit previous usage and advance client cursor
    Stats->>Referrals: Attempt invite binding
    Seller->>Accounting: Accrue paid settlement points
    State->>Referrals: Confirm binding on chain
    State->>Buyer: Remove bound invite and retain client attribution
    Ledger->>Accounting: Read cumulative points on callback or flush
    Rewards->>Ledger: Read finalized epoch shares
    Rewards->>Rewards: Pay referral operators or client agent owners
Loading

Core files read in full: packages/contracts/test/AntseedAttributionRewards.t.sol, packages/contracts/test/AntseedAttributionUsage.t.sol, packages/contracts/test/AntseedClientRewards.t.sol, packages/contracts/test/AntseedReferrals.t.sol, packages/contracts/test/AntseedStatsV2.t.sol, packages/contracts/test/RegisterAttributionMinters.t.sol, packages/contracts/emissions/AntseedAttributionUsage.sol, packages/contracts/emissions/AntseedEpochShareRewards.sol, packages/contracts/emissions/AntseedClientRewards.sol, packages/contracts/rewards/AntseedReferrals.sol, packages/contracts/stats/AntseedStatsV2.sol, packages/contracts/script/RegisterAttributionMinters.s.sol, packages/contracts/test/mocks/EmissionsGateMock.sol, packages/contracts/test/mocks/MockERC8004Registry.sol, packages/node/tests/buyer-identity-attribution.test.ts, packages/node/tests/buyer-payment-manager.test.ts, packages/node/tests/client-rewards-client.test.ts, packages/node/tests/file-lock-no-links.test.ts, packages/node/tests/file-lock.test.ts, packages/node/tests/free-usage-lifecycle.test.ts, packages/node/tests/invites.test.ts, packages/node/tests/referral-state.test.ts, packages/node/tests/seller-cooperative-close.test.ts, packages/node/src/payments/referral-state.ts, packages/node/src/utils/file-lock.ts, packages/node/src/payments/invites.ts, packages/node/src/payments/evm/client-rewards-client.ts, packages/node/src/payments/evm/referrals-client.ts, apps/desktop/src/main/referrals.ts, apps/desktop/src/main/ipc/payments.ts, apps/desktop/src/main/payments/credits.ts, apps/desktop/src/main/payments/buyer-channels.ts, apps/desktop/src/main/payments/rewards-summary.ts, apps/desktop/src/main/payments/read-cache.ts, apps/desktop/src/main/staking/buyer-rewards.ts, apps/desktop/src/main/staking/configuration.ts, apps/cli/src/cli/commands/buyer/start.ts, apps/cli/src/cli/commands/referral.ts, apps/cli/src/cli/referral-state.ts, apps/cli/src/cli/payment-utils.ts, apps/ants/src/service/referrals.ts, apps/ants/src/service/builders.ts, apps/ants/src/service/context.ts, apps/ants/src/service/indexer.ts, apps/ants/src/routes.ts, packages/contracts/script/DeployAttributionUsage.s.sol, packages/contracts/script/DeployClientRewards.s.sol, packages/contracts/script/DeployReferrals.s.sol, packages/contracts/script/DeployStatsV2.s.sol, packages/node/src/payments/evm/identity-client.ts, packages/node/src/payments/chain-config.ts, packages/node/src/payments/index.ts, packages/node/src/payments/seller-payment-manager.ts, packages/node/src/buyer-identity-context.ts, packages/node/src/index.ts, packages/node/src/node.ts, packages/buyer-core/src/attribution.test.ts, packages/buyer-core/src/buyer-free-usage-manager.ts, packages/buyer-core/src/buyer-payment-manager.ts, packages/buyer-core/src/channel-usage-accounting.ts, packages/buyer-core/src/base-evm-client.ts, apps/cli/src/config/loader.ts

Reviews (11): Last reviewed commit: feat(payments): Bind referrals through invites and attribute (18426389b6) | requested by @kotevcode | Tag @reviewant with "re-review" to run again

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants