Yamale docs ← back to the site

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:

  1. 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/treasury depends on: locked-versus-available is only real because the funds genuinely live in the module account.
  2. The outcome is deterministic. Given the offering record and the block time, any node computes the same settlement. Nobody decides it.
  3. 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:

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:

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

Open before implementation

  1. The distribution snapshot mechanism, above. Largest remaining unknown.
  2. Whether MaxHolders is a chain param or per-offering.
  3. 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.