Voltar para Documentação

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.

O conteúdo abaixo vem das fontes técnicas do repositório e é prerenderizado no site para leitura direta por pessoas, crawlers e agentes.

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

python
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_symbol

Lifecycle

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 0

KYC Gating

Every transfer checks both sides:

python
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.