Real estate tokenization¶
The problem¶
A building is worth millions, takes months to sell, and its ownership lives in a county deed office and a stack of paper. Tokenization turns that single illiquid asset into fractional shares that many investors can hold and transfer in seconds. That is the promise behind the tokenized real estate market: fractional access, faster settlement, a global pool of buyers.
Two things make it hard, and both are what this recipe handles. First, a property share is a regulated security. You cannot let arbitrary addresses hold or trade it. Only KYC-verified, eligible investors may, and the issuer must be able to freeze transfers or revoke a holder when a regulator requires it. Second, the arrangement is worthless unless on-chain ownership is authoritative: the token has to point at the real legal title, and the record of who owns what has to be impossible to forge.
Ownership here is a signature. Property stays owned for decades, far longer than the shelf life of today's signatures (RSA, ECDSA), which rest on math a large quantum computer can break. The public keys needed to forge them are already visible on-chain now, ready to be harvested. A forged signature would let an attacker mint a fraudulent transfer and move someone's shares, or forge a sale years after the fact, with the fake indistinguishable from the real thing. For an asset meant to stay owned across generations, that exposure is present the moment the shares exist and lasts as long as the asset does. So every share transfer and every compliance action here is a post-quantum-signed transaction, and the legal deed is anchored by its SHA3 fingerprint, keeping ownership authoritative no matter when a quantum computer arrives, or whether one already has.
The contract¶
Source: fourier/examples/real_estate.fou (compiles and runs on the WaveLedger VM).
// RealEstateToken: compliance-gated fractional ownership of a property.
// Shares transfer only between KYC-verified investors, the legal title
// deed is anchored by its SHA3 fingerprint, and every ownership change is
// a post-quantum-signed transaction recorded permanently on-chain.
contract RealEstateToken {
storage issuer: address @ 0; // sponsor who tokenized the property
storage total_shares: uint @ 1; // fixed supply = 100% of the property
storage deed_hash: uint @ 2; // SHA3 fingerprint of the off-chain title deed
storage property_ref: uint @ 3; // off-chain registry / parcel identifier
storage frozen: uint @ 4; // 1 = transfers halted (e.g. legal hold)
storage shares: map[address, uint] @ 5; // holder -> shares owned
storage is_verified: map[address, uint] @ 6; // holder -> 1 if KYC-verified
event PropertyTokenized(issuer: address, total_shares: uint, deed_hash: uint);
event InvestorVerified(investor: address);
event InvestorRevoked(investor: address);
event SharesTransferred(from: address, to: address, amount: uint);
event DeedUpdated(deed_hash: uint);
event FrozenSet(state: uint);
// The deployer is the issuer and receives 100% of the shares to
// allocate. `total` is the share count (e.g. 1000 = 0.1% granularity).
fn init(total: uint, deed: uint, property: uint) {
issuer = caller();
total_shares = total;
deed_hash = deed;
property_ref = property;
is_verified[caller()] = 1;
shares[caller()] = total;
emit PropertyTokenized(caller(), total, deed);
}
pub fn verify_investor(investor: address) {
require(caller() == issuer);
is_verified[investor] = 1;
emit InvestorVerified(investor);
}
pub fn revoke_investor(investor: address) {
require(caller() == issuer);
is_verified[investor] = 0;
emit InvestorRevoked(investor);
}
pub fn set_frozen(state: uint) {
require(caller() == issuer);
frozen = state;
emit FrozenSet(state);
}
pub fn update_deed(new_deed: uint) {
require(caller() == issuer);
deed_hash = new_deed;
emit DeedUpdated(new_deed);
}
pub fn transfer(to: address, amount: uint) -> uint {
require(frozen == 0);
require(is_verified[caller()] == 1);
require(is_verified[to] == 1);
let bal: uint = shares[caller()];
require(bal >= amount);
shares[caller()] = bal - amount;
shares[to] = shares[to] + amount;
emit SharesTransferred(caller(), to, amount);
return 1;
}
pub fn shares_of(holder: address) -> uint {
return shares[holder];
}
pub fn is_kyc(holder: address) -> uint {
return is_verified[holder];
}
pub fn get_deed() -> uint {
return deed_hash;
}
}
How it works¶
Storage¶
| Slot | Name | Type | Purpose |
|---|---|---|---|
0 | issuer | address | Sponsor who tokenized the property; set in init |
1 | total_shares | uint | Fixed supply representing 100% of the property |
2 | deed_hash | uint | SHA3 fingerprint of the off-chain legal title |
3 | property_ref | uint | Off-chain registry or parcel identifier |
4 | frozen | uint | 1 halts all transfers (legal hold) |
5 | shares | map[address, uint] | holder → shares owned |
6 | is_verified | map[address, uint] | holder → 1 if KYC-verified |
Selector layout¶
| Selector | Function |
|---|---|
0x01 | verify_investor(address) |
0x02 | revoke_investor(address) |
0x03 | set_frozen(uint) |
0x04 | update_deed(uint) |
0x05 | transfer(address, uint) -> uint |
0x06 | shares_of(address) -> uint |
0x07 | is_kyc(address) -> uint |
0x08 | get_deed() -> uint |
Compliance is built into the transfer¶
A plain token lets anyone send to anyone. A property share cannot. The transfer gate is three lines:
require(frozen == 0); // no legal hold in effect
require(is_verified[caller()] == 1); // sender is a KYC-verified investor
require(is_verified[to] == 1); // recipient is a KYC-verified investor
Only addresses the issuer has verified can hold or receive shares, so shares can never land with an unvetted party. Because caller() is post-quantum-authenticated, the "is this really the issuer?" and "is this really the share owner?" checks hold even against an adversary with a quantum computer.
Issuer controls for a regulated asset¶
Real securities need levers that ordinary tokens lack, and the issuer holds them:
verify_investor/revoke_investormanage the KYC allow-list. Revoking a holder blocks them from sending or receiving further shares.set_frozen(1)halts every transfer at once, for a court order, a dispute, or a corporate action.set_frozen(0)resumes trading.
The token points at a real, unaltered deed¶
The legal title stays off-chain in the county record or a signed PDF. deed_hash anchors that document's SHA3 fingerprint on-chain, so the token provably references one specific deed. If the recorded title is re-issued, update_deed (issuer only) rolls the fingerprint forward, and the change is a permanent DeedUpdated event. Anyone can hash the deed they were shown and confirm it matches what the token claims.
Verified end-to-end¶
Deploying the contract and running a full issuance on the WaveLedger VM confirms the compliance guards fire:
holdings issuer/alice/bob : 700 / 190 / 110
to non-KYC recipient blocked : True
non-KYC sender blocked : True
non-issuer verify blocked : True
overspend blocked : True
frozen halts transfer : True
END-TO-END REAL ESTATE: PASS
Driving it from your application¶
- Deploy with its constructor arguments: total share count, the deed fingerprint (
sha3of the title document), and a property reference. The deployer becomes theissuerand holds 100% of the shares. - Onboard investors. Run your KYC/AML off-chain, then call
verify_investor(address)for each cleared investor. - Allocate shares.
transfershares from the issuer to investors during the primary sale. (Take payment on- or off-chain; see Extending it.) - Let investors trade. Verified holders
transferamong themselves. Any transfer to or from an unverified address reverts automatically. - Stay compliant.
revoke_investorremoves a holder from the allow-list;set_frozen(1)pauses the whole book if a regulator or court requires it.
See the ABI & calldata reference for exact encoding and the Python compiler API to produce the bytecode.
Extending it¶
- Rental income distribution. Pay dividends pro-rata with the cumulative-per-share pattern: keep an
acc_income_per_shareaccumulator that grows each time income is deposited, and areward_debt[holder]that is settled on every balance change. Each holder then claimsshares[holder] * acc_income_per_shareminus their debt. This avoids iterating over holders (which no contract can do cheaply). - Forced transfer for recovery. Add an issuer-only
force_transfer(from, to, amount)for court-ordered reassignment or a holder who lost their keys, mirroring the controller operations in security-token standards like ERC-1400. - Lockups and caps. Enforce Reg D holding periods with a
locked_until[holder]timestamp, or per-investor ownership caps to respect concentration limits. - Multiple properties. Key every map by a
property_idto run a whole portfolio from one contract. - On-chain primary sale. Let investors buy shares by sending WAVE with the call (
callvalue()), transferring shares and forwarding proceeds to the issuer in the same transaction.