What is ERC-3643 on EmpoorioChain?


ERC-3643 is an Ethereum token standard for regulated, permissioned issuance — it layers eligibility checks (KYC/AML), transfer restrictions, and forced-transfer/freeze capabilities on top of a fungible-token contract.

Key Characteristics

  • Eligibility Verification: Transfers are only allowed between holders who have completed KYC/AML checks.
  • Regulatory Compliance: Whitelists/blacklists, investor caps, and jurisdictional rules are enforced at the protocol level.
  • Enforcement & Control: Unauthorized transfers are blocked, and an issuer can forcibly transfer or burn tokens to satisfy real-world regulatory obligations.

The EmpoorioChain Equivalent: pallet-emp-rwa

EmpoorioChain's compliance suite includes pallet-emp-rwa, a native pallet purpose-built for permissioned, real-world-asset-style tokens. It sits alongside pallet-emp-assets rather than requiring per-token compliance contracts — eligibility, freezing, and forced-transfer logic are enforced by the runtime itself for any asset registered under it.

Key capabilities (verify exact extrinsic names against the current pallet reference here)

  • Holder Approval: A compliance authority approves or revokes eligible holders on-chain, gating transfers at the runtime level.
  • Account Freezing: A compliance authority can freeze a specific holder, blocking further transfers without touching the asset's core logic.
  • Forced Transfer / Recovery: The compliance authority can execute a forced transfer to satisfy legal orders or recover tokens from a compromised or fraudulent account.
  • Auditability: Every compliance action (approval, freeze, forced transfer) is an on-chain extrinsic, giving auditors a native transaction history instead of relying on off-chain records.

How Does This Compare to a Custom Solidity Implementation?

On Ethereum, ERC-3643-style compliance is usually built as bespoke Solidity — a token contract with its own KYC mappings, a freeze mapping, and a privileged compliance-authority role, all written and audited per project.

On EmpoorioChain, the equivalent logic already exists as a shared, once-audited pallet. A team doesn't write and deploy compliance logic per token — it registers an asset under pallet-emp-rwa and calls the pallet's existing extrinsics to manage eligible holders.

javascript
pragma solidity ^0.8.28;

interface IEmpAssets {
    function transfer(address to, address mintAddress, uint256 amount) external;
    function getTokenAccount(address owner, address token) external view returns (uint256 balance, bool isFrozen);
    function mintTokens(address to, address mintAddress, uint256 amount) external;
}

contract PermissionedAsset {
    IEmpAssets public immutable empAssets;
    address public immutable mintAddress;

    mapping(address => bool) public isKYCApproved;
    mapping(address => bool) public frozen;

    address public complianceAuthority;
    address public transferHookProgram;

    event KYCApproved(address indexed user, bool status);
    event AccountFrozen(address indexed user, bool status);
    event TransferHookSet(address indexed hookProgram);
    event ForcedTransfer(address indexed from, address indexed to, uint256 value);
    event Transfer(address indexed from, address indexed to, uint256 value);

    constructor(address _empAssets, address _mint, address authority) {
        empAssets = IEmpAssets(_empAssets);
        mintAddress = _mint;
        complianceAuthority = authority;
    }

    modifier onlyComplianceAuth() {
        require(msg.sender == complianceAuthority, "not compliance auth");
        _;
    }

    function approveKYC(address user, bool approved) external onlyComplianceAuth {
        isKYCApproved[user] = approved;
        emit KYCApproved(user, approved);
    }

    function freezeAccount(address user, bool freeze) external onlyComplianceAuth {
        frozen[user] = freeze;
        emit AccountFrozen(user, freeze);
    }

    function setTransferHook(address hookProgram) external onlyComplianceAuth {
        transferHookProgram = hookProgram;
        emit TransferHookSet(hookProgram);
    }

    function setComplianceAuthority(address newAuthority) external onlyComplianceAuth {
        complianceAuthority = newAuthority;
    }

    function transfer(address to, uint256 amount) external {
        require(!frozen[msg.sender] && !frozen[to], "account frozen");
        require(isKYCApproved[msg.sender] && isKYCApproved[to], "KYC required");
        if (transferHookProgram != address(0)) {
            bool ok = ITransferHook(transferHookProgram).onTransfer(msg.sender, to, amount);
            require(ok, "blocked by hook");
        }
        empAssets.transfer(to, mintAddress, amount);
        emit Transfer(msg.sender, to, amount);
    }

    function forceTransfer(address from, address to, uint256 amount) external onlyComplianceAuth {
        // temporarily unfreeze to bypass EmpAssets.transfer require(msg.sender == owner)
        frozen[from] = false;
        empAssets.transfer(to, mintAddress, amount);
        frozen[from] = true;
        emit ForcedTransfer(from, to, amount);
    }
}

interface ITransferHook {
    function onTransfer(address from, address to, uint256 amount) external returns (bool);
}

Holder Eligibility

Only accounts explicitly approved by the compliance authority can send or receive the asset — enforced by the pallet before a transfer is applied, the same guarantee ERC-3643's eligibility check provides on Ethereum.

Freeze Enforcement

A compliance authority can freeze an individual holder's ability to transfer at the pallet level — no custom contract logic required, the same outcome as ERC-3643's account-freeze feature.

Enforcement Point

Where ERC-3643 implementations typically run a transfer-hook callback for each transfer, pallet-emp-rwa performs the equivalent eligibility/freeze checks natively inside the pallet's transfer extrinsic, before the transfer is applied. In practice that means:

  • Ineligible or frozen accounts are rejected before state changes
  • No separate hook contract to deploy or wire up per asset
  • Compliance logic is part of the audited pallet, not custom per-project code

This gives the same protocol-level enforcement ERC-3643's transfer hooks provide, without a separate hook contract per token.

Forced Transfer (Override Authority)

The compliance authority can execute a forced transfer between any two eligible accounts — the EmpoorioChain equivalent of ERC-3643's permanent-delegate / forced-transfer capability, used for legal orders or fraud recovery.

How to Use pallet-emp-rwa

1. Conceptual Map

ERC-3643 Featurepallet-emp-rwa Equivalent
KYC whitelistApproved-holder storage, gated by the compliance authority
Account freezeNative freeze extrinsic
Force / claw-back transferNative forced-transfer extrinsic
On-transfer custom logicBuilt into the pallet's transfer extrinsic, not a separate hook
Compliance adminA designated AccountId32 (or governance-controlled origin)

2. End-to-End Flow (simplified)

latex
User → pallet-emp-assets (transfer) ↘
                                    pallet-emp-rwa eligibility check → OK / ERR
                                    ↘
                             pallet-emp-assets (balance change applied)

A transfer of a pallet-emp-rwa-registered asset is a single extrinsic; the runtime checks both accounts' eligibility and freeze status as part of applying it. If either check fails, the whole extrinsic is rejected and no state changes — mirroring ERC-3643's on-chain enforcement, without a separate hook call.

3. Minimal Client Example

Client code for compliance actions is a normal @polkadot/api call: sign and submit an approve-holder or freeze-holder extrinsic from the compliance-authority account, and submit ordinary transfers from holder accounts. There is no separate program to write, build, or deploy for the compliance logic itself.

javascript
// EmpoorioChain does not need a separate "transfer hook" contract for this —
// pallet-emp-rwa (part of the emp-compliance-suite) enforces eligibility and
// freeze rules natively at the runtime level, before a transfer is applied.
// The client side simply calls its extrinsics via @polkadot/api; there is no
// program to write, build, or deploy for the compliance logic itself.
import { ApiPromise, WsProvider, Keyring } from "@polkadot/api";

const api = await ApiPromise.create({
  provider: new WsProvider("wss://rpc.testnet.empooriochain.org"),
});
const keyring = new Keyring({ type: "sr25519" });
const complianceAdmin = keyring.addFromUri("//compliance-admin-seed-here");

const assetId = 1;

// Whitelist a holder after off-chain KYC/AML has cleared them.
async function approveHolder(accountId) {
  const hash = await api.tx.empRwa
    .approveHolder(assetId, accountId)
    .signAndSend(complianceAdmin);
  return hash.toString();
}

// Freeze/unfreeze an account — enforced natively, no custom logic required.
async function freezeHolder(accountId) {
  const hash = await api.tx.empRwa
    .freezeHolder(assetId, accountId)
    .signAndSend(complianceAdmin);
  return hash.toString();
}

4. emp-cli Quickstart (illustrative)

A typical flow: register a new permissioned asset under pallet-emp-rwa as the compliance admin, approve each holder after off-chain KYC/AML clears them, then transfers between approved holders succeed automatically while transfers involving a non-approved or frozen account are rejected by the runtime itself. Exact command names should be checked against the current emp-cli docs.

bash
# emp-cli quickstart (illustrative — see EmpoorioChain CLI docs for the
# exact subcommands and flags, which evolve alongside the pallet).

# 1. Register a new permissioned asset under pallet-emp-rwa
emp-cli assets create --admin $COMPLIANCE_ADMIN

# 2. Approve holders after off-chain KYC/AML
emp-cli emp-rwa approve-holder --asset-id 1 --account $HOLDER

# 3. Transfers between approved holders succeed automatically;
#    transfers involving a non-approved or frozen account are rejected
#    by the runtime itself, not by client-side validation.
emp-cli assets transfer --asset-id 1 --to $HOLDER --amount 10

EVM TO EMPOORIOCHAIN

Start building on EmpoorioChain

Intro to EmpoorioChain Development

Read

EmpoorioChain Pallet Reference

Read

emp-cli Quickstart

Read

More EmpoorioChain Developer Tools

Read

Node storing all history and participating in consensus

  • Ethereum: Archive Node
  • EmpoorioChain: Full node with archive pruning enabled

Node producing/finalizing blocks

  • Ethereum: Full Node (execution + consensus client)
  • EmpoorioChain: Validator node (Aura block production + GRANDPA finality, single Rust binary)

Node serving RPC without validating

  • Ethereum: Light Node
  • EmpoorioChain: RPC node (non-validator, full state)
What is ERC-3643 on EmpoorioChain? | empoorio