NEAR Blockchain
Video/Text

Part 3: Building the Fungible Token Contract

Lesson 9 Chapter 2 Module 2

The last lesson saw us implement the Fungible Token Standard in main.ts.  While that is all we really need as a foundation to create and use a token on NEAR, there's a couple more functions we're going to add to make the token more useful and extend the token standard.

Section 5: Non-Spec Methods to Extend the Fungible Token Standard

Let's dive right in.  Add the following to the end of your main.ts file.

/******************************************************/
/* NON-SPEC METHODS TO EXTEND FUNGIBLE TOKEN STANDARD */
/******************************************************/

// CHANGE METHODS
// ————–

/**
* Allow owner to burn tokens
* @param tokens
*/
export function burn(tokens: u128): boolean {
assert(isOwner(Context.predecessor), ERR_NOT_OWNER)
const balance = get_balance(Context.predecessor)
assert(balance >= tokens, ERR_NOT_ENOUGH_TOKENS)
let currentSupply = totalSupply.getSome(‘totalSupply’)
totalSupply.set(‘totalSupply’, u128.sub(currentSupply, tokens))
balanceRegistry.set(Context.predecessor, u128.sub(balance , tokens))
return true;
}

/**
* Allow owner to mint more tokens
* @param tokens
*/
export function mint(tokens: u128): boolean {
assert(isOwner(Context.predecessor), ERR_NOT_OWNER)
let currentSupply = totalSupply.getSome(‘totalSupply’)
totalSupply.set(‘totalSupply’, u128.add(currentSupply, tokens))
let currentBalance = get_balance(Context.predecessor)
balanceRegistry.set(Context.predecessor, u128.add(currentBalance, tokens));
return true;
}

/**
* Allow current owner to transfer ownership to a new owner
* @param newOwner
*/
export function transferOwnership(newOwner: AccountId): boolean {
assert(isOwner(Context.predecessor), ERR_NOT_OWNER)
assert(env.isValidAccountID(newOwner), ERR_INVALID_ACCOUNT_ID)
storage.set(“owner”, newOwner);
return true;
}

// VIEW METHODS
// ————

/**
* get name of the token
*/
export function getTokenName(): string {
return storage.getSome<string>(‘tokenName’)
}

/**
* get token symbol
*/
export function getTokenSymbol(): string {
return storage.getSome<string>(‘tokenSymbol’)
}

/**
* returns number of decimals the token uses. e.g. 8 means to divide the token
* amount by 100000000 to get its user representation
*/
export function getPrecision(): u128 {
return storage.getSome<u128>(‘precision’)
}

export function burn...

In plain English - for a variety of reasons we might want to reduce the total supply of tokens available.  Without a burn function, that wouldn't be possible.  With a burn function, we enable the overall token owner (the one who created the token in the first place) the ability to reduce the totalSupply by a designated number of tokens - effectively taking them out of existence.

The function takes a u128 representing the number of tokens to burn as an argument and returns true or false (it worked or it didn't).  Let's run through it:

  • first we use the isOwner utility function to assert that the account calling the function is the owner of the token otherwise we abort.  In our implementation, only the original token creator can burn tokens.
  • next, we get the owner's token balance.  It's not good enough to just burn tokens from totalSupply without checking the owner's balance because he or she may have already transferred some to other accounts (people).  Those shouldn't be considered burnable because the owner doesn't own them anymore.
  • The next assert ensures that the owner's balance is greater than or equal to the number of tokens he or she is looking to burn, otherwise returns a not enough tokens error.
  • next we get the currentSupply (equal to totalSupply) and adjust it downwards by the number of tokens being burned.
  • lastly, we adjust the owner's balance by adjusting it downwards by the number of tokens being burned, returning true to confirm the tokens have been burned.

export function mint...

In plain English - for a variety of reasons we might want to increase the total supply of tokens available.  Without a mint function, that wouldn't be possible.  With a mint function, we enable the overall token owner (the one who created the token in the first place) the ability to increase the totalSupply by a designated number of tokens - effectively putting more into existence.

The function takes a u128 representing the number of tokens to burn as an argument and returns true or false (it worked or it didn't).  Let's run through it:

  • first we use the isOwner utility function to assert that the account calling the function is the owner of the token otherwise we abort.  In our implementation, only the original token creator can mint more tokens.
  • unlike with the burn function above, we do not need the owner's balance to check whether we should mint more tokens. There is no ownership concern here in creating tokens like there is if we burn tokens that might have belonged to someone else.
  • we get the currentSupply (equal to totalSupply) and adjust it upwards by the number of tokens being burned.
  • lastly, we adjust the owner's balance by adjusting it upwards by the number of tokens being minted, returning true to confirm the tokens have been created.

export function transferOwnership...

In plain English - there might be good reason for the current token creator (owner) to want to give that ownership to another person.  The transferOwnership function enables that.

  • first we use the isOwner utility function to assert that the account calling the function is the owner of the token otherwise we abort.  In our implementation, only the current token owner can transfer ownership to someone else.
  • next we confirm the account Id we are transferring ownership to is valid
  • finally, we look up the owner key in storage and set it to the value of the new account Id (newOwner), returning true to confirm ownership has been transferred

view functions - getTokenName(), getTokenSymbol(), getPrecision()...

In plain English - all three of these functions do the same thing - they return a piece of information about the token:  it's name, it's symbol, and it's precision.  That's it, that's all.

Congratulations, getting to this point means you have a fully functional Fungible Token contract ready for deployment.

Soon we'll build a frontend user interface using React so all this becomes intuitive and useful, but first we need to write some tests to confirm everything is working as it should. 

Pen
>