States
Params
Module parameters stored as a singleton. Contains the platform administrator address.
message Params {
string platform_admin = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
}
- platform_admin: The address authorised to register new depositories
DepositoryCounter
A global sequence counter that auto-increments when new depositories are registered. Used to generate unique depository IDs.
Depositories
A mapping from depository ID to the Depository object:
message Depository {
string id = 1;
string owner = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string pauser = 3 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string region = 4;
bool paused = 5;
}
- id: Unique identifier (auto-generated from counter)
- owner: Address that can manage the depository (authorise issuers, update params)
- pauser: Address that can pause/unpause the depository
- region: Geographic region identifier
- paused: Whether the depository is currently paused
AuthorizedIssuers
A mapping from (depository_id, issuer) pair to boolean, tracking which issuers are authorised for each depository.
Key: (depository_id, issuer) → Value: bool (true = authorised)
GlobalNotes
An indexed map storing Global Note data. Primary key is (isin, issuer):
// Internal storage format
message InternalGlobalNote {
string depository_id = 1;
string global_note_hash = 2;
string pt_denom = 3;
}
The full GlobalNote is assembled from InternalGlobalNote combined with data from PTDenom:
message GlobalNote {
Payload payload = 1;
VersionMetadata metadata = 2;
}
message Payload {
string isin = 1;
string depository_id = 2;
string issuer = 3;
string global_note_hash = 4;
string total_supply = 5 [(cosmos_proto.scalar) = "cosmos.Int"];
uint64 asset_type_id = 6;
string notarised_asset_id = 7;
}
Indexes:
- Issuer: Reverse index allowing lookup of all Global Notes by issuer address
PTDenom
A mapping from PT denom string to PTDenom metadata:
type PTDenom struct {
ISIN string
TotalSupply math.Int
}
The PT denom is compiled from asset information: pt/{asset_type_id}/{notarised_asset_id}
PTTransferability
A mapping from PT denom to transferability settings. Keyed under prefix 6 (KeyPTTransferability):
message PtTransferability {
bool paused = 1;
reserved 2;
reserved "restrict_send";
}
- paused: If true, all transfers of this PT denom are blocked (hard freeze)
Field 2 (formerly restrict_send) has been removed and is now reserved. Per-address permissions no longer live on
this record; they are held in the separate PtSendRestriction records below.
PtSendRestriction
Per-address transfer permissions for a single PT denom, keyed by (pt_denom, address) under prefix 11
(KeyPTSendRestrictions):
message PtSendRestriction {
string address = 1;
bool can_send = 2;
bool can_receive = 3;
}
- address: The account these permissions apply to
- can_send: Whether the address may send this PT denom
- can_receive: Whether the address may receive this PT denom
These records follow a blocklist model: an address with no record may both send and receive. When a record is
present, its can_send / can_receive flags are enforced. These are managed by the PT's issuer (see
MsgSetPTTransferability / MsgSendAndSetTransferability).
Addresses are canonicalized to their lowercase Bech32 rendering before every read and write, so equivalent case encodings (Bech32 also accepts all-uppercase) always map to the same record.
DepositoryBlockList
Per-depository block entries that apply across all PT denoms of a depository, keyed by (depository_id, address)
under prefix 12 (KeyDepositoryBlockList):
message DepositoryBlockEntry {
string address = 1;
bool block_send = 2;
bool block_receive = 3;
}
- address: The account being blocked
- block_send: If true, the address cannot send any PT of the depository
- block_receive: If true, the address cannot receive any PT of the depository
Managed by the depository owner (see MsgSetDepositoryBlockList). As with PtSendRestriction records, addresses are
canonicalized to their lowercase Bech32 rendering before every read and write.
Transfer Enforcement
Transfers are gated by the bank BlockBeforeSend hook (x/depository/keeper/before_send.go). For each coin in a send,
the hook applies these layers in a deliberate order — when several layers would reject, the error cites the broadest
one:
- Depository-module-account exemption: sends where either side is the depository module account are always
allowed, so issuance distribution, redemption and surrender can never deadlock on pause/blocks/restrictions. (User
MsgSendinto the depository module account is separately rejected for every denom by the bank'sblocked_module_accounts_overridelist inapp.yaml.) - Module-account quarantine: a PT send into any other module account is rejected. The recipient is classified
by asking x/auth (
GetAccount+ModuleAccountIassertion), so future modules are covered automatically. This layer is denom-aware on purpose — module accounts that accept the native denom (fee collectors, a futurex/servicefee) keep doing so; only PTs are quarantined. - Depository block list: owner-controlled, spanning all PTs of the depository. Resolving the depository needs the Global Note; only a genuine not-found means the denom is not a live PT and this layer does not apply. Any other lookup failure (dangling index, decode error) rejects the send — state corruption fails closed, never open.
- Paused flag: issuer-controlled hard freeze on the PT denom.
- Per-address restrictions: issuer-controlled blocklist; a missing record means unrestricted.
The native (bond) denom is skipped entirely. The Global Note is the single source of truth for "this is a PT": a denom
with no backing note is not a depository concern and passes through untouched — unless it squats on the reserved PT
namespace (PT-…/aPT-…), in which case it is an unbacked/spoofed PT and the send is rejected (fail closed).
EVM / ERC20 coverage
Every PT also has an ERC20 face: a dynamic precompile at the denom-derived address (keccak256(baseDenom)[12:]),
registered at issuance. ERC20 transfer/transferFrom calls on it move the same bank coins through SendCoins, so
the BlockBeforeSend hook — and therefore every layer above — applies identically to EVM transfers. Addresses are one
account in two encodings (eth_secp256k1 keys): a block list or restriction entry stored for dchain1… binds the same
20-byte account acting as 0x… through MetaMask. Note the layers match the bank sender/recipient of each hop: PT
held inside a contract is sent by the contract's own address, so screening investors happens at distribution time,
before tokens could reach a contract.
Vesting prohibition
PT denoms can never be placed in vesting accounts: the ante handler (x/depository/ante) rejects
MsgCreateVestingAccount, MsgCreatePermanentLockedAccount and MsgCreatePeriodicVestingAccount (including nested in
authz.MsgExec) whose amount touches the reserved PT namespace. Vesting-locked coins are unspendable, and
SurrenderGlobalNote must be able to pull every holder's full balance at any time — a single vesting-locked PT would
make the surrender of the entire note fail.
LatestPtHolders
Returned when a Global Note is surrendered, recording the final state of PT ownership:
message LatestPtHolders {
string isin = 1;
repeated PtHolder holders = 2;
}
message PtHolder {
string holder = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string amount = 2 [(cosmos_proto.scalar) = "cosmos.Int"];
}
This data is returned in the MsgSurrenderGlobalNoteResponse and can be used for off-chain settlement processes.
State Relationships
Params
│
└── platform_admin ─────────────────────────────────────────┐
│
DepositoryCounter │
│ │
└── generates ──► Depository ◄── registered by ─────────────┘
│
├── owner ──► can authorise issuers
│
└── pauser ──► can pause/unpause
│
▼
AuthorisedIssuers
│
└── (depository_id, issuer) → bool
│
▼
GlobalNotes
│
├── InternalGlobalNote
│ └── pt_denom ──► PTDenom
│ │
│ └── ISIN ──► PTTransferability
│
└── indexed by Issuer
Storage Keys
| Prefix | Name | Description |
|---|---|---|
| 1 | KeyParams | Module parameters |
| 2 | KeyDepositories | Depository records |
| 3 | KeyGlobalNotes | Global Note records |
| 4 | KeyAuthorisedIssuers | Issuer authorisation mapping |
| 5 | KeyDepositoryCounter | Auto-increment counter |
| 6 | KeyPTTransferability | PT transfer controls |
| 7 | KeyPtDenom | PT denomination metadata |
| 8 | KeyGlobalNotesByIssuer | Index: Global Notes by issuer |
| 11 | KeyPTSendRestrictions | Per-address PT restrictions |
| 12 | KeyDepositoryBlockList | Per-depository block entries |