The Token Program on LEE is a built-in smart contract that provides standard token functionality (creation of new tokens and transfers between accounts). It is similar to Solana's SPL Token Program, implemented as a single universal program rather than deploying a separate contract per token. All custom tokens on the network will be managed by this one token program, which owns the token accounts and enforces the token logic. This approach avoids the overhead of deploying new code for each token and ensures consistency in how tokens behave across the network. Key Features: - Token creation (Token definition account): The ability to create a new token type with an initial fixed supply distributed to specified accounts. - Token accounts (Token holding account): Accounts that represent holdings of a specific token for a given user. Each token holding account is tied to a particular token type and an owner. - Token transfers: An instruction to transfer tokens from one account to another, modifying balances accordingly. - Program ownership: All token holding accounts are standard LEE accounts owned by the token program (meaning only the token program can modify them), while each token holding account's data designates a user owner who controls the tokens. - Privacy-agnostic design: The token program's logic does not change whether transactions are public or submitted with ZKPs. Privacy-preserving transactions simply execute the same token instructions off-chain and prove correctness on-chain, so the token program itself remains unchanged. ## Design choice We implement the token program as a built-in (native) program, rather than requiring a separate deployed contract for each token. This means there is one fixed program responsible for managing all custom tokens. This design is similar to Solana's model where one SPL Token Program manages all Mint accounts. A single built-in token program offers several advantages: - Consistency and security: With one vetted program, all tokens adhere to the same code and interface, reducing the risk of bugs and simplifying audits. - Efficiency: New tokens can be created by simply initializing new accounts (data entries) rather than deploying new code. This uses less blockchain storage and avoids the overhead of multiple smart contract deployments. - Framework simplicity: `LEE v0.1` already supports the necessary features (account ownership, custom data) to implement a token program without additional frameworks. We don't need a plugin system for multiple token programs: the single program can handle arbitrary many tokens out of the box. In summary, the token program is a core, built-in program (with a well-known `program ID` on the chain) that users invoke to create and manage tokens. All token instances share this program's code. ## Token creation and supply When a user wants to create a new token, they will call the token program's `Create Token` instruction. This results in the creation of a Token definition account representing the new token. Key considerations include: - Fixed supply vs. Mint authority: For simplicity in `v0.1`, we propose that new tokens are created with a fixed total supply, determined at creation time. The token program will not assign a minting authority by default, which means no one can mint additional tokens after creation (the mint has no authority, indicating a fixed supply). This addresses the question of mutability of the minting authority: by not having one (or immediately revoking it), the supply is locked. In the future, if dynamic supply is needed, the design could allow an optional mint authority that could mint more tokens or be transferred/removed, but initial version keeps it immutable for predictability. - Disabling further minting: Because we start with no ongoing mint authority, minting is effectively disabled from the get-go. If a mint authority were provided (e.g. if a creator wanted the ability to issue more tokens later), the program could allow that authority to be renounced (set to None) via a special instruction, as Solana does. But in our current design, we default to no additional minting after the initial supply. - Initial supply distribution: The creator specifies the total supply at token creation. The token program will initialize the Token definition account's data (including the supply count) and typically also create at least one initial Token holding account to receive this supply. The simplest approach is to create a Token holding account owned by the creator (or a designated address) and credit the entire supply there. Optionally, the creation instruction could allow specifying multiple initial recipient accounts and split the supply among them. However, to keep things straightforward, we can mint the full supply to the creator's own Token holding account by default, and the creator can later transfer tokens to others as needed. - Metadata: The Token definition account will store global metadata about the token: - Total supply: The total number of token units in existence (as a fixed value if no further minting is allowed). - Name/symbol (optional): We can include a name or symbol for convenience. (This is purely informational; on Solana, the mint account itself doesn't store name/symbol, but we might allow a data field for it, so we can include it). - Decimals (precision): If the token should support fractional units, a decimals field can specify the number of decimal places (e.g. 2 for cents in a currency, or 0 if only whole units). Including a decimals field is standard for token definitions. - Authorities (optional): Fields for `mint_authority` and `freeze_authority` could be part of the Mint data structure. In our fixed-supply model, `mint_authority` would be None (no one can mint more). We might initially omit a freeze authority for simplicity, or allow it but set it to None if not used. Creating a token produces a new Token definition account that represents the token type globally, and one or more Token holding accounts holding the initial supply. The Token definition account's address effectively serves as the unique identifier for the token (similar to how an SPL token mint address identifies a token on Solana). ## Transfer logic and restrictions The token program provides a `Transfer` instruction to move tokens from one account to another. This is analogous to a standard token transfer and works as follows: - Basic transfer mechanics: A transfer will specify a source token account, a destination token account, and an amount. The program will verify that the source account has enough balance and that the transaction is authorized by the owner of that source account. Upon a valid transfer, the source account's balance is decremented and the destination account's balance is incremented by the specified amount. - Owner authorization: Only the owner of a token account (or an approved delegate, if we later add that feature) can initiate a transfer from that account. In practice, the owner's signature is required in the transaction for the instruction to succeed. This prevents unauthorized movement of tokens. (**NOTE: We might opt to have a list of signers instead, as discussed on Discord**) - No complex restrictions by default: The question of transfer restrictions (freezing accounts, global pause, etc.) is important for some use cases (e.g., regulatory compliance for stablecoins). However, in our initial design we opt for a strictly basic transfer model. That means: - There is no built-in ability to globally pause transfers of a token. - There is no default freeze functionality to lock a specific account's tokens. - The token program will not check any allowlists/blacklists or special conditions beyond ownership and balances. - Future extensions (freeze/pause): If needed, the design can be extended to support features like a freeze authority (an entity that can freeze certain token accounts, preventing transfers) or a global pause on a token. Solana's token program, for instance, includes an optional freeze authority in the Mint that can freeze/unfreeze accounts. For LEE, we would only introduce such features if there's a clear requirement, as they necessitate additional authority fields and checks in the transfer logic. For now, no transfer restrictions are implemented beyond the basic requirement that the source owner's signature is present. - Multiple token programs: Since we have a single token program, all transfers use the same instruction interface. We do not need to support multiple token program instances, simplifying the transfer logic to one canonical implementation. *Transfer example*: If Alice wants to send `50` of her custom tokens (say, USDC token instance) to Bob, the sequence would be: 1. Alice (or anyone) ensures Bob has a token account for that token. If not, a new token account can be created for Bob tied to the USDC definition account. 2. Alice signs a transaction calling the Token Program's transfer instruction, specifying her token account as source, Bob's token account as destination, and `amount = 50`. 3. The token program deducts `50` from Alice's account balance and adds `50` to Bob's account balance, provided Alice's account had at least `50` and Alice's signature is valid. This operation does not involve any native coin movement; it purely updates the data in the token accounts. ## Account model and token accounts LEE's account model (Address -> `Account {program_owner, balance, data, nonce}`) will be utilized to represent Token definition accounts and Token holding accounts. We will use dedicated token accounts (owned by the token program) to track token balances, separate from the native currency balance of a user's main account. Below are the two primary account types used by the token program: - Token definition account: An account that defines a token. Its `program_owner` is the Token Program, and it holds data describing the token's properties and total supply. - Token holding account (user token account): An account that represents a balance of a specific token held by some user (owner). Its `program_owner` is also the Token Program, and its data contains information about which token it represents and who the owning user is. Each standard user will already have a native account, but to hold a custom token, they need a separate token account for that token. This design mirrors Solana's approach where each (owner, token) pair requires a distinct token account. Structure Details: ### Program ownership Both Token definition accounts and Token holding accounts are owned by the token program (meaning only the token program can modify their data). The chain ensures that only the owning program's logic can update an account's data, which is crucial for security (no arbitrary user can alter token balances except via the token program's instructions). ### Token definition account data Contains token metadata. For example: ```json { "program_owner": "TokenProgramID", "balance": 0, "data": { "type": "definiton", "body": { "name": "USDC", "total_supply": 1000, "decimals": 2, "mint_authority": null, "freeze_authority": null } }, "nonce": 0 } ``` - `type: "definiton"` indicates this is a Token definition account. - `name` and `decimals` describe the token (name is optional metadata; decimals indicate divisibility). - `total_supply` is the fixed supply set at creation. - `mint_authority` is null (no further minting allowed; token is fixed supply). If minting were allowed, this would hold an address of the authority. - `freeze_authority` is null in this simple design (no freeze feature). If implemented, it would hold an address allowed to freeze accounts. The Token definition account itself typically has no native coin balance except what’s needed for rent (represented by balance: 0 here assuming rent not required or already paid). ### Token holding account data Represents a balance of a specific token held by a user. For example: ```json { "program_owner": "TokenProgramID", "balance": 0, "data": { "type": "account", "body": { "owner": "0xsome_user_address", "token": "USDC_DefinitionAccountAddress", "amount": 330 } }, "nonce": 0 } ``` - `type: "account"` signifies this is a Token holding account. - `owner` is the user's main address who controls this Token definition account (e.g., Alice's public key). This user must sign to authorize transfers from this account. - `token` is the reference (address) of the Token definition account that this token account is associated with. This ties the account to a specific token type. - `amount` (or balance of tokens) is the quantity of that token the account holds. - The base `program_owner` is again the token program, distinguishing it from normal user accounts (where `program_owner` might be "SystemProgram" or similar). The distinction between the token account's `data.owner` (token controller) and the base `program_owner` (account controller) is important: the former is who can spend the tokens, the latter is which program governs the account's state. ### Native balance Both Token definition account and Token holding account generally won't carry a native balance except potentially a small amount to pay for account rent or keep the account alive (depending on how we want to implement the LEE's rent model). In Solana, token accounts must maintain a minimum SOL balance to be rent-exempt, but the amount of SOL is tracked in the `is_native` field if the token is actually wrapping SOL. For our design, we assume the token accounts are separate from the native currency and only hold token data; their balance field for native coins would usually be zero. ### Multiple accounts per wallet A single user (wallet address) can have multiple token accounts, even for the same token if desired, but typically they will have one associated token account per token for simplicity (the default token account for that user and token). We may adopt a convention or utility to derive an associated token account address given a wallet and a token definition account (similar to Solana's Associated Token Account program), to simplify account management, though that can be an optional feature. ### Account lifecycle - To create a Token definition account, a new account is created and then initialized by the token program as a definition account. - To create a Token holding account, a new account is allocated and initialized by the token program with the appropriate owner and mint reference. Often, this can be done automatically (for example, an instruction could create an associated token account for a user if it doesn't exist when they receive tokens). - When transferring tokens, the destination account must exist and be initialized for the correct token; the token program will not implicitly create the destination (to avoid unauthorized account creation). If a user wants to receive a token they haven't held before, they or the sender should create a token account for that token first. ## Privacy-preserving transactions LEE aims to support privacy-preserving transactions. The token program design itself, however, is agnostic to whether a transaction is private or public. All instructions (create token, transfer) can be executed normally on-chain or alternatively executed off-chain and proven on-chain: - A privacy-preserving token transfer would mean the user executes the token program's transfer logic off-chain (perhaps in a zk circuit), using private inputs, and then submits a proof that this execution was done correctly according to the token program's rules. If the proof verifies, the network accepts the state change (balances updated) without revealing sensitive details. - The token program code does not change for privacy as it will be the same logic being proved. Thus, we do not embed any special privacy logic in the token program; we rely on the outer protocol to handle the ZKP part. The program just needs to be deterministic and suitable for circuit execution. The token program is designed in a straightforward way without knowledge of privacy layers. This keeps the design clean and avoids entangling business logic with privacy mechanics. Users who want privacy will invoke the same program functions but via the privacy-preserving pipeline of LEE.