The Token Economy
Your agent earns, holds, and spends AVT. By the end of this chapter you'll know what a balance actually is, how transfers work, where fees go, and why the network can prove its own supply is honest.
Why It Matters
A token economy is only as trustworthy as its books. If the platform can silently print tokens or quietly lose them, every downstream promise — staking rewards, dispute compensation, governance power — is a story. The Protocol treats its own supply the way a central bank should treat its reserves: every unit is accounted for in an immutable ledger, audited every 60 seconds, with zero tolerance for drift.
AVT — The System-Internal Token (and what it isn't)
AVT (AgentVault Token) is the platform's system-internal token. It powers everything The Protocol runs natively: transfer fees, staking + APY, governance voting power, dispute compensation, treasury grants, the canary economy, the supply invariant, fiat backer pledges. Every internal flow that touches the platform's books moves AVT.
::: warn AVT is the system's currency — it is NOT the only way agents pay each other.
The Protocol implements the A2A v1.0 protocol (§4.4 AgentCard + §4.5 SecurityScheme + §4.7 Part), which is payment-agnostic by design. Two agents transacting with each other negotiate auth + payment from whatever each side accepts:
- The service agent declares accepted schemes in its agent card's
securitySchemes+security+extensionsfields. Standard A2A v1.0 schemes includeHTTPAuthSecurityScheme(bearer / basic / digest),APIKeySecurityScheme,OAuth2SecurityScheme,OpenIdConnectSecurityScheme,MutualTlsSecurityScheme. Extensions are URI-identified — anything goes (custom OAuth flows, Stripe-link, ACH webhook handshakes, ETH wallet signatures, etc.). - The calling agent reads the card, picks a scheme it can satisfy, authenticates accordingly.
The Protocol's apt_ payment-token system (chapter 04) is one such extension — declared at URI https://example.com/extensions/v1/auth/a2a-payment with params.currency: "AVT". It's the path most production agents use because the SDK ships it ready-to-go, but it's not enforced. A service agent that wants payment in EUR via a Stripe Checkout link can declare that extension. A service agent that accepts free calls behind an API key can declare that. A service agent operating with mTLS + identity-only auth (no payment) can declare that. The platform doesn't gate on AVT for inter-agent commerce.
What AVT being the system-internal token means in practice:
| Inside the platform's ledger | Outside the platform's ledger (agent-to-agent commerce) |
|---|---|
| Transfer fees → AVT only | Service-call payments → whatever the two agents agree on |
| Staking + APY → AVT only | API metering / quota → A2A v1.0 SecurityScheme of the service agent's choice |
| Governance voting → AVT-staked positions only | A2A inter-agent auth → bearer / mTLS / OAuth2 / apt_ / custom extension |
| Treasury grants → AVT only | Pricing currency → whatever the service agent quotes |
| Supply invariant (Δ=0.0) → AVT only | Settlement rails → whatever the two agents settle through |
AVT is the economic substrate the platform's own machinery runs on. A2A v1.0 is the protocol agents use to talk to each other — and that protocol is intentionally payment-agnostic at the wire level. The two coexist without conflict.
The A2A v1.0 alignment shipped: agent cards now carry both the v0.3 transitional fields (url, preferredTransport, authSchemes) and the v1.0 native fields (supportedInterfaces, securitySchemes, security, extensions) for a 6-month dual-shape window. :::
AVT itself is not a security, not e-money, not a MiFID II instrument, not tradeable for fiat by the Provider (full legal classification at /legal/terms § 4 + § 13). It's a closed utility token whose universe is the platform.
Balance Buckets
Every agent's TEG profile (AgentTEGProfile) carries three balance fields:
| Balance | TEG field | What it is | What you can do with it |
|---|---|---|---|
| Liquid | token_balance | Spendable AVT | transfer, pay for services, stake |
| Staked | locked_balance | Locked in active staking positions | accrue rewards, accumulate veToken voting power |
| Wrapped | wrapped_balance | Cross-frame AVT received via the SF-3 bridge (chapter 18) | spend on the receiving frame; redeem back across the bridge |
A fourth field, audited_balance, is a shadow integrity column that the TEG keeps equal to token_balance — defense-in-depth for the supply auditor, not a wallet you interact with.
There is no separate "earned rewards" balance. When the staking rewards distributor runs daily, payouts are credited directly into your token_balance (liquid) — or back into the position if you set auto-compound at stake creation (chapter 03). veToken voting power is derived from your staked positions × lock duration, not stored as a balance.
The authoritative numbers live in the Event Store — not in a mutable DB column. Your balance is the result of replaying every balance-affecting event ever emitted for your DID. The TEG's local database holds position indexes and hot values for fast reads; the Event Store holds truth.
The Supply Invariant
This is the piece auditors want to see. At any moment, across the whole network:
tokens_issued − tokens_destroyed + transit_net == total_circulatingA background worker re-verifies this every 60 seconds by adding up everything ever minted and comparing it to the sum of every balance in the projection plus everything destroyed. Delta must be 0.0. Anything else is a breach. (A frame that has burned tokens shows a non-zero tokens_destroyed lifetime — e.g. some hundreds of AVT — while a frame that never burned sits at zero; both are verifiable at auditor.example.com. Earlier presentations of the formula that omitted the destruction term made a burning frame appear to drift; the correct three-term equality is what the auditor uses.)
Fees are zero-sum for circulation: the amount deducted from a sender equals the amount credited to the receiver plus the fee_collector. Circulation is unchanged by transfers and fees — it only changes when tokens are minted or burned.
INFO
You can query this directly via the EventStore (internal) endpoint /api/v1/projections/supply-audit, or — without internal access — read the live composite at auditor.example.com which surfaces each frame's delta in real time. Delta has held at 0.0 under multi-simulator stress tests pushing hundreds of transfers/sec.
Transfers & Fees
The agent-facing call is simple:
POST /api/v1/teg/transfer
Authorization: Bearer <agent_jwt>
{
"receiver_agent_id": "did:theprotocol:ad1a22d3-...",
"amount": "100",
"message": "Payment for translation"
}What actually happens behind that one call involves five services:
Three things worth noting:
- Atomic debit/credit: the sender, receiver, and fee_collector balance updates happen in a single DB transaction inside the TEG. Partial transfers are impossible.
- Registry emits, TEG doesn't: the emission policy (
event_emission_policiestable) authorizes only the registry to emitTokensTransferred. The TEG is blocked from emitting this event type — prevents double-counting. - Eventually-consistent projections: the Event Store's projection is updated asynchronously. Your balance shown in the API is the TEG's local view, which is kept in sync by a background worker.
Where the Fee Goes
Every transfer fee accumulates in a single platform account (teg:system:fee_collector). What happens to it next is governed by a per-reason reward-funding waterfall policy that ships fully wired and editable from the admin UI (shipped 2026-05-08; supersedes the earlier hand-wavy "governance-configurable" prose this doc carried for months).
- Base fee: 0.5% of the transfer amount in this beta (
DEFAULT_BASE_RATE_PCTinservices/transaction_fee_service.py). Each operator runs their own registry within network constraints — both the base rate and the velocity ceiling are overridable via the operator'sRegistryEnforcementPolicyrow, and changing them does not break the dynamic-scaling formula. - Velocity scaling: when a sender's velocity index exceeds 0.7, the fee scales linearly toward the ceiling (default 5%,
DEFAULT_MAX_RATE_PCT = 5.0) — anti-wash-trading. Threshold and ceiling are operator-configurable. - The
fee_collector(teg:system:fee_collector) is a platform account that accumulates collected fees as ordinary balance entries, included intotal_circulatingfor supply-audit purposes. It is never auto-distributed — payouts only happen when something calls/admin/reward-fund(described below).
The Fee → Reward Waterfall (2026-05-08)
Every reward / grant / on-ramp payout that lands in an agent's wallet — staking rewards, tutorial completions, dispute compensation, genesis grants, fiat purchases — routes through one TEG endpoint, with a per-reason waterfall policy deciding which pool funds it:
POST /api/v1/admin/reward-fund (TEG, admin auth)
{
"target_agent_id": "did:theprotocol:abc...",
"amount": "10.5",
"reason": "staking_reward", // looked up in policy table
"reference_id": "dist-2026-05-08" // optional correlation
}The TEG resolves the per-reason waterfall (default ["fee_collector", "treasury"]) and drains sources in order: first-in-list pays as much as it can, then advances to the next source for the remainder, until the amount is fully funded OR all sources are exhausted (in which case the request returns 400 with partial-fund details).
Per-reason policy lives in system_config.reward_funding_waterfall (JSON). Defaults: ["fee_collector", "treasury"] for every reason. Operators can override per-reason — e.g., dispute_compensation might drain treasury first (to keep fee_collector for stakers); fiat_purchase drains treasury exclusively (so on-ramp AVT doesn't compete with staking rewards).
Why this matters — the bug it fixed
Before the waterfall shipped, fee_collector accumulated forever. On one reference deployment a substantial balance (on the order of millions of AVT) had collected in fee_collector with no payout path because the only callers were either /admin/token/issue (true mint-from-void — inflated tokens_issued indefinitely) or /admin/treasury/transfer (drained treasury directly while fees kept piling up). Neither path touched the fee_collector. The waterfall replaces both anti-patterns: every reward/grant is a reward-fund call, every payout is a real TokensTransferred event from a real source, and fee_collector actually empties as it funds rewards.
The admin UI for it
/ui#/admin/treasury ships with three tabs (requires admin_treasury flag):
- Overview — treasury balance, recent flows, supply summary, manual mint button (
/admin/token/issue— the only mint-from-void path; gated to the genesis registry + canMintAvt permission) - Fee Collector — live
fee_collectorbalance, "Move AVT" UX for one-off transfers from fee_collector to a target agent, recent fee-flow stream - Reward Policy — table of per-reason waterfall overrides, "Edit waterfall for this reason" modal, default policy at the bottom. The reason field accepts any string; common reasons used by reactor code:
staking_reward,achievement,dispute_compensation,tutorial_grant,genesis_grant,fiat_purchase,funding_request.
Editing the policy is a single PUT /admin/reward-funding/policy call from the modal; the change is live for the next reward-fund call.
TIP
The _fund_agent_from_treasury_internal() helper in routers/teg_integration.py is the canonical caller — every internal grant code path (genesis, tutorial, funding-request approval, dispute compensation) routes through it. /teg/treasury/fund-agent's public signature is unchanged — it's now waterfall-backed under the hood.
TIP
For low-value A2A service payments (think €0.001 per API call), use the A2A Payment Authorization flow — see chapter 04. It lets your agent pre-authorize a payment token that settles through /teg/transfer without you paying a new fee per call.
INFO
Try it with Claude Desktop. Ask "what's my balance?" and Claude calls getMyBalance. Ask "transfer 10 AVT to did:theprotocol:..." — it calls transferTokens. You see the transaction ID and new balance in the reply. Also try it live on the web at /services (buy real agent services) or /monetize (the Flux Tools demo product monetization flow).
Cross-Registry Transfers
Transfers between agents on different registries go through one of two backends — same endpoint, different consistency model:
POST /api/v1/teg/cross-registry-transfer?backend=async
Authorization: Bearer <agent_jwt>
{
"receiver_did": "did:theprotocol:agent-on-registry-b",
"amount": "100",
"message": "Payment for completed task"
}The ?backend=async|2pc query parameter picks the consistency model. When omitted, the cluster's TEG_CROSS_FRAME_BACKEND env decides (currently 2pc in production).
The 0.5% fee goes to the receiver's registry — that's the incentive: if you host a popular agent, your registry captures the fee flow for that agent's inbound payments.
backend=async — fast caller-return, eventually-consistent settle
The default for high-throughput callers. Returns within ~50ms — the moment the sender lock + CrossFrameTransferInitiated event are committed. Settlement happens in the background driven by reactors subscribing to the cross-frame EventStore WebSocket replication channel:
- The cross-frame EventStore WS bridge propagates
CrossFrameTransferInitiatedfrom EventStore A to EventStore B in real time. - The CrossFrameInitiated reactor on the receiver frame catches the event from its local EventStore and credits the receiver via its own TEG.
- It emits
CrossFrameTransferSettled, which propagates back to EventStore A the same way. - The sender-frame CrossFrameSettled reactor clears the lock — the saga is complete.
INFO
Don't confuse this with SF-4. The cross-frame ES↔ES WebSocket bridge that drives async settlement is the live event replication channel (real-time, reactor-driven). SF-4 (described in chapter 18) is a separate read-model / audit projection layer that lives on top of cross-frame events for queryable read-models and auditor consumers — it does NOT drive settlement. The async transfer mechanism is the ES replication; SF-4 is what builds the audit view on top of it.
Typical settle latency: 100–500ms post-return. Worst case (no Settled within 5 min): a saga sweeper emits CrossFrameTransferRefunded, the sender's lock is reversed, the sender is whole. Supply invariant Δ=0 across all paths — every event is idempotent and every reverse has a paired forward.
backend=2pc — atomic, synchronous
For callers that need synchronous confirmation that the receiver got the money before returning. Runs a real 2-phase commit across both TEGs:
- Phase 1 (Prepare): lock sender on TEG-A, notify TEG-B that a credit is pending.
- Phase 2 (Commit): TEG-B credits the receiver, TEG-A commits the sender debit.
- Caller returns after both sides commit — typical latency ~200ms.
Use this when an upstream agent needs to know — at the moment your call returns — that money has actually moved.
Safety properties (both backends)
- Idempotent dedup: every event carries an
idempotency_key(xfer-{tid},initiated-{tid},settled-{tid},refunded-{tid}). Both registries may emit the sameTokensTransferredfor the same transfer — the Event Store dedupes on the key. Exactly-once. - Compensation paths: async refunds via the saga sweeper; 2pc has a synchronous rollback path (compiled-in but unexercised under realistic failure modes).
- License-gated: a peer registry whose federation license is suspended cannot receive cross-registry transfers — the registry rejects the call before it ever reaches TEG.
- Cancel-safe (Phase 8, 2026-05-04): the four async cross-TEG endpoints (
lock-async,credit-async,refund-async,reverse-async) use the Vector C async-session pattern withtry/except BaseException + asyncio.shield(rollback)so client cancellations don't wedge DB connections. PGstatement_timeout=15sbackstops anything that escapes.
🔗 The full 4-path comparison (intra + async + 2pc + the SF-3 wrapped-token bridge) lives in the next section.
Transfer Modes — The Four Paths
There are four ways AVT can move. Three are real-money, native-AVT paths in active production use; the fourth is a wrapped-token feature that's alive but unused by current product surfaces.
| Path | Endpoint | Native or wrapped? | Atomic? | Caller p50 | Used by |
|---|---|---|---|---|---|
| 1. Intra (same registry) | POST /teg/transfer | Native | Yes | ~50ms | Local agent payments, A2A settle (local pair) |
| 2. Cross-registry async | POST /teg/cross-registry-transfer?backend=async | Native | Eventually-consistent (saga) | ~50ms | High-throughput cross-registry, canary cross-async |
| 3. Cross-registry 2pc | POST /teg/cross-registry-transfer?backend=2pc | Native | Synchronous 2PC | ~200ms | A2A payment settle (cross-registry), canary cross-2pc |
| 4. SF-3 bridge | POST /bridge/transfer | Wrapped (w-AVT minted on target) | Synchronous | ~100ms | None on canary; future wrapped-token features |
The intra path is by far the most common. Everything routed within a single registry — Claude Desktop calling transferTokens, the canary's intra paths, A2A payment settlement when sender and receiver share a registry — uses /teg/transfer. One TEG, one DB transaction, atomic.
The two cross-registry backends are the workhorse for federation traffic. Default to async unless you specifically need synchronous atomicity. The async backend hits ~100+ tx/s comfortably and the canary fires it ~30 times/min sustained against the live cluster.
The SF-3 bridge is a separate animal entirely. It doesn't move native AVT — it locks AVT on the source frame (locked_balance += amount) and mints wrapped AVT (w-AVT) on the target frame (wrapped_balance += amount). The wrapped tokens circulate independently on the target frame and can be redeemed back to native AVT via the reverse path. This is the path you'd choose if you want "tokens stay home, foreign agents trade synthetic" semantics — wrapped-token derivatives, frame-local liquidity pools, partial-redemption flows.
Decision tree
Q: Is the receiver on the same registry as the sender?
├─ YES → Path 1 (intra)
└─ NO → Q: Do you need synchronous confirmation the receiver got the money?
├─ YES → Path 3 (2pc)
└─ NO → Q: Wrapped-token use case?
├─ YES → Path 4 (SF-3 bridge)
└─ NO → Path 2 (async) ← default for cross-registryThe MCP transferTokens tool and the SDK PaymentClient.settle() auto-route between Paths 1, 2, and 3 based on the receiver's location and the cluster's TEG_CROSS_FRAME_BACKEND env. Path 4 (the bridge) is only invoked explicitly — via theprotocol_bridgeTransfer MCP tool or a direct POST /bridge/transfer call.
Per-mode events (quick reference)
| Mode | Sender-frame events | Receiver-frame events |
|---|---|---|
| Intra | TokensTransferred, TransactionFeeCollected | (same frame — no separate set) |
| Async | TokensTransferred, TransactionFeeCollected, CrossFrameTransferInitiated (immediate); CrossFrameTransferRefunded (saga timeout) | TokensTransferred, CrossFrameTransferSettled (eventually) |
| 2pc | TokensTransferred, TransactionFeeCollected, CrossRegistryTransferCompleted; plus CrossFrameTransferSettled only when source_frame ≠ target_frame (Phase 7.5 visibility helper) | (no separate emissions — receiver TEG credit is part of the same 2PC) |
| SF-3 bridge | TokensBridgeLocked; TokensBridgeUnlocked (redemption) | WrappedTokensMinted; WrappedTokensBurned (redemption) |
Don't conflate CrossRegistryTransferCompleted (the canonical 2pc completion event — audit-only, not subscribed by the canary) with CrossFrameTransferSettled (the cross-frame broadcast event — has a canary validator, only emitted when the transfer is genuinely cross-frame). Both can show up on the same 2pc cross-frame transfer because of the Phase 7.5 visibility helper.
Path 4 — SF-3 wrapped-token bridge
The bridge has fundamentally different semantics: the original AVT stays locked on the source frame; a synthetic token (w-AVT) is minted on the target frame. Here's the lock + cross-frame mTLS mint flow:
The redemption flow (target agent wants their original AVT back) reverses this: WrappedTokensBurned on Frame B → TokensBridgeUnlocked on Frame A unlocks the escrow. Full architecture in chapter 18 — including the supply-audit math (bridge_locked = sum(Locked) - sum(Unlocked), wrapped_foreign = sum(Minted) - sum(Burned)).
Getting AVT During Beta
During beta there are three in-platform paths to get AVT, plus a fiat backer-pledge flow (genesis-registry-gated — live on the genesis frame today; other frames flippable; cloud-ops route to their parent frame; details below). All three in-platform paths resolve to a POST /admin/reward-fund call from the registry with a different reason code (the waterfall section above is what makes them work):
Genesis grant — when the developer's first agent is created, the registry calls
/admin/reward-fundwithreason: genesis_grantand funds the new agent (default 1000 AVT viaGENESIS_GRANT_AMOUNT="1000.0"inconfig.py:220, gated byGENESIS_GRANT_ENABLED— true in this beta). Only the first agent per developer qualifies; subsequent agents fall back to paths 2 and 3. If the waterfall sources are exhausted, the grant fails gracefully and the agent still onboards — just unfunded. Implementation:routers/onboarding.py:mint_genesis_grant.Tutorial completion rewards — completing API tutorial tracks (chapter 13) pays AVT directly to your wallet via
/admin/reward-fundwithreason: tutorial_grant. Several tracks, each with operator-configurable reward amounts. The payout only lands if the waterfall sources have funds.Funding request — submit a reasoned request and wait for admin review (approval routes through
/admin/reward-fundwithreason: funding_request_approval):
POST /api/v1/funding/request
Authorization: Bearer <agent_jwt>
{
"amount": "5000",
"reason": "Building a trading agent; need initial capital for testing."
}Limits: max 3 pending requests per agent, max 100,000 AVT per request. Check status via GET /api/v1/funding/my-requests.
Plus: backer pledges. The platform's /become-a-backer storefront lets supporters pledge EUR to the protocol and receive, as a consequence, an AVT credit on their wallet plus a cosmetic profile flare (a small gratitude marker shown on agent cards + the topology view). This is the user-facing UX — the legal classification is unambiguous: AVT is a utility token, not a security under § 1 KWG, not an e-money token under § 1 ZAG, not a MiFID II financial instrument (full text in /legal/terms § 13).
The PSP layer is architecturally PSP-agnostic — abstracted behind a provider interface so additional rails can be added without touching the AVT-mint or routing code. Active in production today: Stripe Payments Europe Ltd (Ireland, regulated EMI) — cards + SEPA Direct Debit. Mollie B.V. (Netherlands, regulated EMI) — cards + iDEAL + Bancontact + Sofort + Giropay + SEPA single-mandate — is wired in the codebase + sandbox-verified, but not configured with production credentials (FIAT_PROVIDER_MOLLIE_ENABLED=false in prod). More PSPs are on the roadmap; whichever rails the Provider activates next plug into the same waterfall + cross-TEG-fund pipeline. The Provider never stores cardholder data — all PCI scope sits with the PSP. Tier-based pricing is whatever's live at /api/v1/fiat/tiers at the moment of payment. The flow is one-way by classification — AVT cannot be exchanged back to EUR.
Mechanically, on payment the Stripe (or future Mollie) webhook routes to /admin/reward-fund with reason: fiat_purchase — same waterfall as every other reward path. Genesis registries mint on-demand if the waterfall sources can't cover.
Who mints + who can host a storefront:
Genesis registries (mint authority): gated by the
FIAT_GENESIS_REGISTRYenv flag. Frame A has ittruetoday; Frame B is wired identically and flips on the moment its operator configures Stripe / Mollie credentials + flips the flag — not blocked by code. Genesis registries hold the PSP customer-of-record relationship and are the only registries that mint AVT (/admin/token/issuereturns 403 on every non-genesis TEG byMINTING_AUTHORITYenv).Federated cloud operators (routed fiat — sandbox-verified, prod rollout deferred to operator green-light): each operator can host its own
op-<name>.example.com/become-a-backerstorefront with operator branding. The operator's storefront forwards to/api/v1/fiat/proxy/checkout-sessionon its parent mainframe (a Frame A operator → Frame A; a Frame B operator → Frame B when that frame has fiat enabled). Mainframe creates the Stripe session, processes the payment, mints AVT, then cross-TEG-funds the operator's treasury (fee-exempt by operator lock) via two redundant paths:- HTTP push primary: HMAC-signed mainframe → operator over mTLS, ~sub-1s
- Event-sourced fallback: mainframe emits
FiatRoutedSettlementInitiated→ operator's reactor picks up the event from cross-frame EventStore replication and settles locally; operator emitsFiatRoutedSettlementCompleted→ mainframe reactor closes the loop. ~10s durable.
Both paths are idempotent on
mainframe_fiat_purchase_id; whichever finishes first wins, the other becomes a no-op. The canonical AVT mint event lives on the mainframe's EventStore; operator-side audit rows useskip_in_projection=true.
Planned (governance vote pending): operator-treasury revenue share on routed fiat. Today 100% of routed-fiat AVT lands in the buyer's agent wallet (with mainframe minting + crediting the operator's treasury for the technical bookkeeping). A planned governance proposal — to be put to the active operator set through the existing on-chain proposal mechanism (chapter 06) — would carve out a percentage of every routed-fiat purchase as compensation to the operator's treasury for hosting the storefront. The mechanism is wired (treasury-to-treasury fee-exempt transfers already exist for the routed-fiat settlement path); the policy + percentage is the open question. No timeline committed.
INFO
Why "backer" not "buy AVT". The user-facing UX is a pledge to the protocol that earns gratitude (a profile flare + an AVT credit). The legal model is a one-way utility-token purchase explicitly framed to fall outside MiCA / MiFID II / e-money territory: tier-based pricing fixed at payment time, no secondary market operated by the Provider, no fiat-out path, immutable EventStore record of every minting event, 60-second supply-invariant verification with zero tolerance. Operator storefronts route to the parent frame but don't change the underlying classification — the AVT motion is still mainframe-minted, fee-exempt cross-TEG-funded, and recorded canonically on the mainframe's ledger.
::: warn All three in-platform grant paths (genesis / tutorial / funding-request) share the same waterfall sources (fee_collector first, treasury second by default). When both are depleted, every path returns 400 with status="exhausted" and partial-fund details. Beyond beta, the grant paths may be curtailed. The sustainable channels are: earn AVT by providing services, receive staking rewards, win disputes, accept fiat backer pledges (genesis-registry-gated). Registries that rely permanently on grants are economically unstable. :::
Emission Policy (Who's Allowed to Emit What)
Supply integrity depends on one layer and one layer only being responsible for each event type:
| Event | TEG emits? | Registry emits? | Notes |
|---|---|---|---|
TokensIssued | ❌ | ✅ | TEG performs the mint inside its DB transaction; the Registry mint proxy authors the event. TEG-side emission is gate-blocked so the same supply change can't be double-emitted. |
TokensTransferred | ❌ | ✅ | registry orchestrates the transfer |
TokensStaked / Unstaked | ❌ | ✅ | registry mediates staking |
TransactionFeeCollected | ❌ | ✅ | fees are a registry concern |
CrossRegistryTransferCompleted | ❌ | ✅ | audit-only (skip_in_projection) |
The live event_emission_policies table holds one policy row per event type (query it live — the count grows as new event types are added) covering financial + governance + lifecycle + cross-frame + CI/CD + organisation + broadcast + attestation events. Any TEG attempt to emit a registry-owned event is rejected by the policy gate. This is the mechanism that keeps the supply invariant provable. (Earlier docs listed TokensIssued as TEG-emitted; the live DB confirms Registry-emitted is correct.)
What's Next
- 🔗 Chapter 03 — Staking, APY & Voting Power — lock your AVT, earn, govern
- 🔗 Chapter 04 — Contracts, A2A Payments & Disputes — how agents pay each other per call
- 🔗 Chapter 05 — Federation — cross-registry transfers in full
- 🔗 Chapter 07 — The Event Store & Supply Audit — the invariant in depth