Creating Tokens

This page provides a step-by-step guide for creating an OP-20 compliant token. You will learn how to implement the required interface and customize your token's behavior.

Extend the OP20 Class

Create a new class that extends OP20 class to inherit the standard token functionality.

My Token - Step 1assemblyscript
import { u256 } from '@btc-vision/as-bignum/assembly';
import {
    Address,
    AddressMap,
    Blockchain,
    BytesWriter,
    Calldata,
    OP20,
    OP20InitParameters,
    SafeMath,
} from '@btc-vision/btc-runtime/runtime';

@final
export class MyToken extends OP20 {
    public constructor() {
        super();

        // IMPORTANT. THIS WILL RUN EVERYTIME THE CONTRACT IS INTERACTED WITH. 
        // FOR SPECIFIC INITIALIZATION, USE "onDeployment" METHOD.
    }
}

Override onDeployment() Method

Provide an onDeployment() method to handle contract initialization. This method functions similarly to a Solidity constructor and executes once during deployment. Within this method, call instantiate() with an OP20InitParameters object containing your token's metadata (maximum supply, name, symbol, decimals).

Optionally, you can mint an initial token supply.

My Token - Step 2assemblyscript
import { u256 } from '@btc-vision/as-bignum/assembly';
import {
    Address,
    AddressMap,
    Blockchain,
    BytesWriter,
    Calldata,
    OP20,
    OP20InitParameters,
    SafeMath,
} from '@btc-vision/btc-runtime/runtime';

@final
export class MyToken extends OP20 {
    public constructor() {
        super();

        // IMPORTANT. THIS WILL RUN EVERYTIME THE CONTRACT IS INTERACTED WITH. 
        // FOR SPECIFIC INITIALIZATION, USE "onDeployment" METHOD.
    }

    // This is a solidity-like constructor.
    // This method will only run once when the contract is deployed.
    public override onDeployment(_calldata: Calldata): void {
        // max supply: 1 billion tokens
        // decimals: 18
        // name: Test
        // symbol: TEST
        const parameters = new OP20InitParameters(
            u256.fromString('1000000000000000000000000000'), 
            18,
            'Test',
            'TEST'
        );

        this.instantiate(parameters);

        // Add your logic here. Eg, minting the initial supply:
        // this._mint(Blockchain.tx.origin, maxSupply);
    }
}

Add Custom Functionality (optional)

Extend your token with additional features such as:

  • Public minting function.
  • Airdrop mechanisms.
  • Custom transfer logic.
Security Best Practice

Always protect methods intended for the token owner with this.onlyDeployer(Blockchain.tx.sender); to prevent unauthorized access.

My Token - Step 3assemblyscript
import { u256 } from '@btc-vision/as-bignum/assembly';
import {
    Address,
    AddressMap,
    Blockchain,
    BytesWriter,
    Calldata,
    OP20,
    OP20InitParameters,
    SafeMath,
} from '@btc-vision/btc-runtime/runtime';

@final
export class MyToken extends OP20 {
    public constructor() {
        super();

        // IMPORTANT. THIS WILL RUN EVERYTIME THE CONTRACT IS INTERACTED WITH. 
        // FOR SPECIFIC INITIALIZATION, USE "onDeployment" METHOD.
    }

    // This is a solidity-like constructor.
    // This method will only run once when the contract is deployed.
    public override onDeployment(_calldata: Calldata): void {
        // max supply: 1 billion tokens
        // decimals: 18
        // name: Test
        // symbol: TEST
        const parameters = new OP20InitParameters(
            u256.fromString('1000000000000000000000000000'), 
            18,
            'Test',
            'TEST'
        );

        this.instantiate(parameters);

        // Add your logic here. Eg, minting the initial supply:
        // this._mint(Blockchain.tx.origin, maxSupply);
    }

    @method(
        {
            name: 'address',
            type: ABIDataTypes.ADDRESS,
        },
        {
            name: 'amount',
            type: ABIDataTypes.UINT256,
        },
    )
    @emit('Minted')
    public mint(calldata: Calldata): BytesWriter {
        this.onlyDeployer(Blockchain.tx.sender);

        this._mint(calldata.readAddress(), calldata.readU256());

        return new BytesWriter(0);
    }

    @method({
        name: 'addressAndAmount',
        type: ABIDataTypes.ADDRESS_UINT256_TUPLE,
    })
    @emit('Minted')
    public airdrop(calldata: Calldata): BytesWriter {
        this.onlyDeployer(Blockchain.tx.sender);

        const addressAndAmount: AddressMap<u256> = calldata.readAddressMapU256();
        const addresses: Address[] = addressAndAmount.keys();

        let totalAirdropped: u256 = u256.Zero;

        for (let i: i32 = 0; i < addresses.length; i++) {
            const address = addresses[i];
            const amount = addressAndAmount.get(address);

            const currentBalance: u256 = this.balanceOfMap.get(address);

            if (currentBalance) {
                this.balanceOfMap.set(address, SafeMath.add(currentBalance, amount));
            } else {
                this.balanceOfMap.set(address, amount);
            }

            totalAirdropped = SafeMath.add(totalAirdropped, amount);

            if (this._totalSupply.value + totalAirdropped > this._maxSupply.value) {
                throw new Revert('Max supply reached');
            }

            this.createMintedEvent(address, amount);
        }

        this._totalSupply.set(SafeMath.add(this._totalSupply.value, totalAirdropped));

        return new BytesWriter(0);
    }
}