From 41b4927adfefbca1bc1e0112e280207e3d0a350f Mon Sep 17 00:00:00 2001 From: Vijith Bheemireddi Date: Tue, 14 Jul 2026 12:10:43 -0700 Subject: [PATCH 1/9] docs: add ERC-20 to TIP-20 migration guide --- src/pages/docs/guide/bridge-bungee.mdx | 2 + src/pages/docs/guide/bridge-layerzero.mdx | 2 + src/pages/docs/guide/bridge-relay.mdx | 2 + src/pages/docs/guide/issuance/index.mdx | 7 + .../guide/issuance/migrate-erc20-to-tip20.mdx | 382 ++++++++++++++++++ src/pages/docs/quickstart/integrate-tempo.mdx | 6 + vocs.config.ts | 4 + 7 files changed, 405 insertions(+) create mode 100644 src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx diff --git a/src/pages/docs/guide/bridge-bungee.mdx b/src/pages/docs/guide/bridge-bungee.mdx index fe3bae07..287a097d 100644 --- a/src/pages/docs/guide/bridge-bungee.mdx +++ b/src/pages/docs/guide/bridge-bungee.mdx @@ -7,6 +7,8 @@ description: Bridge tokens to and from Tempo using Bungee. Covers Bungee Link, d [Bungee](https://www.bungee.exchange/) is a cross-chain routing protocol built by the [SOCKET](https://docs.socket.tech/) team. For Tempo, the recommended integration path is Bungee Deposit: request a quote, execute the returned source-chain transaction, and track the request until Bungee delivers funds on the destination chain. +If you are bringing an existing ERC-20 to Tempo, first decide whether to preserve it as the canonical asset behind a bridge adapter or migrate issuance to native TIP-20. See [Migrate an ERC-20 to TIP-20](/docs/guide/issuance/migrate-erc20-to-tip20). + Tempo's Bungee chain ID is **`4217`**. ## How Bungee Deposit works diff --git a/src/pages/docs/guide/bridge-layerzero.mdx b/src/pages/docs/guide/bridge-layerzero.mdx index 2d4a1ad1..931f25f9 100644 --- a/src/pages/docs/guide/bridge-layerzero.mdx +++ b/src/pages/docs/guide/bridge-layerzero.mdx @@ -14,6 +14,8 @@ There are two flavors of OFT on Tempo: Both use the same underlying LayerZero endpoint on Tempo. +If you are bringing an existing ERC-20 to Tempo, first decide whether to preserve it as the canonical asset behind a bridge adapter or migrate issuance to native TIP-20. See [Migrate an ERC-20 to TIP-20](/docs/guide/issuance/migrate-erc20-to-tip20). + ## USDC.e and native USDC USDC.e is the bridged representation of USDC on Tempo. It is backed 1:1 by native USDC in Stargate liquidity infrastructure. diff --git a/src/pages/docs/guide/bridge-relay.mdx b/src/pages/docs/guide/bridge-relay.mdx index 5437ef2b..fd45e788 100644 --- a/src/pages/docs/guide/bridge-relay.mdx +++ b/src/pages/docs/guide/bridge-relay.mdx @@ -7,6 +7,8 @@ description: Bridge tokens to and from Tempo using Relay. Includes supported tok [Relay](https://relay.link/) is a cross-chain payments network powered by a solver that fills bridge requests instantly. Users deposit on the source chain and receive funds on the destination chain within seconds - no lock-and-mint or messaging protocol required. +If you are bringing an existing ERC-20 to Tempo, first decide whether to preserve it as the canonical asset behind a bridge adapter or migrate issuance to native TIP-20. See [Migrate an ERC-20 to TIP-20](/docs/guide/issuance/migrate-erc20-to-tip20). + Tempo's Relay chain ID is **`4217`**. ## Contracts on Tempo diff --git a/src/pages/docs/guide/issuance/index.mdx b/src/pages/docs/guide/issuance/index.mdx index 81fdd681..00268237 100644 --- a/src/pages/docs/guide/issuance/index.mdx +++ b/src/pages/docs/guide/issuance/index.mdx @@ -19,6 +19,13 @@ Create and manage your own stablecoin on Tempo. Launch the token first, then con title="Create a Stablecoin" /> + + >Factory: createToken(name, symbol, currency, quoteToken, admin, salt, logoURI) + Factory-->>Token: Create native TIP-20 token + Factory-->>Admin: Grant token admin role + + Note over Admin,Operator: One-time or occasional role setup + Admin->>Token: grantRole(ISSUER_ROLE, Operator) + Admin->>Token: grantRole(PAUSE_ROLE, pause wallet) + Admin->>Token: grantRole(UNPAUSE_ROLE, unpause wallet) + + opt Compliance required + Note over PolicyAdmin,Token: One-time policy setup and attachment + PolicyAdmin->>Registry: Create or select policy + Admin->>Token: changeTransferPolicyId(policyId) + + Note over PolicyAdmin,Registry: Ongoing compliance maintenance + PolicyAdmin->>Registry: Add, remove, allow, or block wallets + end + + Note over Operator,Vault: Ongoing issuance operations + Operator->>Token: mint(Vault, amount) + Token->>Token: Check ISSUER_ROLE, pause state, and supply cap + Token->>Registry: Authorize Vault as mint recipient + Token-->>Vault: TIP-20 balance increases`} /> + +## 3. Complete the implementation checklist + +Before production deployment, confirm: + +- TIP-20 token is deployed through `TIP20Factory`. +- Token name, symbol, currency, quote token, and logo are correct. +- Admin address is controlled by the issuer or approved operator. +- `ISSUER_ROLE` is granted only to the intended issuer wallet, manager, or bridge adapter. +- Native role queries use `hasRole(account, role)`, and each role's administrator is confirmed with `getRoleAdmin(role)`. +- `PAUSE_ROLE` and `UNPAUSE_ROLE` are assigned. +- Supply cap is set if required. +- Transfer policy is configured if compliance is required. +- If using a compound policy, sender, recipient, and mint-recipient checks are tested independently. +- Existing ERC-20 supply is burned, locked, or reconciled if this is a migration or bridge. +- Decimal conversion is tested. +- Mint and burn flows are tested. +- Mint tests cover the supply cap, paused state, and TIP-403 mint-recipient authorization. +- Pause tests confirm that transfers, minting, normal burns, and `burnBlocked` are blocked while role and configuration administration remain available. +- Transfer and transferFrom are tested. +- Custody and treasury wallets are funded and labeled. +- Tokenlist and explorer metadata are ready. +- The issuer has an operational runbook for role changes, pausing, minting, burning, and reconciliation. + +## 4. Inspect the current ERC-20 + +Before deploying on Tempo, identify the current token architecture. + +| Item | What to check | +|---|---| +| Token address | Current ERC-20 or proxy address | +| Implementation | Proxy implementation, if upgradeable | +| Metadata | `name`, `symbol`, `decimals` | +| Supply | `totalSupply`, current holder or treasury vault | +| Roles | Admin, minter, burner, pauser, upgrader | +| Mint flow | Who can create supply and through what contract | +| Burn flow | Who can destroy supply and whether `burnFrom` is used | +| Pause flow | Who can pause and unpause | +| Compliance | Allowlist, blocklist, sanctions, KYC, transfer hooks | +| Bridge | Source adapter, destination adapter, replay protection | +| Oracle | NAV, FX, redemption price, or yield calculation | + +## 5. Map OpenZeppelin patterns to TIP-20 + +Map by capability, not by contract name. + +| OpenZeppelin pattern | Common behavior | Tempo mapping | +|---|---|---| +| `ERC20` / `ERC20Upgradeable` | Core transfer and allowance surface | Native TIP-20 transfer and allowance surface | +| `IERC20Metadata` | `name`, `symbol`, `decimals` | Set name, symbol, and currency at TIP-20 creation | +| `AccessControl` | Role-based permissions | TIP-20 native roles | +| `Pausable` | Pause state and pause checks | `PAUSE_ROLE` and `UNPAUSE_ROLE` | +| `ERC20Burnable` | `burn` and often `burnFrom` | An `ISSUER_ROLE` holder can burn only its own TIP-20 balance; use pull-then-burn for user funds | +| `ERC20Wrapper` | Deposit underlying ERC-20, mint wrapped ERC-20 | Use an external adapter that mints TIP-20 | +| Proxy upgrade pattern | Upgradeable implementation and storage | Not portable to TIP-20 | + +## 6. Map roles + +| Current role or control | Tempo role | +|---|---| +| `DEFAULT_ADMIN_ROLE` or owner | TIP-20 `DEFAULT_ADMIN_ROLE` for role and configuration administration; grant `ISSUER_ROLE` separately for minting | +| `MINTER_ROLE` | `ISSUER_ROLE` | +| `BURNER_ROLE` | `ISSUER_ROLE` for normal burns | +| `PAUSER_ROLE` | `PAUSE_ROLE` plus `UNPAUSE_ROLE` | +| Compliance admin | TIP-403 simple-policy admin | +| Bridge operator | Bridge adapter with scoped `ISSUER_ROLE` | +| Lifecycle manager | Manager contract with scoped `ISSUER_ROLE` | + +Important: `Pausable` does not define who can pause. That authority usually comes from `AccessControl`, `Ownable`, or a custom manager. On TIP-20, pause and unpause are separate permissions. + +## 7. Handle burn semantics + +OpenZeppelin-style ERC-20 tokens may expose: + +```solidity +burn(amount) +burnFrom(account, amount) +``` + +An `ISSUER_ROLE` holder can burn only its own TIP-20 balance. TIP-20 does not give the issuer a general `burnFrom(user, amount)` path. If your current flow uses `burnFrom`, replace it with a manager-mediated pull-then-burn flow. + +```text +transferFrom(user, managerOrBridgeAdapter, amount) +managerOrBridgeAdapter burns its own TIP-20 balance +``` + +In practice, `managerOrBridgeAdapter` means: + +| Flow | Contract that pulls and burns | +|---|---| +| Native redemption | Lifecycle manager | +| Bridge out of Tempo | Bridge adapter | +| Migration burn | Migration adapter or issuer-controlled burn contract | +| Simple issuer operation | Issuer wallet for admin-only mint/burn operations, not user-facing redemption | + +For bridge or redemption flows, the lifecycle manager or bridge adapter should hold `ISSUER_ROLE`. + +Do not treat `BURN_BLOCKED_ROLE` as a general redemption role. Use it only for blocked-account or compliance recovery flows. + +## 8. Handle decimals + +OpenZeppelin ERC-20 tokens commonly use 18 decimals unless overridden. TIP-20 uses 6 decimals. + +Before migration or integration: + +- Define the conversion rule. +- Decide how to handle dust. +- Update bridge and migration scripts. +- Update reporting and accounting. +- Confirm wallet and explorer display. +- Test mint, burn, transfer, and reconciliation in smallest units. + +Example: + +```text +1.000000 TIP-20 unit = 1 token +1 TIP-20 token = 1,000,000 base units +``` + +## 9. Move custom logic outside the token + +TIP-20 is native. Do not port custom ERC-20 methods into the token. + +| Current custom logic | Tempo location | +|---|---| +| Transfer hooks | TIP-403 policy, maintained by an issuer compliance sync service | +| Allowlist or blocklist | TIP-403 simple or compound policy | +| Sanctions checks | TIP-403 policy, maintained by an issuer compliance sync service | +| Subscription flow | Lifecycle manager | +| Redemption flow | Lifecycle manager | +| Fee logic | Lifecycle manager | +| Rate limits | Lifecycle manager | +| Oracle pricing | External oracle or manager-facing oracle | +| Bridge accounting | Bridge adapter | + +## 10. Map compliance hooks to TIP-403 + +Many issuer ERC-20s enforce compliance inside token transfer hooks such as `_beforeTokenTransfer` or `_update`. + +Common checks include: + +- allowlisted sender +- allowlisted recipient +- blocked sender or recipient +- sanctions screening +- KYC or identity registry membership +- transfer-agent or operator eligibility + +On Tempo, keep the token native and move transfer eligibility into TIP-403 policy. + +A TIP-20 token points to one `transferPolicyId`. Do not model compliance as attaching both an allowlist policy and a blocklist policy directly to the token. Use a simple whitelist or blacklist when one rule applies across token movement. Use a compound policy when sender, recipient, and mint-recipient rules need to differ. + +### Simple vs compound policies + +| Policy type | Use when | +|---|---| +| Simple whitelist | The same allowlist applies to all token movement checks | +| Simple blacklist | The same blocklist applies to all token movement checks | +| Compound policy | Sender, recipient, and mint-recipient rules need to differ | + +For simple whitelist or blacklist policies, the flow is shorter: create the simple policy, update its wallet entries, and set the token's `transferPolicyId` to that simple policy ID. Use a compound policy only when sender, recipient, and mint-recipient rules need to differ. + +A compound policy references three simple policies: + +| Compound policy component | Used for | +|---|---| +| `senderPolicyId` | Transfer senders and blocked-account burn eligibility | +| `recipientPolicyId` | Transfer recipients | +| `mintRecipientPolicyId` | Mint recipients | + +For compound policies, the TIP-20 admin attaches the compound policy initially: + +```text +TIP20.changeTransferPolicyId(compoundPolicyId) +``` + +The TIP-20 admin can later change `transferPolicyId` to another policy. Day to day, the issuer updates the underlying simple policies as wallets become allowed, blocked, or ineligible. + +### Separate policy and token responsibilities + +Each TIP-403 simple policy has one admin address. A compliance sync service must operate through that address; the service is not a parallel native authority. + +| Responsibility | Owner | +|---|---| +| Select which policy the token uses | TIP-20 admin | +| Maintain allowlist, blocklist, KYC, or sanctions entries | Simple-policy admin address, which may be operated by a compliance sync service | +| Mint and burn normal supply | Issuer wallet, lifecycle manager, or bridge adapter with `ISSUER_ROLE` | +| Pause or unpause token operations | `PAUSE_ROLE` / `UNPAUSE_ROLE` holders | + +### Follow the compliance flow + +>Registry: Create sender policy + Updater->>Registry: Create recipient policy + Updater->>Registry: Create mint recipient policy + Updater->>Registry: Create compound policy(senderPolicyId, recipientPolicyId, mintRecipientPolicyId) + + Note over Admin,Token: One-time policy attachment + Admin->>Token: changeTransferPolicyId(compoundPolicyId) + + Note over Compliance,Registry: Ongoing compliance maintenance + Compliance->>Updater: Wallet eligibility changes + Updater->>Registry: Update simple policy entries + + Note over Sender,Recipient: Ongoing transfer checks + Sender->>Token: transfer(Recipient, amount) + Token->>Registry: Check senderPolicyId for Sender + Token->>Registry: Check recipientPolicyId for Recipient + Registry-->>Token: allow or reject + Token-->>Recipient: Balance increases if allowed + + Note over Issuer,Recipient: Ongoing mint checks + Issuer->>Token: mint(Recipient, amount) + Token->>Token: Check ISSUER_ROLE, not paused, and totalSupply + amount <= supplyCap + Token->>Registry: Check mintRecipientPolicyId for Recipient + Registry-->>Token: allow or reject`} /> + +Minting succeeds only when the caller holds `ISSUER_ROLE`, the token is not paused, the resulting supply does not exceed the supply cap, and TIP-403 authorizes the mint recipient. + +### Handle burn and redemption + +Normal burn and redemption are not a separate TIP-403 policy attachment. + +For normal redemption: + +```text +user approves lifecycle manager or bridge adapter +manager pulls tokens with transferFrom +manager burns its own TIP-20 balance using ISSUER_ROLE +``` + +For blocked-account recovery: + +```text +BURN_BLOCKED_ROLE can use burnBlocked(...) +``` + +`burnBlocked` is a special compliance path. It should not be treated as the normal redemption or bridge burn path. + +### Map ERC-20 compliance patterns + +| Current ERC-20 compliance pattern | Tempo mapping | +|---|---| +| `_beforeTokenTransfer` allowlist check | Simple whitelist or compound policy component | +| `_update` allowlist check | Simple whitelist or compound policy component | +| Blocklist check | Simple blacklist or compound policy component | +| Sanctions-list contract | Simple blacklist maintained by issuer compliance sync service | +| Identity or KYC registry | Simple whitelist maintained by issuer compliance sync service | +| Different sender and recipient rules | Compound policy | +| Mint recipient eligibility | Compound policy `mintRecipientPolicyId` | +| Operator or spender compliance | Lifecycle manager or bridge adapter check | +| Compliance admin | TIP-403 simple-policy admin | + +## 11. Avoid incompatible ERC-20 assumptions + +- Do not deploy a custom ERC-20 when the target is native TIP-20. +- Do not assume proxy storage or upgrade logic carries over. +- Do not grant issuer permissions to broad operational wallets without a reason. +- Use TIP-20's native role-query methods; do not assume OpenZeppelin argument ordering. +- Do not map `burnFrom` directly. Use a lifecycle manager or bridge adapter that pulls tokens with `transferFrom`, then burns its own TIP-20 balance. +- Do not ignore 18-to-6 decimal conversion. +- Do not put compliance or subscription logic inside the token. + +## 12. Consult the operational reference + +### Query and administer native roles + +TIP-20 exposes native methods for querying and administering roles: + +```solidity +hasRole(address account, bytes32 role) +getRoleAdmin(bytes32 role) +setRoleAdmin(bytes32 role, bytes32 adminRole) +``` + +Unlike OpenZeppelin's `hasRole(role, account)`, TIP-20's `hasRole` takes the account first: `hasRole(account, role)`. + +`DEFAULT_ADMIN_ROLE` is `bytes32(0)`. Multiple accounts may hold it. It governs role and token administration, but it does not itself permit minting; an account or contract must hold `ISSUER_ROLE` to mint. + +Initially, every role is governed by `DEFAULT_ADMIN_ROLE`. `setRoleAdmin(role, adminRole)` can later change which role is authorized to grant and revoke that role. Use `getRoleAdmin(role)` to inspect the current relationship before changing role assignments. + +### Apply pause semantics + +Since the T3 upgrade, a paused TIP-20 blocks: + +- transfers +- minting +- normal burns +- `burnBlocked` + +Administration remains available while paused. + +`transferFeePreTx` and `transferFeePostTx` are system-only entrypoints used by Tempo protocol precompiles, not issuer-callable operations. As a protocol invariant, `transferFeePreTx` respects pause while `transferFeePostTx` remains available so fee settlement can complete. + +## Related references + +- [OpenZeppelin ERC-20 contracts](https://docs.openzeppelin.com/contracts/5.x/api/token/erc20) +- [OpenZeppelin access control](https://docs.openzeppelin.com/contracts/5.x/access-control) +- [TIP-20 tokens specification](/docs/protocol/tip20/spec) diff --git a/src/pages/docs/quickstart/integrate-tempo.mdx b/src/pages/docs/quickstart/integrate-tempo.mdx index 4a1f6c2a..c387d6e9 100644 --- a/src/pages/docs/quickstart/integrate-tempo.mdx +++ b/src/pages/docs/quickstart/integrate-tempo.mdx @@ -67,6 +67,12 @@ Tempo is EVM-compatible and targets the **Osaka** EVM hard fork. Most Ethereum t ## Bridges and Ecosystem + Date: Tue, 14 Jul 2026 19:23:58 +0000 Subject: [PATCH 2/9] docs: correct TIP-20 admin grant diagram --- src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx b/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx index b82f28aa..ceb5d593 100644 --- a/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx +++ b/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx @@ -50,7 +50,7 @@ Use this guide if your current token uses: Note over Issuer,Token: One-time token deployment Issuer->>Factory: createToken(name, symbol, currency, quoteToken, admin, salt, logoURI) Factory-->>Token: Create native TIP-20 token - Factory-->>Admin: Grant token admin role + Factory->>Token: Grant DEFAULT_ADMIN_ROLE to Admin Note over Admin,Operator: One-time or occasional role setup Admin->>Token: grantRole(ISSUER_ROLE, Operator) @@ -379,4 +379,4 @@ Administration remains available while paused. - [OpenZeppelin ERC-20 contracts](https://docs.openzeppelin.com/contracts/5.x/api/token/erc20) - [OpenZeppelin access control](https://docs.openzeppelin.com/contracts/5.x/access-control) -- [TIP-20 tokens specification](/docs/protocol/tip20/spec) +- [TIP-20 tokens specification](/docs/protocol/tip20/spec) \ No newline at end of file From c4e40d6913c2802742caa7f645c208c8a3f2ca70 Mon Sep 17 00:00:00 2001 From: Derek Cofausper <256792747+decofe@users.noreply.github.com> Date: Wed, 15 Jul 2026 06:13:58 +0000 Subject: [PATCH 3/9] docs: address TIP-20 issuer review feedback --- .../guide/issuance/migrate-erc20-to-tip20.mdx | 51 +++++++++++++++---- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx b/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx index ceb5d593..a630f28c 100644 --- a/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx +++ b/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx @@ -33,7 +33,7 @@ Use this guide if your current token uses: | Native TIP-20 launch | New issuance on Tempo | Deploy TIP-20 through `TIP20Factory` | | ERC-20 migration | Existing supply should move to Tempo | Burn or retire ERC-20 supply, then mint TIP-20 | | Bridge adapter | ERC-20 remains canonical on another chain | Lock or burn ERC-20, then mint TIP-20 on Tempo through a bridge adapter | -| Lifecycle manager | Issuance has subscriptions, redemptions, caps, or oracle pricing | Keep business logic outside TIP-20, grant the manager scoped `ISSUER_ROLE` | +| Lifecycle manager | Issuance has subscriptions, redemptions, caps, or oracle pricing | Keep business logic outside TIP-20 and grant the manager `ISSUER_ROLE`; enforce limits in the manager | ## 2. Review the recommended deployment flow @@ -47,21 +47,27 @@ Use this guide if your current token uses: participant Registry as TIP-403 registry participant Vault as Treasury or customer vault + opt Compliance required + Note over PolicyAdmin,Registry: Prepare policy before token deployment + PolicyAdmin->>Registry: Create or select policy + end + Note over Issuer,Token: One-time token deployment Issuer->>Factory: createToken(name, symbol, currency, quoteToken, admin, salt, logoURI) Factory-->>Token: Create native TIP-20 token Factory->>Token: Grant DEFAULT_ADMIN_ROLE to Admin + opt Compliance required + Note over Admin,Token: Attach the prepared policy before token operations + Admin->>Token: changeTransferPolicyId(policyId) + end + Note over Admin,Operator: One-time or occasional role setup Admin->>Token: grantRole(ISSUER_ROLE, Operator) Admin->>Token: grantRole(PAUSE_ROLE, pause wallet) Admin->>Token: grantRole(UNPAUSE_ROLE, unpause wallet) opt Compliance required - Note over PolicyAdmin,Token: One-time policy setup and attachment - PolicyAdmin->>Registry: Create or select policy - Admin->>Token: changeTransferPolicyId(policyId) - Note over PolicyAdmin,Registry: Ongoing compliance maintenance PolicyAdmin->>Registry: Add, remove, allow, or block wallets end @@ -72,6 +78,8 @@ Use this guide if your current token uses: Token->>Registry: Authorize Vault as mint recipient Token-->>Vault: TIP-20 balance increases`} /> +`TIP20Factory.createToken` does not accept a policy ID. If compliance is required, prepare the policy first, deploy the token, and attach the policy before minting or transferring. A new TIP-20 otherwise starts on the always-allow policy. + ## 3. Complete the implementation checklist Before production deployment, confirm: @@ -82,7 +90,7 @@ Before production deployment, confirm: - `ISSUER_ROLE` is granted only to the intended issuer wallet, manager, or bridge adapter. - Native role queries use `hasRole(account, role)`, and each role's administrator is confirmed with `getRoleAdmin(role)`. - `PAUSE_ROLE` and `UNPAUSE_ROLE` are assigned. -- Supply cap is set if required. +- Supply cap is set deliberately. Every TIP-20 starts at `type(uint128).max`; prefer the lowest practical cap and increase it as issuance grows. - Transfer policy is configured if compliance is required. - If using a compound policy, sender, recipient, and mint-recipient checks are tested independently. - Existing ERC-20 supply is burned, locked, or reconciled if this is a migration or bridge. @@ -95,6 +103,21 @@ Before production deployment, confirm: - Tokenlist and explorer metadata are ready. - The issuer has an operational runbook for role changes, pausing, minting, burning, and reconciliation. +### Review mutable and immutable settings + +| Setting | Initial value | Can it change? | +|---|---|---| +| Token address | Deterministically derived from factory caller and salt | No | +| Name and symbol | Set at creation | No | +| Decimals | `6` | No | +| Currency | Set at creation | No | +| Supply cap | `type(uint128).max` | Yes, but not below current total supply | +| Quote token | Set at creation | Yes, through the staged quote-token update flow | +| Transfer policy | Always-allow policy (`1`) | Yes, by the TIP-20 admin | +| Logo URI | Set or left empty at creation | Yes, by the TIP-20 admin | +| Roles and role administrators | Admin receives `DEFAULT_ADMIN_ROLE` | Yes | +| Pause state | Unpaused | Yes, through `PAUSE_ROLE` and `UNPAUSE_ROLE` | + ## 4. Inspect the current ERC-20 Before deploying on Tempo, identify the current token architecture. @@ -136,11 +159,13 @@ Map by capability, not by contract name. | `BURNER_ROLE` | `ISSUER_ROLE` for normal burns | | `PAUSER_ROLE` | `PAUSE_ROLE` plus `UNPAUSE_ROLE` | | Compliance admin | TIP-403 simple-policy admin | -| Bridge operator | Bridge adapter with scoped `ISSUER_ROLE` | -| Lifecycle manager | Manager contract with scoped `ISSUER_ROLE` | +| Bridge operator | Bridge adapter with `ISSUER_ROLE`; enforce limits in the adapter | +| Lifecycle manager | Manager contract with `ISSUER_ROLE`; enforce limits in the manager | Important: `Pausable` does not define who can pause. That authority usually comes from `AccessControl`, `Ownable`, or a custom manager. On TIP-20, pause and unpause are separate permissions. +`ISSUER_ROLE` is not natively scoped by amount, destination, or operation. If an issuer needs those limits, its wallet, manager, or adapter must enforce them before calling TIP-20. + ## 7. Handle burn semantics OpenZeppelin-style ERC-20 tokens may expose: @@ -157,6 +182,8 @@ transferFrom(user, managerOrBridgeAdapter, amount) managerOrBridgeAdapter burns its own TIP-20 balance ``` +The pull and burn can be atomic in one adapter transaction, but the adapter temporarily takes custody of the tokens between the two calls. Confirm that this custody model is acceptable for the issuer's legal and regulatory requirements. + In practice, `managerOrBridgeAdapter` means: | Flow | Contract that pulls and burns | @@ -227,8 +254,8 @@ A TIP-20 token points to one `transferPolicyId`. Do not model compliance as atta | Policy type | Use when | |---|---| -| Simple whitelist | The same allowlist applies to all token movement checks | -| Simple blacklist | The same blocklist applies to all token movement checks | +| Simple whitelist | The same allowlist applies to transfer-sender, transfer-recipient, and mint-recipient checks | +| Simple blacklist | The same blocklist applies to transfer-sender, transfer-recipient, and mint-recipient checks | | Compound policy | Sender, recipient, and mint-recipient rules need to differ | For simple whitelist or blacklist policies, the flow is shorter: create the simple policy, update its wallet entries, and set the token's `transferPolicyId` to that simple policy ID. Use a compound policy only when sender, recipient, and mint-recipient rules need to differ. @@ -300,6 +327,8 @@ Each TIP-403 simple policy has one admin address. A compliance sync service must Minting succeeds only when the caller holds `ISSUER_ROLE`, the token is not paused, the resulting supply does not exceed the supply cap, and TIP-403 authorizes the mint recipient. +The recipient can also configure an account-level receive policy, which the issuer does not control. If that policy blocks a transfer or mint, the call succeeds but delivery is redirected to `ReceivePolicyGuard` as a claimable receipt. Issuers should monitor this outcome when reconciling mints and transfers. See [Receive policies](/docs/protocol/tip403/receive-policies). + ### Handle burn and redemption Normal burn and redemption are not a separate TIP-403 policy attachment. @@ -379,4 +408,4 @@ Administration remains available while paused. - [OpenZeppelin ERC-20 contracts](https://docs.openzeppelin.com/contracts/5.x/api/token/erc20) - [OpenZeppelin access control](https://docs.openzeppelin.com/contracts/5.x/access-control) -- [TIP-20 tokens specification](/docs/protocol/tip20/spec) \ No newline at end of file +- [TIP-20 tokens specification](/docs/protocol/tip20/spec) From cc8de21646f9411102f2fec9621eadced40784ca Mon Sep 17 00:00:00 2001 From: Vijith Bheemireddi Date: Tue, 14 Jul 2026 23:36:13 -0700 Subject: [PATCH 4/9] docs: remove prescriptive burnFrom migration flow --- .../guide/issuance/migrate-erc20-to-tip20.mdx | 64 ++----------------- 1 file changed, 6 insertions(+), 58 deletions(-) diff --git a/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx b/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx index a630f28c..14cf2703 100644 --- a/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx +++ b/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx @@ -146,7 +146,7 @@ Map by capability, not by contract name. | `IERC20Metadata` | `name`, `symbol`, `decimals` | Set name, symbol, and currency at TIP-20 creation | | `AccessControl` | Role-based permissions | TIP-20 native roles | | `Pausable` | Pause state and pause checks | `PAUSE_ROLE` and `UNPAUSE_ROLE` | -| `ERC20Burnable` | `burn` and often `burnFrom` | An `ISSUER_ROLE` holder can burn only its own TIP-20 balance; use pull-then-burn for user funds | +| `ERC20Burnable` | `burn` and often `burnFrom` | An `ISSUER_ROLE` holder can burn only its own TIP-20 balance; TIP-20 does not expose `burnFrom` | | `ERC20Wrapper` | Deposit underlying ERC-20, mint wrapped ERC-20 | Use an external adapter that mints TIP-20 | | Proxy upgrade pattern | Upgradeable implementation and storage | Not portable to TIP-20 | @@ -166,38 +166,7 @@ Important: `Pausable` does not define who can pause. That authority usually come `ISSUER_ROLE` is not natively scoped by amount, destination, or operation. If an issuer needs those limits, its wallet, manager, or adapter must enforce them before calling TIP-20. -## 7. Handle burn semantics - -OpenZeppelin-style ERC-20 tokens may expose: - -```solidity -burn(amount) -burnFrom(account, amount) -``` - -An `ISSUER_ROLE` holder can burn only its own TIP-20 balance. TIP-20 does not give the issuer a general `burnFrom(user, amount)` path. If your current flow uses `burnFrom`, replace it with a manager-mediated pull-then-burn flow. - -```text -transferFrom(user, managerOrBridgeAdapter, amount) -managerOrBridgeAdapter burns its own TIP-20 balance -``` - -The pull and burn can be atomic in one adapter transaction, but the adapter temporarily takes custody of the tokens between the two calls. Confirm that this custody model is acceptable for the issuer's legal and regulatory requirements. - -In practice, `managerOrBridgeAdapter` means: - -| Flow | Contract that pulls and burns | -|---|---| -| Native redemption | Lifecycle manager | -| Bridge out of Tempo | Bridge adapter | -| Migration burn | Migration adapter or issuer-controlled burn contract | -| Simple issuer operation | Issuer wallet for admin-only mint/burn operations, not user-facing redemption | - -For bridge or redemption flows, the lifecycle manager or bridge adapter should hold `ISSUER_ROLE`. - -Do not treat `BURN_BLOCKED_ROLE` as a general redemption role. Use it only for blocked-account or compliance recovery flows. - -## 8. Handle decimals +## 7. Handle decimals OpenZeppelin ERC-20 tokens commonly use 18 decimals unless overridden. TIP-20 uses 6 decimals. @@ -217,7 +186,7 @@ Example: 1 TIP-20 token = 1,000,000 base units ``` -## 9. Move custom logic outside the token +## 8. Move custom logic outside the token TIP-20 is native. Do not port custom ERC-20 methods into the token. @@ -233,7 +202,7 @@ TIP-20 is native. Do not port custom ERC-20 methods into the token. | Oracle pricing | External oracle or manager-facing oracle | | Bridge accounting | Bridge adapter | -## 10. Map compliance hooks to TIP-403 +## 9. Map compliance hooks to TIP-403 Many issuer ERC-20s enforce compliance inside token transfer hooks such as `_beforeTokenTransfer` or `_update`. @@ -329,26 +298,6 @@ Minting succeeds only when the caller holds `ISSUER_ROLE`, the token is not paus The recipient can also configure an account-level receive policy, which the issuer does not control. If that policy blocks a transfer or mint, the call succeeds but delivery is redirected to `ReceivePolicyGuard` as a claimable receipt. Issuers should monitor this outcome when reconciling mints and transfers. See [Receive policies](/docs/protocol/tip403/receive-policies). -### Handle burn and redemption - -Normal burn and redemption are not a separate TIP-403 policy attachment. - -For normal redemption: - -```text -user approves lifecycle manager or bridge adapter -manager pulls tokens with transferFrom -manager burns its own TIP-20 balance using ISSUER_ROLE -``` - -For blocked-account recovery: - -```text -BURN_BLOCKED_ROLE can use burnBlocked(...) -``` - -`burnBlocked` is a special compliance path. It should not be treated as the normal redemption or bridge burn path. - ### Map ERC-20 compliance patterns | Current ERC-20 compliance pattern | Tempo mapping | @@ -363,17 +312,16 @@ BURN_BLOCKED_ROLE can use burnBlocked(...) | Operator or spender compliance | Lifecycle manager or bridge adapter check | | Compliance admin | TIP-403 simple-policy admin | -## 11. Avoid incompatible ERC-20 assumptions +## 10. Avoid incompatible ERC-20 assumptions - Do not deploy a custom ERC-20 when the target is native TIP-20. - Do not assume proxy storage or upgrade logic carries over. - Do not grant issuer permissions to broad operational wallets without a reason. - Use TIP-20's native role-query methods; do not assume OpenZeppelin argument ordering. -- Do not map `burnFrom` directly. Use a lifecycle manager or bridge adapter that pulls tokens with `transferFrom`, then burns its own TIP-20 balance. - Do not ignore 18-to-6 decimal conversion. - Do not put compliance or subscription logic inside the token. -## 12. Consult the operational reference +## 11. Consult the operational reference ### Query and administer native roles From ea7bcaec92f6de042310b59ae85b41a5bd6be87c Mon Sep 17 00:00:00 2001 From: Vijith Bheemireddi Date: Tue, 14 Jul 2026 23:39:07 -0700 Subject: [PATCH 5/9] docs: restore blocked-account burn guidance --- .../docs/guide/issuance/migrate-erc20-to-tip20.mdx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx b/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx index 14cf2703..74cd14ff 100644 --- a/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx +++ b/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx @@ -298,6 +298,18 @@ Minting succeeds only when the caller holds `ISSUER_ROLE`, the token is not paus The recipient can also configure an account-level receive policy, which the issuer does not control. If that policy blocks a transfer or mint, the call succeeds but delivery is redirected to `ReceivePolicyGuard` as a claimable receipt. Issuers should monitor this outcome when reconciling mints and transfers. See [Receive policies](/docs/protocol/tip403/receive-policies). +### Handle burn and redemption + +Normal burn and redemption are not a separate TIP-403 policy attachment. + +For blocked-account recovery: + +```text +BURN_BLOCKED_ROLE can use burnBlocked(...) +``` + +`burnBlocked` is a special compliance path. It should not be treated as the normal redemption or bridge burn path. + ### Map ERC-20 compliance patterns | Current ERC-20 compliance pattern | Tempo mapping | From f45b24ecef02090f5ea02e1be8404667aaa8a116 Mon Sep 17 00:00:00 2001 From: Derek Cofausper <256792747+decofe@users.noreply.github.com> Date: Wed, 15 Jul 2026 06:44:42 +0000 Subject: [PATCH 6/9] docs: remove burnBlocked migration guidance --- .../docs/guide/issuance/migrate-erc20-to-tip20.mdx | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx b/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx index 74cd14ff..14cf2703 100644 --- a/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx +++ b/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx @@ -298,18 +298,6 @@ Minting succeeds only when the caller holds `ISSUER_ROLE`, the token is not paus The recipient can also configure an account-level receive policy, which the issuer does not control. If that policy blocks a transfer or mint, the call succeeds but delivery is redirected to `ReceivePolicyGuard` as a claimable receipt. Issuers should monitor this outcome when reconciling mints and transfers. See [Receive policies](/docs/protocol/tip403/receive-policies). -### Handle burn and redemption - -Normal burn and redemption are not a separate TIP-403 policy attachment. - -For blocked-account recovery: - -```text -BURN_BLOCKED_ROLE can use burnBlocked(...) -``` - -`burnBlocked` is a special compliance path. It should not be treated as the normal redemption or bridge burn path. - ### Map ERC-20 compliance patterns | Current ERC-20 compliance pattern | Tempo mapping | From 6004206ee658527c3dec163cb0cc03714741b9e5 Mon Sep 17 00:00:00 2001 From: Vijith Reddy <297285227+vijith-reddy@users.noreply.github.com> Date: Fri, 17 Jul 2026 11:56:13 -0700 Subject: [PATCH 7/9] docs: clarify TIP-20 currency selection --- .../guide/issuance/migrate-erc20-to-tip20.mdx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx b/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx index 14cf2703..fdb26704 100644 --- a/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx +++ b/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx @@ -118,6 +118,25 @@ Before production deployment, confirm: | Roles and role administrators | Admin receives `DEFAULT_ADMIN_ROLE` | Yes | | Pause state | Unpaused | Yes, through `PAUSE_ROLE` and `UNPAUSE_ROLE` | +### Choose the currency by unit-price behavior + +Set `currency` to the asset or unit of account that **one token unit is designed to remain approximately 1:1 with**. The deciding question is how the price of one token unit behaves, not whether the token generates yield. + +| Token behavior | Example | `currency` | +|---|---|---| +| USD stablecoin that remains near $1 | USDC | `USD` | +| Rebasing USD yield token whose balance grows while each unit remains near $1 | Rebasing USD yield token | `USD` | +| Wrapped token that remains 1:1 with BTC | WBTC or cbBTC | `BTC` | +| Accumulating token whose balance stays fixed while its redemption value rises | PRIME or cbETH | Its own asset identifier, such as `PRIME` or `cbETH` | +| Non-rebasing wrapper around a rebasing asset | Wrapped yield token | Its own asset identifier, unless one wrapper unit remains near 1:1 with the underlying denomination | + +Do not set `currency = USD` merely because an accumulating token is USD-backed or reports its value in USD. Classifying a TIP-20 as USD makes it eligible for infrastructure designed for near-par USD assets: + +- The [Fee AMM](/docs/protocol/fees/spec-fee-amm) converts between a user's fee token and a validator's preferred fee token at fixed near-par rates. If one token unit is worth more than $1, that conversion would misprice it. +- The [Stablecoin DEX](/docs/protocol/exchange) is market-priced, but it is designed for USD-classified pairs and currently limits orders to a ±2% range around parity. An accumulating token can move outside that range as its redemption value grows. + +Because `currency` is immutable, confirm the token's unit-price behavior before deployment. See [Currency Declaration](/docs/protocol/tip20/overview#currency-declaration) for the full selection rules. + ## 4. Inspect the current ERC-20 Before deploying on Tempo, identify the current token architecture. From 7a55fc19e4b273f65e73a698c296154a0d83b8f9 Mon Sep 17 00:00:00 2001 From: Vijith Reddy <297285227+vijith-reddy@users.noreply.github.com> Date: Fri, 17 Jul 2026 12:31:15 -0700 Subject: [PATCH 8/9] docs: clarify TIP-403 policy scope --- src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx b/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx index fdb26704..4fd73a2c 100644 --- a/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx +++ b/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx @@ -236,7 +236,7 @@ Common checks include: On Tempo, keep the token native and move transfer eligibility into TIP-403 policy. -A TIP-20 token points to one `transferPolicyId`. Do not model compliance as attaching both an allowlist policy and a blocklist policy directly to the token. Use a simple whitelist or blacklist when one rule applies across token movement. Use a compound policy when sender, recipient, and mint-recipient rules need to differ. +A TIP-20 token points to one `transferPolicyId`. Do not model compliance as attaching both an allowlist policy and a blocklist policy directly to the token. Use a simple whitelist or blacklist when the same rule applies to transfer senders, transfer recipients, and mint recipients. Use a compound policy when those three rules need to differ. ### Simple vs compound policies From 44633876711399e25ac8fa04300ad905269db20d Mon Sep 17 00:00:00 2001 From: Derek Cofausper <256792747+decofe@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:15:41 +0000 Subject: [PATCH 9/9] docs: render migration sequence diagrams correctly --- src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx b/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx index 4fd73a2c..4c55f613 100644 --- a/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx +++ b/src/pages/docs/guide/issuance/migrate-erc20-to-tip20.mdx @@ -3,7 +3,7 @@ title: Migrate an ERC-20 to TIP-20 description: Map an existing OpenZeppelin ERC-20 architecture to native TIP-20 issuance, bridging, lifecycle management, roles, burns, decimals, and TIP-403 policies. --- -import { MermaidDiagram } from '../../../../components/MermaidDiagram' +import { StaticMermaidDiagram } from '../../../../components/StaticMermaidDiagram' # Migrate an ERC-20 to TIP-20 @@ -37,7 +37,7 @@ Use this guide if your current token uses: ## 2. Review the recommended deployment flow -