Docs Técnicas
KYC-Gated Token
A token where both sender and recipient must be individually whitelisted before any transfer or mint can occur. Composes BasicToken and Ownable. Includes optional per-transfer and daily-mint limits.
A token where both sender and recipient must be individually whitelisted before any transfer or mint can occur. Composes BasicToken and Ownable. Includes optional per-transfer and daily-mint limits.
Use this for regulated assets where every participant must have passed identity verification: security tokens, CBDCs, restricted fund units.
Source
import std.token.BasicToken
import std.access.Ownable
contract KYCGatedToken impl BasicToken, Ownable:
storage:
# inherited: total (u128), owner (Address)
token_name: String
token_symbol: String
kyc_approved: Map[Address, bool, 4096]
balances: Map[Address, u128, 4096]
transfer_limit: u128 = 0
daily_mint_cap: u128 = 0
mint_day_total: u128 = 0
mint_day_reset_at: u64 = 0
@construct
def initialize(name: String, symbol: String, initial_owner: Address):
self.token_name = name
self.token_symbol = symbol
self.owner = initial_owner
# ── KYC management ──────────────────────────────────────────────────
@tx
def approve_kyc(account: Address):
require(ctx.caller == self.owner, "only_owner")
self.kyc_approved = map_set(self.kyc_approved, account, True)
@tx
def revoke_kyc(account: Address):
require(ctx.caller == self.owner, "only_owner")
self.kyc_approved = map_set(self.kyc_approved, account, False)
@tx
def set_transfer_limit(limit: u128):
require(ctx.caller == self.owner, "only_owner")
self.transfer_limit = limit
@tx
def set_daily_mint_cap(cap: u128):
require(ctx.caller == self.owner, "only_owner")
self.daily_mint_cap = cap
@tx
def reset_daily_mint():
require(ctx.caller == self.owner, "only_owner")
self.mint_day_total = 0
self.mint_day_reset_at = ctx.block_timestamp
# ── Token operations ─────────────────────────────────────────────────
@tx
override def mint(amount: u128):
require(ctx.caller == self.owner, "only_owner")
require(amount > 0, "amount_must_be_positive")
require(map_get(self.kyc_approved, ctx.caller), "sender_kyc_required")
require(self.daily_mint_cap == 0 or self.mint_day_total + amount <= self.daily_mint_cap, "daily_mint_cap_exceeded")
self.balances[ctx.caller] = self.balances[ctx.caller] + amount
self.total = self.total + amount
self.mint_day_total = self.mint_day_total + amount
@tx
def mint_to(to: Address, amount: u128):
require(ctx.caller == self.owner, "only_owner")
require(amount > 0, "amount_must_be_positive")
require(map_get(self.kyc_approved, to), "recipient_kyc_required")
require(self.daily_mint_cap == 0 or self.mint_day_total + amount <= self.daily_mint_cap, "daily_mint_cap_exceeded")
self.balances[to] = self.balances[to] + amount
self.total = self.total + amount
self.mint_day_total = self.mint_day_total + amount
@tx
def transfer(to: Address, amount: u128):
require(map_get(self.kyc_approved, ctx.caller), "sender_kyc_required")
require(map_get(self.kyc_approved, to), "recipient_kyc_required")
require(amount > 0, "amount_must_be_positive")
require(self.balances[ctx.caller] >= amount, "insufficient_balance")
require(self.transfer_limit == 0 or amount <= self.transfer_limit, "transfer_limit_exceeded")
self.balances[ctx.caller] = self.balances[ctx.caller] - amount
self.balances[to] = self.balances[to] + amount
@tx
def burn(account: Address, amount: u128):
require(ctx.caller == self.owner, "only_owner")
require(amount > 0, "amount_must_be_positive")
require(self.balances[account] >= amount, "insufficient_balance")
self.balances[account] = self.balances[account] - amount
self.total = self.total - amount
@view
def balance_of(account: Address) -> u128:
return self.balances[account]
@view
def is_kyc_approved(account: Address) -> bool:
return map_get(self.kyc_approved, account)
@view
def name() -> String:
return self.token_name
@view
def symbol() -> String:
return self.token_symbolLifecycle
initialize("Real Tokenizado", "BRL-T", compliance_officer)
# onboard participants
approve_kyc(alice)
approve_kyc(bob)
# optional limits
set_transfer_limit(10_000_000) # max 10 tokens per tx
set_daily_mint_cap(1_000_000_000) # max 1000 tokens minted per day
# issuance (compliance_officer must also be KYC-approved to call mint)
approve_kyc(compliance_officer)
mint_to(alice, 500_000_000)
# transfer (both parties must be approved)
transfer(bob, 100_000_000) # called by alice
# offboard
revoke_kyc(alice)
# → alice.transfer(...) now rejects with "sender_kyc_required"
# → mint_to(alice, ...) now rejects with "recipient_kyc_required"
# daily mint accounting
reset_daily_mint() # owner resets mint_day_total to 0KYC Gating
Every transfer checks both sides:
require(map_get(self.kyc_approved, ctx.caller), "sender_kyc_required")
require(map_get(self.kyc_approved, to), "recipient_kyc_required")A missing key in kyc_approved defaults to false, so participants are blocked by default until explicitly approved.
Revocation takes effect immediately on the next transaction — there is no grace period and no pending-transfer queue. If alice's KYC is revoked mid-flight, any transaction signed after the revocation block is rejected.
Operational Limits
`transfer_limit` caps the amount per individual transfer. 0 means unlimited. The owner can raise or lower it at any time.
`daily_mint_cap` caps cumulative minting. mint_day_total accumulates across all mint and mint_to calls since the last reset_daily_mint. The contract has no clock — the owner is responsible for calling reset_daily_mint once per day. The mint_day_reset_at field records the timestamp of the last reset for audit purposes.
Audit Trail
Every approve_kyc and revoke_kyc is a signed on-chain transaction, providing an immutable record of who authorized whom and when. No off-chain database is required for the approval history.