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.
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.
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.
Always protect methods intended for the token owner with this.onlyDeployer(Blockchain.tx.sender); to prevent unauthorized access.
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);
}
}