Tokenisation and crowdfunding
An issuer opens an offering, subscribers commit funds, and if the round succeeds the chain mints a token representing the thing that was funded. The chain's job is narrow: hold the money honestly until the outcome is known, then either mint or refund. Everything about what was funded — the business, the building, the harvest, the bond — lives off-chain in an agreement the chain never sees.
What the chain enforces
Three things, and nothing else:
- Subscribed funds are held by the module, not the issuer. They move to the
issuer only at settlement, and only if settlement succeeded. This is the same
invariant
x/treasurydepends on: locked-versus-available is only real because the funds genuinely live in the module account. - The outcome is deterministic. Given the offering record and the block time, any node computes the same settlement. Nobody decides it.
- Approval gates issuance. Anyone may propose an offering; only governance may let one take money.
Valuation, due diligence, whether the warehouse exists, whether the business is solvent — none of that is consensus. It is what the approval step is for, and the approval is a human judgement recorded on-chain, not a computed one.
The offering lifecycle
CreateOffering -> PENDING
| ApproveOffering (gov only)
v
OPEN ---- Subscribe / WithdrawSubscription
|
| close_time reached
v
CLOSED ---- Settle (permissionless crank)
|
+-----------+-----------+
v v
SETTLED REFUNDED
CreateOffering is permissionless and writes a PENDING record that can hold
no funds. ApproveOffering accepts only the gov module account as signer.
Keeping those in separate messages is deliberate: an approval path reachable by
the applicant is the critical bug in this module, and one message with an
internal branch is exactly how that bug gets written.
Settlement is a permissionless crank rather than an EndBlocker sweep. An
EndBlocker that iterates every closed offering is a denial-of-service surface
that grows with usage; a crank puts the cost on whoever wants the outcome, and
anyone can pay it. The offering is fully determined at close_time, so nothing
depends on when the crank runs.
Two raise modes
Declared per offering, scrutinised at approval:
RAISE_MODE_ALL_OR_NOTHING — if raised < target at close_time, every
subscriber is refunded in full and nothing is minted. This is the model retail
participants can be defended with, and the one a regulator recognises.
RAISE_MODE_KEEP_WHAT_YOU_RAISE — the issuer takes whatever was raised and
tokens mint pro-rata. Appropriate for an issuer funding a divisible thing (ten
hectares planted instead of thirty), dangerous for an indivisible one. Half a
bridge is not half as useful as a bridge, and approval is where that gets
caught.
The mode is on the record because both are legitimate and the difference is about the asset, not the platform. Every client must state which mode applies in plain words before a user commits funds — "you get your money back if this does not reach its goal" or "you do not". That sentence is most of the protection.
The token
Settlement mints a plain bank denom, tok/{offering_id}/{symbol}. It transfers
freely and can be pooled on x/amm like any other coin. The offering id is in
the denom because symbols are not unique and never will be — two issuers will
both want SOLAR.
Free transfer is a deliberate choice with a real cost, recorded here so nobody
has to re-derive it. A freely tradable token representing SME equity or a
municipal bond is a bearer instrument that can reach anyone with a wallet,
including people who never signed the agreement that gives it value and
jurisdictions where offering it is an offence. The chain cannot fix this; the
approval step and the issuer's own terms have to. If that proves untenable for
a given asset class, the retrofit is a send restriction on the tok/ prefix —
which is consensus-breaking and therefore a decision to make before mainnet,
not after.
NFTs are minted by a declared authority, never by anyone
Fungible offering tokens are minted by settlement, so their supply is bounded by what was actually subscribed — the maths is the permission. Non-fungible assets have no such bound. A title deed, a warehouse receipt, a vehicle registration: each is a claim that someone with standing has to make, and if anyone can mint one then the token means nothing. A registry that will attribute a deed to whoever asks is not a registry.
This is not a user-facing feature. There is no MsgCreateCollection that a
subscriber, an issuer, or an application can send — collections are chain-level
constructs and they come into existence only by governance. That is the
difference from the fungible path above, where anyone may apply to run an
offering and governance merely approves it. Here there is no application step to
approve, because a registry of deeds is not something a chain grants on request.
So minting is two-tier, the same shape as x/custody's attestors and
x/oracle's appointed valuers:
Governance appoints the authority. MsgSetCollectionAuthority accepts only
the gov module account as signer, and binds an address to a collection: the
lands ministry to deeds/ci, the licensed warehouse operator to their own
receipts, the vehicle registry to theirs. Appointment is a public, revocable,
on-chain act.
The authority mints, and only into its own collection. MsgMintAsset
checks the signer against the collection's authority and rejects everything
else. A mint names its recipient, so the asset is attributed to a wallet at
creation and never exists unattributed. There is no self-mint-then-transfer
path, because that path is where an authority laundering assets to itself
becomes indistinguishable from an authority doing its job.
Three rules that follow, and are easier to hold than to retrofit:
- An empty authority is not a collection. A collection whose authority is
unset or revoked accepts no mints. Not "falls back to governance" — refuses.
The failure mode of a permissive default here is unlimited issuance, which is
precisely what
x/custodyrefuses when it rejects an attestation threshold below two. - Revocation does not burn. Removing an authority stops future mints and
touches nothing already issued. Deciding an existing asset was wrongly issued
is a seizure, and seizures go through
x/enforcement, where two thirds decide — not through a registry keeper. - The authority cannot move what it minted. Once attributed, the asset is the holder's. An authority that can move assets after issuance is a custodian with unlimited power over everything it has ever touched, which is the thing the user explicitly ruled out for funds and applies with equal force here.
Whether these NFTs transfer freely is a separate question from the fungible tokens above and should not inherit that answer by default. A deed that trades without the registry knowing is a deed the registry cannot honour.
Distributions — the hard part
Three of the four target asset classes pay their holders: revenue share, bond coupons, rent. Free transfer makes this genuinely difficult, and the naive design is wrong in a way that is worth stating.
The naive design pays holders at the moment the issuer distributes. But by then
a large share of the tokens sit in an x/amm pool, which is a module account
that cannot want a dividend. Paying the pool silently transfers value from
token holders to liquidity providers, and does it every single distribution.
The design that survives free trading:
MsgDistributemoves the payment into the module account and writes a distribution record stamped with the current block height.- Holders of record at that height claim against it with
MsgClaim. Entitlement is a holder's balance at the snapshot height over the supply at that height, excluding balances held by module accounts. - Unclaimed funds return to the issuer after an expiry, so the module does not accumulate dust forever.
Excluding module accounts is the load-bearing detail: it makes the AMM pool's share undistributable rather than misdistributed, and it means the pool's price reflects a token whose dividends accrue to whoever holds it directly. Liquidity providers forgo income in exchange for fees, which is a trade they can evaluate.
Building the snapshot needs a bounded holder set — iterating unbounded bank
owners inside a handler is a halt. MaxHolders caps it per offering, checked on
every transfer of a tok/ denom.
This is the part of the module that is not yet designed to the point of implementation. The snapshot mechanism above is a sketch with a known cost, and it should be settled before the keeper is written.
State
Offerings Map[uint64, Offering] // id -> record
Subscriptions Map[Pair[uint64, string], Coin] // (offering, subscriber)
Distributions Map[Pair[uint64, uint64], Dist] // (offering, height)
Claims KeySet[Triple[uint64, uint64, string]]
NextOfferingID Sequence
Offering ids start at 1. Zero is indistinguishable from an unset proto field, and that has already cost this project once.
Subscriptions is keyed by a pair so a subscriber's commitment to one offering
is a single read. Listing every subscriber to an offering is a prefix scan;
listing every offering one account joined is not, and belongs in the indexer
rather than in a second index nobody maintains.
Messages
| Message | Signer | Effect |
|---|---|---|
MsgCreateOffering |
anyone | Writes a PENDING record. Holds no funds. |
MsgApproveOffering |
gov only | PENDING -> OPEN. |
MsgRejectOffering |
gov only | PENDING -> REJECTED, terminal. |
MsgSubscribe |
anyone | Moves funds to the module account. |
MsgWithdrawSubscription |
subscriber | Only while OPEN. |
MsgSettleOffering |
anyone | Crank. Mints or refunds per mode. |
MsgDistribute |
issuer | Funds a distribution at the current height. |
MsgClaim |
holder | Claims one distribution. |
MsgSubscribe carries min_tokens_out. The price is fixed at creation so the
computation is not state-dependent, but pro-rata allocation under
keep-what-you-raise is, and a user signs against a state that has moved.
Dependencies
x/amm— pools fortok/denoms follow automatically from free transfer. No new work, but the distribution exclusion above depends on the pool being a module account.x/oracle— the appointed-valuer path already built for NFT appraisal is what real estate and infrastructure offerings need for periodic valuation. Nothing new to build; it needs wiring.x/alias— issuers should be identifiable by user ID, not raw address, everywhere a subscriber sees them.- No lending, no liquidation. Nothing here is collateralised. If that changes, liquidation is a separate module with its own keeper incentives and bad-debt policy, and it should not be smuggled in here.
Open before implementation
- The distribution snapshot mechanism, above. Largest remaining unknown.
- Whether
MaxHoldersis a chain param or per-offering. - Whether an issuer must post a bond that is forfeited on non-delivery. It is the only on-chain lever against an issuer who takes the money and does nothing, and without it the chain's guarantee ends at settlement.