OP-20 Implementations

Introduction

All OP_NET smart contracts:
  • Are required to extend the OP_NET base class (directly or indirectly).
  • Use the Calldata parameters and BytesWriter return values patterns.
  • Accepting tokens must implement the onOP20Received callback.
The following examples provide a brief reminder of working with Calldata and BytesWriter:
Patterns examplesassemblyscript
// How to encode the Calldata parameters
// 1st parameter: selector (method to call)
// 2nd parameter: address1
// 3rd parameter: amount1
const calldata = new BytesWriter(
SELECTOR_BYTE_LENGTH +
ADDRESS_BYTE_LENGTH +
U256_BYTE_LENGTH);

calldata.writeSelector('FUNCTION SELECTOR HERE');
calldata.writeAddress(address1);
calldata.writeU256(amount1);

// How to decode BytesWriter return values
const response = Blockchain.call(token, calldata);

if (response.data.byteLength != 0){
thow new Revert('Invalid response.');
}

// Decode response
const value = response.data.readU256();
const name = response.data.readStringWithLength();

OP20 Abstract Class

The OP20 class is the OP_NET concrete implementation of the OP-20 standard. It implements the IOP20 interface and extends ReentrancyGuard, which provides both OP_NET base class inheritance and built-in reentrancy attack protection.

Protected methods can be overridden to implement custom logic or extend the default behavior, providing flexibility for specialized token implementations.

OP20 classassemblyscript
abstract class OP20 extends ReentrancyGuard implements IOP20 {
    public name(_: Calldata): BytesWriter;
    public symbol(_: Calldata): BytesWriter;
    public decimals(_: Calldata): BytesWriter;
    public icon(_: Calldata): BytesWriter;
    public totalSupply(_: Calldata): BytesWriter;
    public maximumSupply(_: Calldata): BytesWriter;
    public metadata(_: Calldata): BytesWriter;
    public domainSeparator(_: Calldata): BytesWriter;
    public balanceOf(calldata: Calldata): BytesWriter;
    public nonceOf(calldata: Calldata): BytesWriter;
    public allowance(calldata: Calldata): BytesWriter;
    public safeTransfer(calldata: Calldata): BytesWriter;
    public safeTransferFrom(calldata: Calldata): BytesWriter;
    public transfer(calldata: Calldata): BytesWriter;
    public transferFrom(calldata: Calldata): BytesWriter;
    public burn(calldata: Calldata): BytesWriter;
    public increaseAllowance(calldata: Calldata): BytesWriter;
    public decreaseAllowance(calldata: Calldata): BytesWriter;
    public increaseAllowanceBySignature(calldata: Calldata): BytesWriter;
    public decreaseAllowanceBySignature(calldata: Calldata): BytesWriter;
    
    // Used for token deployments
    public instantiate(params: OP20InitParameters, skipDeployerVerification: boolean = false): void;
    
    // Overrides
    protected _balanceOf(owner: Address): u256;
    protected _allowance(owner: Address, spender: Address): u256;
    protected _safeTransfer(from: Address, to: Address, amount: u256, data: Uint8Array): void;
    protected _transfer(from: Address, to: Address, amount: u256): void;
    protected _spendAllowance(owner: Address, spender: Address, amount: u256): void;
    protected _callOnOP20Received( from: Address, to: Address, amount: u256,data: Uint8Array): void;
    protected _increaseAllowance(owner: Address, spender: Address, amount: u256): void;
    protected _decreaseAllowance(owner: Address, spender: Address, amount: u256): void;
    protected _increaseAllowanceBySignature(owner: Address,spender: Address,amount: u256,deadline: u64,signature: Uint8Array): void;
    protected _decreaseAllowanceBySignature(owner: Address, spender: Address, amount: u256, deadline: u64, signature: Uint8Array): void;
    protected _verifySignature(typeHash: u8[], owner: Address, spender: Address, amount: u256, deadline: u64, signature: Uint8Array): void;
    protected _buildDomainSeparator(): Uint8Array;
    protected _mint(to: Address, amount: u256): void;
    protected _burn(from: Address, amount: u256): void;
}

Public Methods Reference

public name(_: Calldata): BytesWriter
public symbol(_: Calldata): BytesWriter
public decimals(_: Calldata): BytesWriter
public icon(_: Calldata): BytesWriter
public totalSupply(_: Calldata): BytesWriter
public maximumSupply(_: Calldata): BytesWriter
public metadata(_: Calldata): BytesWriter
public domainSeparator(_: Calldata): BytesWriter
public balanceOf(calldata: Calldata): BytesWriter
public nonceOf(calldata: Calldata): BytesWriter
public allowance(calldata: Calldata): BytesWriter
public safeTransfer(calldata: Calldata): BytesWriter
public safeTransferFrom(calldata: Calldata): BytesWriter
public transfer(calldata: Calldata): BytesWriter
public transferFrom(calldata: Calldata): BytesWriter
public burn(calldata: Calldata): BytesWriter
public increaseAllowance(calldata: Calldata): BytesWriter
public decreaseAllowance(calldata: Calldata): BytesWriter
public increaseAllowanceBySignature(calldata: Calldata): BytesWriter
public decreaseAllowanceBySignature(calldata: Calldata): BytesWriter
public instantiate(params: OP20InitParameters, skipDeployerVerification: boolean = false): void

Protected Methods Reference

Warning
The following protected methods can be overridden to implement custom logic. However, this must be done with caution. Any override must ensure that OP-20 standard requirements are still met and must not break the existing implementation.
protected _balanceOf(owner: Address): u256
protected _allowance(owner: Address, spender: Address): u256
protected _safeTransfer(from: Address, to: Address, amount: u256, data: Uint8Array): void
protected _transfer(from: Address, to: Address, amount: u256): void
protected _spendAllowance(owner: Address, spender: Address, amount: u256): void
protected _callOnOP20Received(from: Address, to: Address, amount: u256, data: Uint8Array): void
protected _increaseAllowance(owner: Address, spender: Address, amount: u256): void
protected _decreaseAllowance(owner: Address, spender: Address, amount: u256): void
protected _increaseAllowanceBySignature(owner: Address, spender: Address, amount: u256, deadline: u64, signature: Uint8Array): void
protected _decreaseAllowanceBySignature(owner: Address, spender: Address, amount: u256, deadline: u64, signature: Uint8Array): void
protected _verifySignature(typeHash: u8[], owner: Address, spender: Address, amount: u256, deadline: u64, signature: Uint8Array): void
protected _buildDomainSeparator(): Uint8Array
protected _mint(to: Address, amount: u256): void
protected _burn(from: Address, amount: u256): void

Events

The OP20 class emits the three OP-20 standard events: Approved, Burned, and Transferred.

Additionally, the OP20 class emits a Minted event, which is specific to this implementation and is implemented by the MintedEvent class.

Eventsassemblyscript
class ApprovedEvent extends NetEvent {
    constructor(owner: Address, spender: Address, amount: u256);
}

class BurnedEvent extends NetEvent {
    constructor(from: Address, amount: u256);
}

class MintedEvent extends NetEvent {
    constructor(to: Address, amount: u256)
}

class TransferredEvent extends NetEvent {
    constructor(operator: Address, from: Address, to: Address, amount: u256);
}
Approved(owner: Address, spender: Address, amount: u256)
Burned(from: Address, amount: u256)
Minted(to: Address, amount: u256)
Transferred(operator: Address, from: Address, to: Address, amount: u256)

Callbacks

The IOP20Receiver interface is not implemented by the OP20 abstract class. Instead, derived classes must provide their own implementation.

IOP20Receiver concrete implementation exampleassemblyscript
interface IOP20Receiver {
    onOP20Received(callData: Calldata): BytesWriter;
}

// Example
@final
export class ContractEx extends ReentrancyGuard implements IOP20Receiver {
    ...
    public override execute(method: Selector, calldata: Calldata): BytesWriter {
        let writer: BytesWriter;

        switch (method) {
            case encodeSelector('onOP20Received(address,address,uint256,bytes)'):
                return this.onOP20Received(calldata);
            default:
                return super.execute(method, calldata);
        }

        return writer;
    }
    
    public onOP20Received(calldata: Calldata): BytesWriter {
        const operator = calldata.readAddress();
        const from = calldata.readAddress();
        const amount = calldata.readU256();
        const data: Uint8Array = calldata.readBytesWithLength();
        // Custom logic here if needed
        ...

        const writer = new BytesWriter(SELECTOR_BYTE_LENGTH);
        writer.writeSelector(ON_OP_20_RECEIVED_SELECTOR);
        return writer;
    }
    ...
}

Data Structures

Concrete Implementations

  • OP20InitParameters
OP20InitParameters constructor

Type Definitions

Type definitions only, not concrete implementations:

  • OP712Domain
  • OP20AllowanceIncrease
  • OP20AllowanceDecrease

These type definitions define the data schema used by methods for signature validation and hash generation, such as signed allowance operations and the domainSeparator method.

Refer to the OP-20 Standard specification for detailed definitions.