Status: canonical product model. This specification supersedes earlier YET/Wry account diagrams and every ERC-6551 or pooled portfolio proposal.
The accepted Clubs and Harvest extension reuses this hierarchy for managed strategies. A Club is a community, not an owner or pool. Operator collateral and coverage reservations are separate from customer assets and mirror budgets; the extension does not enable new value-moving code.
Customer-facing presentation follows the end-user experience specification. Canonical Account, Account Instance, and Account Mirror are implementation and advanced-audit concepts; ordinary tasks must not require customers to understand them.
1. Product decision
The central customer promise is earning money and creating, holding and trading personal asset Bundles within their own subaccounts. Bundle is the customer name for the existing Personal Basket, with explicit buy/add/sell/rebalance workflows. See the earning and asset-bundle specification. The account architecture implements that product rather than becoming the main customer task.
Forage uses the useful product shape of StonkBrokers—visible, addressable containers with isolated assets and activity—but replaces NFT ownership and token-bound accounts with verified-customer ownership, AGW recovery, programmable smart accounts, bounded delegation, and chain-agnostic account instances.
The system is built around four separations:
- A Portfolio is a logical customer workspace and aggregation view.
- A Canonical Account is a chain-agnostic lane with one beneficial owner and one purpose.
- An Account Instance is the real smart account, provider wallet, or provider book entry on one chain/provider rail.
- An Account Mirror synchronizes bounded authority and budget to a remote instance; it never represents assets.
The customer experience is “one portfolio made of inspectable accounts.” The implementation is never “one wallet pretending every provider and chain has the same custody, rights, or authority.”
2. What we borrow from StonkBrokers
StonkBrokers gives each NFT a distinct ERC-6551 account whose contents travel with NFT ownership. Forage borrows:
- one recognizable container per account lane;
- an addressable account with its own assets and history;
- strong isolation between sibling containers;
- visible composition and activity;
- deterministic discovery of the account associated with a container.
Forage does not borrow:
- an ERC-721 as the ownership root;
- bearer transfer of the container and all contained assets;
- arbitrary token-bound execution;
- NFT marketplace or AMM transfer semantics;
- one EVM-only identity scheme presented as chain agnostic;
- rewards, distributions, or permissions inherited from owning a collectible.
This is a deliberate product distinction. The official StonkBrokers documentation describes its broker NFT and token-bound wallet as traveling together. That behavior is useful for a collectible; it is unsafe as the default ownership model for regulated customer accounts.
3. Canonical hierarchy
Verified Customer or Organization
|
+-- Root Authority
| Abstract Global Wallet (AGW)
| passkey ownership + recovery + root session creation
|
+-- Portfolio: Long-term
| |
| +-- Canonical Account: Dinari U.S. provider lane
| | +-- Instance: provider-approved wallet on chain A
| | +-- Provider Account: authoritative brokerage record
| |
| +-- Canonical Account: Personal Robinhood basket
| | +-- Mirror: bounded control state for chain 4663
| | +-- Instance: customer-owned Robinhood Chain smart account
| |
| +-- Canonical Account: recurring strategy
| | +-- Instance: Abstract strategy account
| | +-- Mandate: selected asset, cadence, caps, expiry
| |
| +-- Canonical Account: AI sandbox
| +-- Instance: isolated agent account
| +-- Delegate: one mandate, one budget, short expiry
|
+-- Portfolio: Income
+-- its own accounts, instances, mirrors, and mandates
The Portfolio node does not own or pool funds. It groups accounts for display, policy, reporting, and customer intent. Assets exist only in an Account Instance or an authoritative provider account.
4. Core object model
| Object | Identity | Authority | May hold assets? | Notes |
|---|---|---|---|---|
| Legal Account | legalAccountId | Verified individual or organization | No | KYC/KYB, jurisdiction, beneficial owner, authorized actors |
| Root Authority | rootAuthorityId | AGW owner/recovery policy | No by product design | Root authorization and recovery surface |
| Portfolio | portfolioId | Legal Account | No | Logical aggregation and intent boundary |
| Canonical Account | accountId | Legal Account through AGW | No | Chain-agnostic purpose and policy identity |
| Account Instance | instanceId | Chain/provider-specific owner model | Yes, or references provider assets | One actual settlement/custody location |
| Provider Account | providerAccountId | Provider and verified customer | Provider-authoritative | Orders, statements, corporate actions, eligibility |
| Wallet Binding | walletBindingId | Provider-approved signed binding | No | Connects provider account to one approved Account Instance |
| Account Mirror | mirrorId | Root Router + local Mirror Controller | No | Control state, allocation, expiry, nonces, pause |
| Mandate | mandateId | Customer signature | No | Bounded permission targeting one or more explicit instances |
| Delegate | delegateId | Mandate + local account policy | No | Short-lived execution signer; never the owner |
| Position | composite provider/chain key | Provider or chain state | N/A | Never inferred from mirror state |
| Reconciliation Record | reconciliationId | System/audit process | No | Compares provider, chain, and Forage state |
Cardinality:
LegalAccount 1 ---- N RootAuthorities normally one active AGW, historical rotations retained
LegalAccount 1 ---- N Portfolios
Portfolio 1 ---- N CanonicalAccounts
CanonicalAccount 1 ---- N AccountInstances
AccountInstance 1 ---- 0..1 active Mirror only when remote delegated control is enabled
AccountInstance 1 ---- N Mandates
Mandate 1 ---- N DelegateGenerations at most one active delegate per chain unless explicitly approved
ProviderAccount 1 ---- 1 active WalletBinding where the provider requires uniqueness
Separate legal entities controlled by the same person receive separate Legal Accounts and account namespaces. They are not sibling portfolios.
5. Account classes
A funded Bundle binds to the customer's actual BASKET instance where the provider permits independent smart-account custody, or to an approved dedicated provider account. Any same-customer accounting partition requires exclusive unit/lot allocation and explicit disclosure that it lacks independent onchain custody. A provider that cannot support the requested isolation cannot be made to support it through a visual label.
The rewards and income architecture adds merchant entitlements outside the customer investment hierarchy. A rewards ledger is not an AGW wallet or brokerage account. A group trail shares progress without shared custody. Financial accounts are provisioned when needed, not as a prerequisite for browsing offers.
| Class | Customer or protocol namespace | Purpose | Principal holder |
|---|---|---|---|
PROVIDER | Customer | One regulated provider/account lane | Provider-recognized customer wallet or provider book |
DIRECT | Customer | General direct onchain holdings | Customer Account Instance |
BASKET | Customer | Chain-local constituents governed by a basket policy | Customer Account Instance |
STRATEGY | Customer | Capital and instruments for one bounded strategy | Customer Account Instance or provider account |
AGENT | Customer | Small, isolated budget for one AI/automation purpose | Customer Account Instance |
CASH | Customer | Funding and settlement assets where permitted | Customer or provider account |
PROMOTION | Protocol only | Pre-funded promotional liabilities | Protocol promotion entity/account |
REVENUE | Protocol only | Earned protocol revenue | Protocol treasury/accounting entity |
PROMOTION and REVENUE cannot be created under a customer namespace. Customer accounts cannot be treated as protocol treasury accounts. Transfers between namespaces require a normal, explicit, auditable transaction; hierarchy never grants sibling access.
An income investment uses the provider/instrument class that reflects its actual legal form. CASH does not imply that a money-market fund, tokenized Treasury or stablecoin is an insured deposit. Customer interest/dividends are attributed to the exact earning position and never appear in protocol revenue. A reward-funded contribution needs payout and provider funding evidence; a mirror cannot manufacture buying power.
6. Chain-agnostic identity
Canonical account ID
The canonical ID intentionally excludes chainId and wallet address:
accountId = keccak256(
abi.encode(
"FORAGE_ACCOUNT_V2",
legalAccountIdHash,
portfolioId,
accountClass,
providerNamespaceHash, // zero for provider-neutral lanes
purposeHash,
accountNonce
)
)
Properties:
- stable across chain deployments and provider-approved wallet replacements;
- unique within one Legal Account namespace;
- contains no PII;
- cannot be sold or reassigned to a different beneficial owner;
- remains stable while Account Instances are added, rotated, frozen, or retired.
Because portfolioId is part of the namespace, a Canonical Account is not dragged between Portfolios. Reorganization creates a new account under the destination Portfolio and uses an explicit, eligible asset/provider transfer; the former account remains in history.
Chain reference
Use a CAIP-2-style namespace rather than assuming every network has an EIP-155 integer:
chainRef = "eip155:4663" // Robinhood Chain
chainRef = "eip155:2741" // Abstract
chainRef = "solana:<genesisHash>" // Solana cluster
chainRef = "provider:dinari-us" // provider book or managed wallet lane
The production registry stores a normalized chainRefHash plus the human-readable, versioned network record.
Account instance ID
instanceId = keccak256(
abi.encode(
"FORAGE_ACCOUNT_INSTANCE_V1",
accountId,
chainRefHash,
providerIdHash,
custodyMode,
instanceGeneration
)
)
The instance record stores the chain-native address as typed bytes. It does not force EVM addresses into non-EVM formats.
Address equality across chains is informational only. It is never proof of common ownership, synchronized policy, equivalent instruments, or shared balances.
7. Account Instance types
| Custody mode | Example | Control model | Authoritative state |
|---|---|---|---|
SMART_ACCOUNT | Abstract or Robinhood Chain programmable account | AGW root intent or chain-local delegate | Chain plus reconciled Forage policy |
PROVIDER_BOUND_WALLET | Dinari-approved dedicated contract wallet | Customer smart-account owner subject to provider binding | Provider books and chain |
PROVIDER_MANAGED_WALLET | Provider-created dedicated wallet | Provider custody; Forage has no signing authority | Provider books |
PROVIDER_BOOK | Offchain brokerage account | Provider API and customer authorization | Provider books |
NON_EVM_PROGRAM | Solana program/PDA-based account | Native chain authorization | Chain plus provider where applicable |
The UI always exposes custody mode, provider, network, actual address/account reference, owner/recovery model, and whether Forage can submit any action.
8. Root ownership and recovery
AGW is the primary customer-facing root authority:
- authenticates the wallet owner with passkeys;
- owns or controls root-chain Account Directory records;
- creates, pauses, narrows, and revokes mandates;
- initiates owner-mediated deposits, withdrawals, provider bindings, and recovery;
- creates an AGW session limited to the Automation Gateway.
AGW ownership does not magically cross a chain boundary. A remote instance is controlled through one of two explicit models:
- an authenticated root intent delivered to a chain-local Mirror Controller; or
- a separate local signer/delegate installed under an accepted mirror policy.
An AGW session key is never copied, reused, or described as a remote-chain signer.
9. Mirror model
Definition
An Account Mirror is the local control-plane record that permits a remote Account Instance to act within an allocation from the root policy.
It synchronizes:
- canonical
accountIdand chain-localinstanceId; - root router and local controller identities;
- configuration hash and approved code/module versions;
- owner epoch and mandate epoch;
- active mandate root or explicit mandate identifiers;
- per-asset and aggregate allocated budgets;
- spent, pending, and released budget;
- provider, instrument, adapter, and venue allowlists;
- message nonce, execution nonce, and replay bitmap/domain;
- valid-after, valid-until, pause, and recovery state.
It does not synchronize:
- token balances;
- legal or beneficial ownership;
- provider statements;
- corporate actions;
- investment performance;
- an assumption that an instrument on two chains has identical rights.
Authority split
| State | Authoritative writer |
|---|---|
| Owner and mandate epochs | Root control plane |
| Global budget ceiling | Root control plane |
| Allocation to each instance | Root control plane |
| Local execution nonce | Local Mirror Controller/Account Instance |
| Locally spent and pending amount | Local execution plane, reconciled to root |
| Provider account and instrument eligibility | Provider/compliance service |
| Asset balances | Chain/provider source, never the mirror |
Mirror lifecycle
UNINITIALIZED
-> SYNCING
-> ACTIVE
-> FREEZING
-> FROZEN
-> RELEASING
-> RETIRED
Any non-retired state -> PAUSED
PAUSED -> SYNCING only after a newer valid root message
An expired mirror fails closed for delegated actions. Owner-mediated recovery or exit follows the chain/provider-specific safe path and does not depend on a live automation delegate.
Message envelope
Every root-to-mirror message binds:
protocolVersion
originChainRef
destinationChainRef
rootRouter
mirrorController
legalAccountIdHash
accountId
instanceId
configurationHash
ownerEpoch
mandateEpoch
sourceNonce
previousStateHash
newStateHash
budgetRoot
validAfter
deadline
The destination verifies the authenticated origin, exact destination, chain domain, monotonic nonce, epochs, previous state, deadline, and approved configuration. Messages are idempotent. A message valid on one chain or controller is invalid everywhere else.
Budget conservation
For every Canonical Account, mandate epoch, and budgeted asset, the following buckets are mutually exclusive:
rootAvailable
+ rootAmbiguityReserve
+ sum(instanceAvailable)
+ sum(instancePending)
+ sum(instanceSpentInEpoch)
= globalMandateBudget
instanceAvailable means allocated but neither submitted nor spent. Submission moves value from instanceAvailable to instancePending; finalization moves it to instanceSpentInEpoch; a proved failure moves it back to instanceAvailable. Amounts with ambiguous finality move to rootAmbiguityReserve only after the source instance is frozen and remain unavailable everywhere else. No amount may inhabit two buckets.
A mirror cannot increase or transfer its own allocation. Reallocation uses:
freeze source
-> destination-independent acknowledgement of final local nonce/spend
-> reserve in-flight amount
-> release unused allocation at root
-> allocate to destination
-> activate destination with a new nonce and state hash
Timeouts never release an amount still considered pending. Manual reconciliation is required when delivery or acknowledgement is ambiguous.
10. Mandates and delegates
A mandate is customer-signed policy, not account ownership.
For an approved managed Club strategy, additionally bind the verified operator and operating entity, accepted strategy/fee versions, customer enrollment and exact STRATEGY instances. Discretion permits selecting trades only inside the signed instrument universe and limits. An operator cannot add to that universe, replace itself or expand a budget through a public plan update. Managed allocations require actual enforceable isolation; an accounting label with sibling access does not satisfy that promise. References to coverage identify independent reservations and never create a spendable balance or duplicate backing on another chain.
Every mandate binds:
- Legal Account, Portfolio, Canonical Account, and explicit Account Instances;
- provider accounts and custody modes;
- chain references;
- instrument and action allowlists;
- adapter, venue, target, selector, and code/config versions;
- per-call, per-day, per-epoch, and lifetime limits;
- turnover, concentration, price, quote, slippage, and market-hours limits;
- allowed recipients, normally the same customer Account Instance or provider;
- start, expiry, cooldown, maximum executions, and nonce domain;
- owner epoch, mandate epoch, and configuration hash;
- model/strategy version when AI or automation is involved.
An AGW session may call only the root Automation Gateway. A remote delegate may call only the local Mirror Controller or a narrow chain-specific executor. Neither can:
- withdraw to an arbitrary recipient;
- approve arbitrary spenders;
- install modules;
- create another delegate;
- add a provider or instrument;
- expand a budget or expiry;
- change ownership or recovery;
- execute against a sibling Account Instance.
11. Ownership rotation
Ownership rotation changes the root authority for the same verified beneficial owner. It is not transfer of the Canonical Account.
ACTIVE
-> RECOVERY_PROPOSED
-> VALUE_MOVEMENT_PAUSED
-> PROVIDER_REVALIDATION_REQUIRED
-> ALL_DELEGATES_REVOKED
-> OWNER_EPOCH_ADVANCED
-> NEW_AGW_ACCEPTED
-> MIRRORS_RESYNCED
-> ACTIVE
The process retains the accountId, rotates or replaces chain/provider instances as required, and records every former binding. A real beneficial-owner change requires a separate customer onboarding and provider-approved asset transfer; the account container itself is not sold.
12. Provider and chain adapters
Every integration implements capabilities rather than pretending all rails are equivalent:
canReadBalance
canReadCorporateActions
canBindSmartAccount
canTrade
canTransfer
canRedeem
canAutomate
canBatch
canSponsorGas
canUseLocalDelegate
supportsContractSignatures
supportsRecoveryRotation
Unsupported capabilities fail explicitly. Provider restrictions override generic chain capabilities. A transferable ERC-20 on a permissionless chain does not prove the customer is eligible to acquire it or that Forage may arrange the transaction.
13. Data model
Minimum relational records:
legal_accountslegal_account_actorsroot_authoritiesownership_rotationsportfolioscanonical_accountsaccount_instancesprovider_accountswallet_bindingsmirror_statesmirror_messagesbudget_allocationsmandatesmandate_instance_bindingsdelegatespositionsprovider_orderschain_transactionsreconciliation_snapshotsreconciliation_exceptionsaudit_eventsidempotency_records
Every external mutation has a durable status. Submitted, accepted, soft-confirmed, provider-executed, chain-settled, finalized, reconciled, failed, cancelled, expired, and manual-review states remain distinct.
14. Public API shape
GET /v1/portfolios
POST /v1/portfolios
GET /v1/portfolios/:portfolioId/accounts
POST /v1/accounts
GET /v1/accounts/:accountId
GET /v1/accounts/:accountId/instances
POST /v1/accounts/:accountId/instances
POST /v1/accounts/:accountId/pause
GET /v1/account-instances/:instanceId
GET /v1/account-instances/:instanceId/positions
GET /v1/account-instances/:instanceId/authority
GET /v1/mirrors/:mirrorId
POST /v1/mirrors/:mirrorId/sync-intents
POST /v1/mirrors/:mirrorId/freeze-intents
POST /v1/mirrors/:mirrorId/reallocation-intents
POST /v1/mandates
GET /v1/mandates/:mandateId
POST /v1/mandates/:mandateId/pause
POST /v1/mandates/:mandateId/revoke
GET /v1/reconciliations/:accountId
GET /v1/activity?portfolioId=&accountId=&instanceId=
High-risk routes produce an intent for explicit customer signing. API authentication is never treated as wallet or order authorization.
15. User experience
My bundles is a primary Portfolio view. Each bundle shows its name, actual allocation, holdings, available cash, income, fees and private realized/unrealized result. Create, Buy/Add, Sell part/all and Rebalance operate its actual positions through customer-approved intents. The bundle definition and the funded holdings remain distinct; editing targets never silently rebalances. Trading the constituent assets does not transfer account ownership or create a transferable bundle token.
The default experience is one Portfolio containing simple, inspectable Accounts. Forage resolves the necessary Canonical Account and Account Instance behind the customer's action. The StonkBrokers-like strength appears in the account browser without exposing wallet architecture as setup work:
- every account has a compact identity, purpose, custody badge, and visual container;
- opening it shows holdings, available cash, pending activity, provider/custody summary, and primary actions before technical topology;
- a Details layer reveals the actual chain/provider instances;
- each instance shows balances, positions, address, provider binding, authority, delegates, budgets, and reconciliation;
- the Account Map renders ownership, provider binding, mirror, and delegation as different edge types;
- a global Portfolio view aggregates economics but never collapses legal/product identity;
- “Move” always means a real provider/chain action, not dragging a visual container;
- ownership and recovery are separate from strategy and automation controls.
Customer-facing copy uses Account by default and Network Account or Provider Account only when the distinction affects consent, status, or recovery. “Canonical Account,” “Account Instance,” and “Mirror” are shown only in technical or advanced authority views. A mirror is labeled “control state only—assets are not mirrored.”
Account creation, funding, trading, withdrawal, and automation each use one primary flow and one normalized status. Forage should sponsor or abstract gas, select eligible routes, create required instances, and coordinate signatures where safely supported. Material differences in provider, custody, rights, price, fees, settlement, tax treatment, transferability, or risk must still be disclosed in plain language before confirmation.
16. Non-negotiable invariants
- No ERC-6551, token-bound ownership, or transferable account container.
- No pooled customer capital or fungible share representing a Personal Basket.
- One Canonical Account belongs to exactly one Legal Account for its lifetime.
accountIdis chain agnostic;instanceIdis chain/provider specific.- Every asset position resolves to one authoritative Account Instance or Provider Account.
- Mirror state is never counted as an asset balance.
- An AGW session has no implicit authority over a subaccount or remote chain.
- A remote delegate is unique, scoped, expiring, budgeted, and independently revocable.
- A compromised mandate cannot spend sibling-account or unallocated root balances.
- A provider-bound address remains unique where the provider requires it.
- Ownership rotation cannot change the verified beneficial owner.
- Cross-chain budget reallocation cannot double allocate in-flight or ambiguously acknowledged funds.
- Every budget unit occupies exactly one conservation bucket for its mandate epoch and asset.
- Provider, chain, mirror, and Forage state are reconciled independently.
- Customer assets, protocol revenue, and promotion liabilities remain separate namespaces and ledgers.
- AI output cannot modify ownership, recovery, provider binding, delegate policy, or its own authority.
17. Required proof sequence
- Create one Legal Account, one AGW Root Authority, two Portfolios, and multiple Canonical Accounts.
- Prove a canonical
accountIdremains stable across two chain-specific Account Instances. - Prove each instance has isolated balances, allowances, nonces, delegates, and mandate epochs.
- Prove an AGW session reaches only the Automation Gateway and one fixed selector.
- Create one remote EVM mirror with a small allocation and short expiry.
- Test replay, reordering, stale epochs, wrong destination, wrong controller, delayed delivery, and duplicate messages.
- Freeze, acknowledge, release, and reallocate budget without double allocation.
- Revoke both AGW session and remote delegate, then prove all old requests fail.
- Rotate AGW ownership for the same test identity and resynchronize mirrors.
- Bind a provider-approved dedicated account and prove provider, chain, and application reconciliation.
- Add one non-EVM or provider-book instance to prove the model does not depend on an EVM address.
- Complete threat model, fuzz/invariant suite, and external review before production value.
18. Source notes
- StonkBrokers documentation describes each broker NFT as owning an ERC-6551 wallet whose contents travel with the NFT.
- ERC-6551 defines accounts controlled by ownership of an NFT and includes the NFT chain ID in the token-bound account tuple.
- Abstract session keys apply policy to an AGW session; Forage does not assume that authority propagates to another account or chain.
- Robinhood Chain account abstraction documents ERC-4337 support on chain 4663, but the local account and validator still require an explicit Forage compatibility decision.