feat(interfaces): first-pass design for the three B-20 token variants#1
Merged
Conversation
IDefaultToken is the base interface every B-20 token implements (Default, Stablecoin, Security all inherit). ERC-20 selector compat, plus memo siblings, mint/burn, role-based access control, pause, EIP-2612 + ERC-1271 permit, transfer-policy hookup, supply cap, and ERC-7572 contractURI. Capabilities is a small library of immutable feature bits that gate the optional surface of any B-20 token. Functions whose capability bit is unset revert with FeatureDisabled, regardless of role state. This is the honest-signaling mechanism: integrators read capabilities() once and know exactly what is permanently possible on the token. Includes named presets ALL, IMMUTABLE_MEMECOIN, and FIXED_SUPPLY.
IStablecoin extends IDefaultToken with a single addition: an immutable currency() identifier (USD, EUR, BTC, etc.) for DEX/routing/wallet categorization. Other stablecoin-specific extensions (reserve attestation, master-minter, freeze, yield distribution) deferred pending team alignment and CDP Custom Stablecoin reference access. ISecurityToken extends IDefaultToken with the Tangor-derived security surface: announcement coupling for metadata changes, share ratio for split-safe accounting, compliant create() with per-caller rate limit, user-side redeem() gated on a separate redeemPolicyId allowlist, batch adminMint/adminBurn cold paths, multi-key security identifiers, name and symbol updates with announcement coupling. Capabilities extended with security-specific bits in the 16+ range (SECURITY_CREATABLE, SECURITY_REDEEMABLE, SHARE_RATIO_MUTABLE, SECURITY_METADATA_MUTABLE, SECURITY_ADMIN_BATCH) and a STANDARD_EQUITY preset that captures the typical security-token configuration (inherited mint/burn off, security paths on, BURN_BLOCKED on for sanctions enforcement). Open questions and unilateral assumptions documented in DESIGN_NOTES.md (separate commit).
Catalogs every decision I made unilaterally vs. every question that needs your input, organized by file and topic. Categories: - ASSUMED: decisions made without explicit sign-off, flag any to revisit - OPEN: questions awaiting your input - VERIFY: ambiguities in source docs (user stories vs. wiki vs. Tangor) Includes a top-9 list of bits to explicitly confirm before iterating further.
…ablecoin After reading coinbase/custom-stablecoin (CCS) end-to-end, the following changes: IDefaultToken: - Split ISSUER_ROLE into MINT_ROLE and BURN_ROLE so issuance and destruction authority can be held separately. Same pattern as CCS; enables operational separation of concerns (treasury team mints, redemption team burns). - Add two-step admin transfer with configurable delay (the OZ AccessControlDefaultAdminRules pattern, also used by CCS): adds defaultAdmin, pendingDefaultAdmin, defaultAdminDelay, plus the begin/cancel/accept/changeDelay/rollbackDelay flow. grantRole and revokeRole now revert when called for DEFAULT_ADMIN_ROLE; the only valid transfer path is the two-step flow with delay. Protects against typo-bricking the admin role and gives detection time on key compromise. IStablecoin: - Add per-minter rate limiting: configureMinter, removeMinterRateLimit, grantMinterRoleWithLimit (atomic combo to avoid first-mint race), currentMintLimit, mintRateLimitConfig. Held under MINT_RATE_LIMIT_ROLE separately from DEFAULT_ADMIN_ROLE. - Add ERC-3009 transfer-with-authorization: transferWithAuthorization, receiveWithAuthorization (front-run-resistant; only payee submits), cancelAuthorization. Both ECDSA (canonical) and bytes (EOA + ERC-1271) variants. Distinct from EIP-2612 permit (random nonces, time windows, direct transfers vs. allowances). - Currency identifier kept from prior draft. Capabilities: - New stablecoin bits in 24..31 range: STABLECOIN_MINT_RATE_LIMITED and STABLECOIN_AUTHORIZATIONS. - New STANDARD_STABLECOIN preset that includes rate limiting + authorizations, OMITS BURN_BLOCKED to default to the CCS 'freeze, never seize' philosophy. Issuers wanting force-burn for sanctions enforcement OR in BURN_BLOCKED at creation.
…decisions Restructure DESIGN_NOTES.md to lead with a Design Rationale section that captures the reasoning behind the major settled decisions, so future contributors do not have to re-derive them: Cross-cutting: - Capabilities bitfield as honest-signaling mechanism - Default IS Core (variant inheritance, no separate ICoreToken) - Single source of truth for compliance (external Policy Registry vs internal blocklist), with note on Tangor sanctions vs CCS freeze - Memos as ERC-20-compatible sibling functions (vs optional parameter) - No third-party deps constraint and its implications Roles & Admin: - Why MINT_ROLE and BURN_ROLE are separate - Why PAUSE_ROLE and UNPAUSE_ROLE are separate - Why two-step admin transfer with delay (typo-bricking protection, key-compromise detection window) - User-defined role support Stablecoin-specific: - Why per-minter rate limiting (risk management, multi-party governance) - Why ERC-3009 (USDC parity for payment apps; complementary to permit) - Currency identifier convention - Freeze vs seize philosophy (CCS vs Tangor) expressed via BURN_BLOCKED Security-specific: - Three issuance paths (create / adminMint / inherited mint) - redeemPolicyId separate from transferPolicyId - Why on-chain announcement coupling (vs off-chain audit policy) - Share ratio for split-safe accounting (DeFi composability) Open questions reorganized to mark resolved items as RESOLVED with context, surface the remaining 9 items in a confirmation summary at the bottom.
Three policy types in v1: WHITELIST, BLACKLIST, COMPOUND. Built-in IDs 0 (always-reject) and 1 (always-allow) reserved as well-known protocol constants (no view-function getters; documented in the contract notice). Surface mirrors Tempo TIP-403 + TIP-1015 with three deliberate omissions: no virtual-address rejection (no TIP-1022 on Base), no receive policies (no TIP-1028 escrow), no callback / richer-guard policies (deferred; can be added in a future hardfork as a backward- compatible enum extension). Authorization queries split per role (isAuthorizedSender / isAuthorizedRecipient / isAuthorizedMintRecipient) so compound policies can carry asymmetric rules; isAuthorized retained as the sender-AND-recipient composite for callers that want a single check. Compound policies are structurally immutable (constituent IDs cannot change after creation); to rotate the configuration, create a new compound policy and re-point the consuming token's transferPolicyId. Constituents may be simple policies or built-ins (0, 1) but never other compound policies (PolicyNotSimple). DESIGN_NOTES updated with the rationale for shipping just Levels 1+2 (not Level 3 callback policies or Level 4 modular guards) in v1, and which rule classes that decision leaves unsupported chain-side (per-tx amount limits, counterparty-dependent rules).
Singleton factory at a fixed precompile address. Three permissionless create methods (createDefault, createStablecoin, createSecurity), each accepting a variant-specific params struct. Tokens are deployed at deterministic addresses derived from (variant, creator, salt). Variant is encoded in the address prefix so the variant of any token is recoverable via variantOf without an SLOAD; address prediction functions (predict*Address) let callers compute the address from (creator, salt) alone, independent of the other creation params. Default and Stablecoin variants accept an initialSupply minted atomically at creation; security tokens have no initialSupply and bootstrap via create() / adminMint() after deployment. The bootstrap mint deliberately bypasses both the policy check and the MINTABLE capability gate (one-shot creator-managed allocation; rationale captured in DESIGN_NOTES). Each token gets a per-token defaultAdminDelay configured at creation so different security postures (stablecoin vs memecoin) can have appropriately different delays from day one. Factory is permissionless and has no admin; each created token self-governs via its own roles.
Comment on lines
+30
to
+31
| NONE, | ||
| DEFAULT, |
Collaborator
There was a problem hiding this comment.
was assuming default and none are effectively the same, what's the case for separating you see?
| /// @param contractURI Initial ERC-7572 contract URI. | ||
| /// @param salt Caller-chosen salt for deterministic | ||
| /// address derivation. | ||
| struct CreateDefaultTokenParams { |
Collaborator
There was a problem hiding this comment.
this is too big, needs to be smaller
Collaborator
There was a problem hiding this comment.
maybe a pattern we can consider is some way for factory smart contracts that wrap our factory precompile can also configure some of these things through the setter directly? Would help reduce our scope of creation params
| function setRoleAdmin(bytes32 role, bytes32 newAdminRole) external; | ||
|
|
||
| /*////////////////////////////////////////////////////////////// | ||
| TWO-STEP DEFAULT ADMIN |
Collaborator
There was a problem hiding this comment.
feeling resistance on including this although whenever we do admin patterns, I always advocate for 2-step patterns...
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
First-pass interface design for the three B-20 token variants: IDefaultToken (the inheritable base), IStablecoin, ISecurityToken, plus the Capabilities bitfield library that gates each variant's optional features.
Draft. Looking for feedback on the design choices documented in DESIGN_NOTES.md before iterating further.
Design highlights
Open for input
DESIGN_NOTES.md has a Design Rationale section explaining the why of the major decisions, plus a 9-item confirmation list at the bottom for the still-open questions. Most consequential of those: should we add a MEMOS_REQUIRED capability bit, and should adminBurn be allowed to affect any account (not just policy-blocked) given announcement coupling.
Not implemented yet