UNPKG

@btc-vision/btc-runtime

Version:

Bitcoin L1 Smart Contract Runtime for OP_NET. Build decentralized applications on Bitcoin using AssemblyScript and WebAssembly. Fully audited.

903 lines (701 loc) 24.1 kB
# OP20 API Reference The `OP20` class implements the fungible token standard, equivalent to ERC20 on Ethereum. ## Import ```typescript import { OP20, OP20InitParameters } from '@btc-vision/btc-runtime/runtime'; ``` ## OP20 Architecture ```mermaid classDiagram class OP20 { <<abstract>> +_totalSupply: StoredU256 #_maxSupply: StoredU256 #_decimals: StoredU256 #_name: StoredString #_symbol: StoredString #_icon: StoredString #balanceOfMap: AddressMemoryMap #allowanceMap: MapOfMap~u256~ #_nonceMap: AddressMemoryMap +instantiate(params, skipDeployerVerification) void +name(calldata) BytesWriter +symbol(calldata) BytesWriter +icon(calldata) BytesWriter +decimals(calldata) BytesWriter +totalSupply(calldata) BytesWriter +maximumSupply(calldata) BytesWriter +balanceOf(calldata) BytesWriter +allowance(calldata) BytesWriter +nonceOf(calldata) BytesWriter +domainSeparator(calldata) BytesWriter +metadata(calldata) BytesWriter +transfer(calldata) BytesWriter +transferFrom(calldata) BytesWriter +safeTransfer(calldata) BytesWriter +safeTransferFrom(calldata) BytesWriter +increaseAllowance(calldata) BytesWriter +decreaseAllowance(calldata) BytesWriter +increaseAllowanceBySignature(calldata) BytesWriter +decreaseAllowanceBySignature(calldata) BytesWriter +burn(calldata) BytesWriter #_mint(to, amount) void #_burn(from, amount) void #_transfer(from, to, amount) void #_balanceOf(owner) u256 #_allowance(owner, spender) u256 #_increaseAllowance(owner, spender, amount) void #_decreaseAllowance(owner, spender, amount) void #_spendAllowance(owner, spender, amount) void #_safeTransfer(from, to, amount, data) void } class ReentrancyGuard { <<abstract>> #reentrancyLevel: ReentrancyLevel #isSelectorExcluded(selector) bool } class OP_NET { <<abstract>> +address: Address +contractDeployer: Address #emitEvent(event) void #onlyDeployer(caller) void Note: @method decorator handles routing } class MyToken { +constructor() +onDeployment(calldata) void +mint(calldata) BytesWriter Note: @method decorator handles routing } OP_NET <|-- ReentrancyGuard ReentrancyGuard <|-- OP20 OP20 <|-- MyToken note for OP20 "Fungible token standard\nwith safe math and\nreentrancy protection" note for MyToken "Your implementation\nextends OP20" ``` ## Class Definition ```typescript @final export class MyToken extends OP20 { public constructor() { super(); } public override onDeployment(calldata: Calldata): void { this.instantiate(new OP20InitParameters( maxSupply, decimals, name, symbol, icon )); } } ``` ## Initialization ### OP20InitParameters ```typescript class OP20InitParameters { constructor( maxSupply: u256, decimals: u8, name: string, symbol: string, icon: string = '' ) } ``` | Parameter | Type | Description | |-----------|------|-------------| | `maxSupply` | `u256` | Maximum token supply (use `u256.Max` for unlimited) | | `decimals` | `u8` | Token decimals (max 32) | | `name` | `string` | Token name | | `symbol` | `string` | Token symbol | | `icon` | `string` | Token icon URL (optional, default empty string) | ### instantiate Initializes the OP20 token. Must be called in `onDeployment`. ```typescript public instantiate(params: OP20InitParameters, skipDeployerVerification: boolean = false): void ``` ```typescript public override onDeployment(calldata: Calldata): void { this.instantiate(new OP20InitParameters( u256.fromU64(1000000), // Max supply: 1M 18, // 18 decimals 'My Token', // Name 'MTK', // Symbol '' // Icon URL (optional) )); } ``` **Solidity Comparison:** | Solidity (ERC20) | OP_NET (OP20) | |------------------|--------------| | `constructor(string name, string symbol)` | `onDeployment(calldata)` + `instantiate()` | ## View Methods All view methods in OP20 take `Calldata` and return `BytesWriter`. The `@method` decorator handles ABI encoding/decoding. ### name Returns the token name. ```typescript @method() @returns({ name: 'name', type: ABIDataTypes.STRING }) public name(calldata: Calldata): BytesWriter ``` ### symbol Returns the token symbol. ```typescript @method() @returns({ name: 'symbol', type: ABIDataTypes.STRING }) public symbol(calldata: Calldata): BytesWriter ``` ### icon Returns the token icon URL. ```typescript @method() @returns({ name: 'icon', type: ABIDataTypes.STRING }) public icon(calldata: Calldata): BytesWriter ``` ### decimals Returns the number of decimals. ```typescript @method() @returns({ name: 'decimals', type: ABIDataTypes.UINT8 }) public decimals(calldata: Calldata): BytesWriter ``` ### nonceOf Returns the current nonce for an address (used for signature verification). ```typescript @method({ name: 'owner', type: ABIDataTypes.ADDRESS }) @returns({ name: 'nonce', type: ABIDataTypes.UINT256 }) public nonceOf(calldata: Calldata): BytesWriter ``` ### domainSeparator Returns the EIP-712 domain separator for signature verification. ```typescript @method() @returns({ name: 'domainSeparator', type: ABIDataTypes.BYTES32 }) public domainSeparator(calldata: Calldata): BytesWriter ``` ### metadata Returns all token metadata in a single call (name, symbol, icon, decimals, totalSupply, domainSeparator). ```typescript @method() @returns( { name: 'name', type: ABIDataTypes.STRING }, { name: 'symbol', type: ABIDataTypes.STRING }, { name: 'icon', type: ABIDataTypes.STRING }, { name: 'decimals', type: ABIDataTypes.UINT8 }, { name: 'totalSupply', type: ABIDataTypes.UINT256 }, { name: 'domainSeparator', type: ABIDataTypes.BYTES32 }, ) public metadata(calldata: Calldata): BytesWriter ``` ### totalSupply Returns current total supply. ```typescript @method() @returns({ name: 'totalSupply', type: ABIDataTypes.UINT256 }) public totalSupply(calldata: Calldata): BytesWriter ``` ### maximumSupply Returns maximum possible supply. ```typescript @method() @returns({ name: 'maximumSupply', type: ABIDataTypes.UINT256 }) public maximumSupply(calldata: Calldata): BytesWriter ``` ### balanceOf Returns balance of an address. ```typescript @method({ name: 'owner', type: ABIDataTypes.ADDRESS }) @returns({ name: 'balance', type: ABIDataTypes.UINT256 }) public balanceOf(calldata: Calldata): BytesWriter ``` Internal helper (for use within your contract): ```typescript protected _balanceOf(owner: Address): u256 ``` ### allowance Returns spending allowance. ```typescript @method( { name: 'owner', type: ABIDataTypes.ADDRESS }, { name: 'spender', type: ABIDataTypes.ADDRESS }, ) @returns({ name: 'remaining', type: ABIDataTypes.UINT256 }) public allowance(calldata: Calldata): BytesWriter ``` Internal helper (for use within your contract): ```typescript protected _allowance(owner: Address, spender: Address): u256 ``` **Solidity Comparison:** | Solidity (ERC20) | OP_NET (OP20) | |------------------|--------------| | `function name() view returns (string)` | `name(calldata): BytesWriter` | | `function balanceOf(address) view returns (uint256)` | `balanceOf(calldata): BytesWriter` | | `function allowance(address, address) view returns (uint256)` | `allowance(calldata): BytesWriter` | ## Transfer Methods ### transfer (Calldata) Transfers tokens from caller to recipient. ```typescript public transfer(calldata: Calldata): BytesWriter ``` **Calldata format:** | Field | Type | Size | |-------|------|------| | to | Address | 32 bytes | | amount | u256 | 32 bytes | **Returns:** Boolean (success) The following diagram shows the complete transfer validation and execution flow: ```mermaid flowchart LR subgraph "Validation" Start([Transfer Request]) --> CheckFrom{from == zero?} CheckFrom -->|Yes| Revert1[Revert: Invalid sender] CheckFrom -->|No| CheckTo{to == zero?} CheckTo -->|Yes| Revert2[Revert: Invalid receiver] CheckTo -->|No| GetBal[Get balance of from] end subgraph "Balance Check" GetBal --> CheckBal{balance >= amount?} CheckBal -->|No| Revert3[Revert: Insufficient balance] CheckBal -->|Yes| SubBal[balance from -= amount] end subgraph "Transfer Execution" SubBal --> AddBal[balance to += amount] AddBal --> Emit[Emit TransferredEvent] Emit --> Success([Complete]) end ``` ### transferFrom (Calldata) Transfers tokens using allowance. ```typescript public transferFrom(calldata: Calldata): BytesWriter ``` **Calldata format:** | Field | Type | Size | |-------|------|------| | from | Address | 32 bytes | | to | Address | 32 bytes | | amount | u256 | 32 bytes | **Returns:** Boolean (success) The following sequence diagram illustrates both standard transfer and transferFrom with allowance: ```mermaid sequenceDiagram participant User as 👤 User participant Contract as OP20 Contract participant BalMap as Balance Map participant Events as Event System Note over User,Events: Standard Transfer Flow User->>Contract: transfer(to, amount) Contract->>Contract: _transfer(tx.sender, to, amount) Contract->>Contract: Validate from != zero Contract->>Contract: Validate to != zero Contract->>BalMap: Get balance[from] BalMap->>Contract: Return balance Contract->>Contract: Check balance >= amount Contract->>BalMap: Set balance[from] -= amount Contract->>BalMap: Set balance[to] += amount Contract->>Events: Emit TransferredEvent(operator, from, to, amount) Contract->>User: Return success Note over User,Events: TransferFrom with Allowance User->>Contract: transferFrom(from, to, amount) Contract->>Contract: _spendAllowance(from, tx.sender, amount) alt Allowance is u256.Max Contract->>Contract: Skip allowance deduction else Normal allowance Contract->>Contract: Check allowance >= amount Contract->>Contract: Deduct from allowance end Contract->>Contract: _transfer(from, to, amount) Contract->>Events: Emit TransferredEvent Contract->>User: Return success ``` **Solidity Comparison:** | Solidity (ERC20) | OP_NET (OP20) | |------------------|--------------| | `function transfer(address to, uint256 amount) returns (bool)` | `transfer(calldata): BytesWriter` | | `function transferFrom(address from, address to, uint256 amount) returns (bool)` | `transferFrom(calldata): BytesWriter` | ## Approval Methods ### increaseAllowance (Calldata) Increases the allowance granted to a spender. ```typescript public increaseAllowance(calldata: Calldata): BytesWriter ``` **Calldata format:** | Field | Type | Size | |-------|------|------| | spender | Address | 32 bytes | | amount | u256 | 32 bytes | **Returns:** Empty BytesWriter (emits ApprovedEvent) **Note:** If overflow would occur, sets allowance to u256.Max (unlimited). ### decreaseAllowance (Calldata) Decreases the allowance granted to a spender. ```typescript public decreaseAllowance(calldata: Calldata): BytesWriter ``` **Calldata format:** | Field | Type | Size | |-------|------|------| | spender | Address | 32 bytes | | amount | u256 | 32 bytes | **Returns:** Empty BytesWriter (emits ApprovedEvent) **Note:** If underflow would occur, sets allowance to zero. ### increaseAllowanceBySignature (Calldata) Increases allowance using an EIP-712 typed signature (gasless approval). ```typescript public increaseAllowanceBySignature(calldata: Calldata): BytesWriter ``` **Calldata format:** | Field | Type | Size | |-------|------|------| | owner | bytes32 | 32 bytes | | ownerTweakedPublicKey | bytes32 | 32 bytes | | spender | Address | 32 bytes | | amount | u256 | 32 bytes | | deadline | u64 | 8 bytes | | signature | bytes | variable | ### decreaseAllowanceBySignature (Calldata) Decreases allowance using an EIP-712 typed signature. ```typescript public decreaseAllowanceBySignature(calldata: Calldata): BytesWriter ``` **Calldata format:** | Field | Type | Size | |-------|------|------| | owner | bytes32 | 32 bytes | | ownerTweakedPublicKey | bytes32 | 32 bytes | | spender | Address | 32 bytes | | amount | u256 | 32 bytes | | deadline | u64 | 8 bytes | | signature | bytes | variable | The following diagram shows the allowance management flow including both direct and signature-based methods: ```mermaid flowchart LR subgraph "Allowance Method Selection" A[User Allows Spender] --> B{Choose<br/>Method} B -->|Direct| C[increaseAllowance] B -->|Off-chain| D[increaseAllowanceBySignature] end subgraph "Direct Allowance Path" C --> E[Get allowanceMap owner] E --> F[Get current for spender] F --> G[new = current + amount] G --> H{Overflow?} H -->|Yes| I[Set to u256.Max<br/>unlimited] H -->|No| J[Set new allowance] end subgraph "Signature Path" D --> L[Verify EIP-712<br/>signature] L --> M{Valid?} M -->|No| N[Revert:<br/>Invalid signature] M -->|Yes| O[Increment nonce] O --> E end I --> K[Emit ApprovedEvent] J --> K ``` The complete allowance lifecycle from approval to spending: ```mermaid sequenceDiagram participant Owner participant Contract as OP20 participant AllowMap as Allowance Map participant Spender Note over Owner,Spender: Standard Approval Flow Owner->>Contract: increaseAllowance(spender, 1000) Contract->>AllowMap: Get allowanceMap[owner] AllowMap->>Contract: Return owner's map Contract->>Contract: Get current allowance for spender Contract->>Contract: newAllowance = current + 1000 Contract->>AllowMap: Set allowance[owner][spender] = newAllowance Contract->>Contract: Emit ApprovedEvent Contract->>Owner: Success Note over Owner,Spender: Spender Uses Allowance Spender->>Contract: transferFrom(owner, recipient, 500) Contract->>AllowMap: Get allowance[owner][spender] AllowMap->>Contract: Returns 1000 alt Allowance is u256.Max Contract->>Contract: Skip deduction (infinite approval) else Normal allowance Contract->>Contract: Check 1000 >= 500 Contract->>AllowMap: Set allowance to 1000 - 500 = 500 end Contract->>Contract: Execute transfer Contract->>Spender: Success ``` **Solidity Comparison:** | Solidity (ERC20) | OP_NET (OP20) | |------------------|--------------| | `function approve(address, uint256) returns (bool)` | N/A (use increaseAllowance/decreaseAllowance) | | `function increaseAllowance(address, uint256) returns (bool)` | `increaseAllowance(calldata): BytesWriter` | | `function decreaseAllowance(address, uint256) returns (bool)` | `decreaseAllowance(calldata): BytesWriter` | ## Protected Methods For internal contract use: ### _mint Mints new tokens. ```typescript protected _mint(to: Address, amount: u256): void ``` ```typescript @method( { name: 'to', type: ABIDataTypes.ADDRESS }, { name: 'amount', type: ABIDataTypes.UINT256 }, ) @returns({ name: 'success', type: ABIDataTypes.BOOL }) @emit('Mint') public mint(calldata: Calldata): BytesWriter { this.onlyDeployer(Blockchain.tx.sender); const to: Address = calldata.readAddress(); const amount: u256 = calldata.readU256(); this._mint(to, amount); return new BytesWriter(0); } ``` ### _burn Burns tokens. ```typescript protected _burn(from: Address, amount: u256): void ``` ```typescript @method({ name: 'amount', type: ABIDataTypes.UINT256 }) @returns({ name: 'success', type: ABIDataTypes.BOOL }) @emit('Burn') public burn(calldata: Calldata): BytesWriter { const amount: u256 = calldata.readU256(); this._burn(Blockchain.tx.sender, amount); return new BytesWriter(0); } ``` ### _transfer Internal transfer. ```typescript protected _transfer(from: Address, to: Address, amount: u256): void ``` ### _increaseAllowance Internal method to increase allowance with overflow protection. ```typescript protected _increaseAllowance(owner: Address, spender: Address, amount: u256): void ``` ### _decreaseAllowance Internal method to decrease allowance with underflow protection. ```typescript protected _decreaseAllowance(owner: Address, spender: Address, amount: u256): void ``` ### _spendAllowance Decrements allowance. ```typescript protected _spendAllowance(owner: Address, spender: Address, amount: u256): void ``` **Solidity Comparison:** | Solidity (ERC20) | OP_NET (OP20) | |------------------|--------------| | `function _mint(address, uint256) internal` | `_mint(Address, u256): void` | | `function _burn(address, uint256) internal` | `_burn(Address, u256): void` | | `function _transfer(address, address, uint256) internal` | `_transfer(Address, Address, u256): void` | ## Safe Transfer Methods ### safeTransfer Transfer with recipient callback. ```typescript public safeTransfer(calldata: Calldata): BytesWriter ``` **Calldata format:** | Field | Type | Size | |-------|------|------| | to | Address | 32 bytes | | amount | u256 | 32 bytes | | data | bytes | variable (with length prefix) | Calls `onOP20Received` on recipient if it's a contract. ### safeTransferFrom TransferFrom with callback. ```typescript public safeTransferFrom(calldata: Calldata): BytesWriter ``` **Calldata format:** | Field | Type | Size | |-------|------|------| | from | Address | 32 bytes | | to | Address | 32 bytes | | amount | u256 | 32 bytes | | data | bytes | variable (with length prefix) | The following diagram shows the safe transfer flow with recipient callback verification: ```mermaid sequenceDiagram participant User as 👤 User participant Token as OP20 Contract participant Target as Recipient Contract participant Guard as ReentrancyGuard Note over User,Guard: Safe Transfer Flow User->>Token: safeTransfer(to, amount, data) Token->>Guard: Check reentrancy depth Guard->>Token: OK (depth allowed for callback) Token->>Token: _transfer(from, to, amount) Token->>Token: Update balances Token->>Token: Emit TransferredEvent Token->>Token: Check if recipient is contract alt Recipient is Contract Token->>Token: Build onOP20Received calldata Token->>Target: call(onOP20Received(operator, from, amount, data)) alt Target implements onOP20Received Target->>Target: Process receipt Target->>Token: Return ON_OP20_RECEIVED_SELECTOR Token->>Token: Verify return value alt Return value matches selector Token->>User: Success else Invalid return value Token->>User: Revert: Transfer rejected end else Target doesn't implement Target->>Token: Error or wrong selector Token->>User: Revert: Transfer rejected end else Recipient is EOA Token->>User: Success (no callback needed) end ``` ## Events ### TransferredEvent Emitted on transfers (transfer, transferFrom, safeTransfer, safeTransferFrom). ```typescript class TransferredEvent extends NetEvent { constructor( operator: Address, // Address that initiated the transfer from: Address, // Address tokens transferred from to: Address, // Address tokens transferred to amount: u256 // Amount transferred ) } ``` ### ApprovedEvent Emitted on allowance changes (increaseAllowance, decreaseAllowance). ```typescript class ApprovedEvent extends NetEvent { constructor( owner: Address, spender: Address, amount: u256 ) } ``` ### MintedEvent Emitted on minting. ```typescript class MintedEvent extends NetEvent { constructor( to: Address, amount: u256 ) } ``` ### BurnedEvent Emitted on burning. ```typescript class BurnedEvent extends NetEvent { constructor( from: Address, amount: u256 ) } ``` **Solidity Comparison:** | Solidity (ERC20) | OP_NET (OP20) | |------------------|--------------| | `event Transfer(address indexed from, address indexed to, uint256 value)` | `TransferredEvent(operator, from, to, amount)` | | `event Approval(address indexed owner, address indexed spender, uint256 value)` | `ApprovedEvent(owner, spender, amount)` | | `emit Transfer(from, to, amount)` | `emitEvent(new TransferredEvent(operator, from, to, amount))` | ## Storage Layout OP20 uses 7 storage pointers: | Pointer | Purpose | |---------|---------| | 0 | Nonce mapping (for signatures) | | 1 | Max supply | | 2 | Decimals | | 3 | String pointer (shared for name/symbol/icon) | | 4 | Total supply | | 5 | Allowances mapping | | 6 | Balances mapping | ## Method Selectors | Selector | Method | |----------|--------| | `name` | Returns name | | `symbol` | Returns symbol | | `icon` | Returns icon URL | | `decimals` | Returns decimals | | `totalSupply` | Returns total supply | | `maximumSupply` | Returns max supply | | `balanceOf` | Returns balance | | `allowance` | Returns allowance | | `nonceOf` | Returns nonce for address | | `domainSeparator` | Returns EIP-712 domain separator | | `metadata` | Returns all metadata | | `transfer` | Transfer tokens | | `transferFrom` | Transfer with allowance | | `safeTransfer` | Safe transfer with callback | | `safeTransferFrom` | Safe transferFrom with callback | | `increaseAllowance` | Increase spender allowance | | `decreaseAllowance` | Decrease spender allowance | | `increaseAllowanceBySignature` | Gasless allowance increase | | `decreaseAllowanceBySignature` | Gasless allowance decrease | | `burn` | Burn tokens from sender | ## Complete Example ```typescript import { u256 } from '@btc-vision/as-bignum/assembly'; import { OP20, OP20InitParameters, Blockchain, Calldata, BytesWriter, ABIDataTypes, } from '@btc-vision/btc-runtime/runtime'; @final export class MyToken extends OP20 { public constructor() { super(); } public override onDeployment(calldata: Calldata): void { const maxSupply = calldata.readU256(); const decimals = calldata.readU8(); const name = calldata.readString(); const symbol = calldata.readString(); const initialMint = calldata.readU256(); this.instantiate(new OP20InitParameters( maxSupply, decimals, name, symbol, '' // icon (optional) )); if (!initialMint.isZero()) { this._mint(Blockchain.tx.origin, initialMint); } } @method( { name: 'to', type: ABIDataTypes.ADDRESS }, { name: 'amount', type: ABIDataTypes.UINT256 }, ) @returns({ name: 'success', type: ABIDataTypes.BOOL }) @emit('Minted') public mint(calldata: Calldata): BytesWriter { this.onlyDeployer(Blockchain.tx.sender); const to = calldata.readAddress(); const amount = calldata.readU256(); this._mint(to, amount); // Note: _mint already emits MintedEvent internally return new BytesWriter(0); } } ``` ## Solidity Comparison Summary | Solidity (ERC20) | OP_NET (OP20) | |------------------|--------------| | `constructor(...)` | `onDeployment(calldata)` | | `function name()` | `name(): string` | | `function balanceOf(address)` | `balanceOf(Address): u256` | | `_mint(address, uint256)` | `_mint(Address, u256)` | | `emit Transfer(...)` | `emitEvent(new TransferredEvent(...))` | --- **Navigation:** - Previous: [Blockchain API](./blockchain.md) - Next: [OP721 API](./op721.md)