Redeem stablecoin to fiat
Question. Alice holds 100 hkd.hsbc in her wallet and wants 100 HKD
wired to her bank account. What is the end-to-end workflow on Hkchain?
Short answer. MsgBurn (parks the tokens) → issuer wires fiat
off-chain → MsgAckRedemption (destroys the parked tokens and records
the fiat reference). The tokens are held by the chain in escrow for the
duration of the off-chain settlement, which preserves the 1:1 backing
invariant at every moment in the lifecycle.
The flow, step by step
1. Alice signs MsgBurn
MsgBurn { holder: alice, amount: 100 hkd.hsbc }
On chain:
bankKeeper.SendCoinsFromAccountToModulemoves Alice's 100hkd.hsbcfrom her EOA into thex/stablecoinmodule-account pot.- Tokens are not destroyed yet — they are parked.
- A new
RedemptionRequestis created:{ id: N, holder: alice, issuer: hsbc, amount: 100 hkd.hsbc, state: OPEN, opened_at: blockTime } IssuerTreasury[hsbc][hkd.hsbc].pending_redemption += 100.EventBurned{ holder, amount, request_id }is emitted.MsgBurnResponsereturns therequest_idso Alice's wallet can follow the request through its lifecycle.
At this moment:
- Alice's
hkd.hsbcbalance = 0 - Module account holds +100
hkd.hsbc - Total supply is unchanged
- HSBC now has an on-chain IOU to pay Alice off-chain
2. HSBC back-office picks up the open request
HSBC's operations system subscribes to EventBurned or periodically
queries:
QueryOpenRedemptions { issuer: hsbc }
It treats open redemptions the same way a traditional bank treats
withdrawal instructions: look up the customer's fiat bank details from
KYC onboarding records (referenced on chain through
Identity.ivms101_off_chain_ref), batch, send the wire.
3. HSBC wires fiat to Alice
Off-chain, through whichever rail is appropriate — FPS, CHATS, SWIFT MT103. The outcome is a fiat-side payment reference (FPS transaction id, MT103 reference, etc.).
At this point Alice has already received her money. The on-chain settlement is still open.
4. HSBC signs MsgAckRedemption
MsgAckRedemption { issuer: hsbc, request_id: N, fiat_ref: "FPS-20260421-xxx" }
On chain:
bankKeeper.BurnCoins(moduleAccount, 100 hkd.hsbc)— the parked tokens are destroyed. Total supply drops by 100.RedemptionRequest.stateflipsOPEN → ACKED.acked_at = blockTime,fiat_refstored on the request row.IssuerTreasury[hsbc][hkd.hsbc].pending_redemption -= 100.EventRedemptionAcked{ issuer, request_id, holder, amount, fiat_ref }is emitted — this is the event regulators and Alice use to reconcile the on-chain burn with the off-chain fiat payment.
5. Alice's wallet view
Tracking the request_id returned in step 1, Alice's wallet / the
Dashboard shows:
- After step 1: "In Progress" (request opened)
- After step 4: "Settled" with the fiat reference
The block explorer links the EventBurned and EventRedemptionAcked
events by request_id.
Why parking, not burn-on-burn?
The tokens represent a claim on HSBC's reserve. Destroying them before HSBC has paid Alice would break 1:1 backing: circulation would drop while the reserve has not yet been debited. On the random-day PoR snapshot the issuer would look over-reserved, which is as much of a reporting defect as being under-reserved.
Parking-then-burning keeps circulation = reserve-backed across the
whole lifecycle:
| Phase | Circulation | Module-account pot | Reserve (off-chain) |
|---|---|---|---|
| Before burn | +100 (Alice holds) | 0 | 100 HKD backing |
After MsgBurn | 0 in Alice's EOA | +100 (parked) | 100 HKD backing (not yet paid out) |
| After fiat wire | 0 in Alice's EOA | +100 (parked) | Reserve −100 (wire sent) |
After MsgAckRedemption | 0 | 0 (burned) | Reserve −100 |
Circulation (in issuer-external EOAs) + parked pot = total supply, and total supply = reserve-backed claims at every phase.
Why MsgAckRedemption exists at all
The blockchain cannot settle fiat. It can only witness that fiat
was settled. MsgAckRedemption is HSBC's on-chain acknowledgement that
the off-chain wire completed, with a fiat reference anchored to the
original on-chain request. It is not the payment itself — the payment
already happened in step 3.
This is the cleanest separation of concerns: on-chain owns supply
accounting and audit trail; off-chain owns actual fiat movement.
Regulators reconcile the two through the fiat_ref field and the
bank's own transaction records.
Edge cases and what happens
HSBC never acks
The redemption stays OPEN forever. This is a legal / regulatory
problem, not a chain problem — the chain has no mechanism to force an
issuer to pay fiat. In a real system, HKMA would notice via
QueryOpenRedemptions returning requests older than some SLA; the
compliance layer (post-MVP) would surface a "stale redemption" flag
and escalate to supervisory intervention. MVP does not enforce
redemption SLAs on chain.
Alice changes her mind after burning
There is no MsgCancelRedemption in MVP. The tokens are already parked
and Alice's balance is already zero. If she changes her mind, the
out-of-band path is: ask HSBC to MsgDistribute the same amount back
to her. The original redemption request still closes normally with a
fiat_ref pointing to "cancelled — redistributed on chain," though
the exact convention here is unspecified in MVP.
A MsgCancelRedemption is a plausible post-MVP addition but adds a
branching path to the lifecycle and muddies the single-narrative audit
story, so the current design keeps the flow linear.
Alice's on-chain view between steps 1 and 4
Her hkd.hsbc balance is 0. She does not have an on-chain "IOU"
entry on her own account — the claim lives in the RedemptionRequest
row indexed by request_id. Wallets subscribe to EventBurned and
store the id for her. Queries by request_id or by holder address
return her open requests.
Partial fill
Not supported in MVP. MsgAckRedemption closes the full amount of the
original request or the request stays open. A request cannot be
half-acked. An issuer who wants to pay in tranches must coordinate that
off-chain with the holder; on-chain they see one MsgBurn / one
MsgAckRedemption pair.
Multiple issuers (cross-issuer redemption)
Not in MVP — Alice redeems hkd.hsbc with HSBC only. Redeeming
hkd.hsbc at Anchorpoint would require a cross-issuer settlement
protocol that is out of scope. Inter-issuer swap is its own design
surface.
On-chain state summary
| State object | Touched by MsgBurn | Touched by MsgAckRedemption |
|---|---|---|
| Alice's bank balance | −100 hkd.hsbc | — |
| Module-account balance | +100 hkd.hsbc | −100 hkd.hsbc (BurnCoins) |
| Total supply | unchanged | −100 hkd.hsbc |
RedemptionRequest[id=N] | created with state=OPEN | flipped to state=ACKED, fiat_ref set |
IssuerTreasury.pending_redemption | +100 | −100 |
EventBurned | emitted | — |
EventRedemptionAcked | — | emitted |