Hkchain

Freeze an address on compliance action

Question. A sanctions update, court order, or supervisory directive requires blocking a specific address — say a holder of hkd.hsbc — from moving tokens. How does that happen on chain, who signs, and what does it actually prevent?

Short answer. MsgFreezeAccount { address } signed by the compliance authority (not governance, not the issuer). The address is marked frozen in x/stablecoin. Frozen addresses cannot initiate redemption, and the compliance layer blocks their outbound transfers (see the MVP note under "What the freeze prevents"). MsgUnfreezeAccount reverses the marker.

The flow, step by step

0. Prerequisite: who is the compliance authority?

The chain keeps a single compliance-authority address as a parameter of the compliance module. It is the only signer authorized for compliance actions — address freezes, sanctions-list updates, flag resolution. Governance rotates this address through MsgUpdateParams on the compliance module; day-to-day compliance actions do not need a governance vote per operation.

Keeping this separate from the governance authority is deliberate. Governance signs policy changes; compliance signs operational actions against individual addresses. A single key with both roles would collapse an important audit boundary — the chain would lose the ability to distinguish "policy decision" from "enforcement action."

1. Compliance signs MsgFreezeAccount

MsgFreezeAccount {
  authority: compliance_auth_addr,
  address: alice_addr,
  reason: "ofac-sdn-list-20260501",
  evidence_hash: "sha256:3f8a..."
}

On chain:

  • Authority check: authority must equal the compliance authority resolved from the compliance module's parameters at tx time. Any other signer is rejected.
  • Address validation: address must be a well-formed bech32 address. The chain does not require the address to have any existing hkd.* balance or prior activity — an address can be frozen preemptively (e.g. before the holder acquires any tokens, as part of a sanctions-list push).
  • A frozen marker is written at FrozenAccounts[alice_addr].
  • EventAccountFrozen { address, frozen: true, reason, evidence_hash } is emitted.

The reason string is a plain-text code identifying the compliance basis (sanctions list reference, case ID, directive number). The evidence_hash is the SHA-256 of the off-chain case file, anchored on chain so that a future reviewer can verify the on-chain action against the exact document that authorized it. The file itself stays off-chain (regulators and issuers keep sensitive case material off-ledger); the hash gives cryptographic binding.

2. What the freeze prevents

Once the marker is in place, two things change for that address:

Cannot initiate redemption. A frozen address cannot submit MsgBurn. The chain rejects the tx at handler time, regardless of which hkd.* denom is involved. Redemption is a privileged operation — it parks supply and opens a settlement obligation on the issuer — so blocking it at the address level removes that obligation from the frozen holder entirely.

Cannot transfer tokens out. Plain token transfers from the frozen address (via x/bank, whether signed natively or through the ERC-20 interface on the EVM side) are rejected before the tx reaches supply processing. The frozen address can hold hkd.* but cannot send it.

MVP: the freeze marker is currently consulted on the redemption path only, so MsgBurn is refused but an ordinary transfer out is not. The primitive that stops an address transacting today is blacklisting — see Blacklist an address, which the sanctions and flag-review paths both reach. A compliance action that must halt all outbound movement should blacklist, or blacklist in addition to freezing.

The cumulative effect in the finalProduct form: tokens held by a frozen address are on-chain but inert. They exist in balance queries, they count toward total supply, they can be observed by auditors — but they cannot move until unfrozen.

3. What the freeze does not prevent

Inbound transfers. A frozen address can still receive hkd.* tokens. This is intentional:

  • The frozen address cannot spend what it receives anyway, so inbound is operationally a no-op for the frozen holder.
  • Blocking inbound would force senders (and the issuer's distribute flow) to check recipient-freeze state on every transfer, which creates a different class of bug: transfers that inexplicably fail because a counterparty was frozen mid-flight, with no remedy for the sender.
  • For sanctions semantics, receipt-without-spend matches the real-world model: frozen bank accounts can still receive wire deposits; the account holder just cannot use them.

If a specific policy requires blocking all inbound as well (e.g. a total-quarantine action), that is a separate operational decision layered on top — the issuer can refuse to MsgDistribute to the frozen address, other senders can choose not to transfer. The chain does not impose it as a primitive.

Redemption already in flight. If the frozen holder had an OPEN RedemptionRequest at the moment of freeze, the request remains in place. The issuer may still ack the redemption (settling off-chain to the holder's registered bank account under whatever rules the issuer's compliance team applies). Freezing the on-chain address does not retroactively void on-chain obligations the issuer has already committed to settle.

Queries and audit access. The frozen address's balances, history, and any associated RedemptionRequest rows remain queryable. Freeze is an action on movement, not visibility.

4. Compliance signs MsgUnfreezeAccount

MsgUnfreezeAccount {
  authority: compliance_auth_addr,
  address: alice_addr,
  evidence_hash: "sha256:9c2b..."
}

On chain:

  • Same authority check.
  • Frozen marker is deleted.
  • EventAccountFrozen { address, frozen: false, evidence_hash } is emitted — same event type, negative value.

reason is not required on unfreeze (the positive action's reason is already on chain); evidence_hash typically points to the off-chain decision to lift the freeze (investigation closed, sanctions list update, court order rescinded).

From the next block, the address can burn and send again. Tokens that were held during the freeze remain where they were — unfreeze does not forfeit or redistribute them; it simply removes the movement block.

The audit trail, end to end

For any freeze-unfreeze cycle on a single address, the chain preserves a structured audit record:

  1. EventAccountFrozen { frozen: true, reason, evidence_hash } at block N.
  2. (During the freeze) an attempted MsgBurn from the address is rejected with a freeze error — as is an outbound transfer, in the finalProduct form noted above; those rejected txs are still part of the chain's mempool/rejection history, observable for supervisory review.
  3. EventAccountFrozen { frozen: false, evidence_hash } at block M.

A compliance review after the fact can reconstruct: who signed, when, on what evidence, what the freeze prevented (if anything was attempted), and who released it. The two evidence_hash fields bind the on-chain actions to their off-chain authorization documents.

Edge cases and what happens

Freeze on a address with no tokens

Supported. The marker is written regardless of balance. Useful for preemptive sanctions-list propagation — the freeze is in place before any hypothetical future token movement.

Freeze from a non-compliance signer

Rejected with an unauthorized-authority error. Governance cannot sign MsgFreezeAccount directly (governance's role is to rotate the compliance authority, not to act as one). An issuer cannot freeze an address either — freeze is a chain-wide compliance action, not an issuer-scoped one.

Frozen address attempts self-transfer

Rejected in the finalProduct form: self-transfer (sending to the same address) still counts as outbound for the freeze check. Under the MVP scope noted above, the transfer path is not freeze-checked at all, so a self-transfer succeeds.

Freezing the compliance authority itself

Mechanically possible — nothing in the chain prevents it — but it is an operational foot-gun. Governance should rotate the compliance authority before a freeze lands on the old address; otherwise the frozen compliance authority can no longer sign the unfreeze.

External sanctions feed integration

The chain carries the freeze primitive; the upstream source of "which addresses to freeze" is orthogonal. A live sanctions-feed integration (automated push from OFAC, HKMA's own list, a chain analytics provider) is an external service layered on top of the compliance authority's signing key — not a chain feature. MVP includes manual freeze by the compliance authority; automated upstream feeds are a future integration, out of MVP scope.

Multiple freezes on the same address

The marker is idempotent. Re-issuing MsgFreezeAccount on a frozen address re-emits the event with the latest reason and evidence_hash — useful for attaching updated case material — but does not double-lock anything. A single MsgUnfreezeAccount lifts it regardless of how many freezes were issued.

On-chain state summary

State objectTouched by MsgFreezeAccountTouched by MsgUnfreezeAccount
FrozenAccounts[address]marker writtenmarker deleted
Bank balance of frozen addressunchangedunchanged
RedemptionRequest rows involving the addressunchangedunchanged
EventAccountFrozenemitted (frozen: true)emitted (frozen: false)