Skip to main content
In this tutorial, we will create a fungible token (FT) using the NEAR SDK Contract Tools package. This package is a collection of common tools and patterns to simplify smart contract development, including:
  • Storage fee management
  • Escrow pattern and derive macro
  • Owner pattern and derive macro
  • Pause pattern and derive macro
  • Role-based access control
  • Derive macros for NEP standards
    • NEP-141 (fungible token), extension NEP-148
    • NEP-145 (storage management), and integrations for the fungible token and non-fungible token standards
    • NEP-171 (non-fungible token), extensions NEP-177, NEP-178, NEP-181
    • NEP-297 (events)

Introduction

Use the existing global FT contract by default. Use this guide only if your application truly requires behavior the global contract does not provide. Use near-contract-standards or near-sdk-contract-tools for the token implementation; do not implement the FT standard by hand. near-sdk-contract-tools provides derive macros for FT standards and access control, allowing us to focus on custom logic such as minting and burning. In this example, the contract starts with zero supply and wraps deposited NEAR into tokens. Only the current owner can mint tokens by depositing NEAR or burn tokens from their own balance to redeem NEAR. Other accounts can hold and transfer tokens using the standard FT methods.

Basic FT Methods

To derive basic FT methods to our contract, we need to derive FungibleToken macro to our contract struct:
lib.rs
This adds the FT interface, metadata, and storage management methods to our contract:
  • ft_balance_of
  • ft_metadata
  • ft_total_supply
  • storage_balance_bounds
  • storage_balance_of
  • ft_resolve_transfer
  • ft_transfer
  • ft_transfer_call
  • storage_deposit
  • storage_unregister
  • storage_withdraw
To bring basic owner methods to our contract, we can also derive the Owner macro which adds the following methods:
  • own_get_owner
  • own_get_proposed_owner
  • own_accept_owner
  • own_propose_owner
  • own_renounce_owner

Initialization

To initialize the basic FT contract with a custom owner, metadata, and storage bounds, we implement the new method. The owner_id account can be different from the account where the contract is deployed. Calling Owner::init records that account as the current owner:
lib.rs

Transfer Hook

Use a transfer hook to run application logic before or after the library-provided transfer. Keep the standard ft_transfer implementation in the library:
transfer_hook.rs
Then derive it to our contract struct:
lib.rs

Minting

The FT standards do not define a minting method, so we implement our own mint method. It takes no arguments and mints tokens for the owner based on the attached NEAR deposit. We import the Owner trait and call self.assert_owner() before making any changes. This checks that the account calling the method is the current owner recorded by the contract:
mint.rs
If the owner is not registered to hold tokens, the method reserves the minimum storage deposit of 0.0025 NEAR from the attached deposit and mints from the remainder. To mint a positive amount on this first call, we attach more than the storage minimum. If the owner is already registered, the full attached deposit is used for minting. Each token unit represents one yoctoNEAR. For example, an unregistered owner attaching 1 NEAR receives 997500000000000000000000 token units, while a registered owner receives 1000000000000000000000000 token units.
You can add further restrictions to this method, for example, to allow minting only when the contract is not paused using Pausable, or to limit the amount the owner can mint.

Burning

The FT standards do not define a burning method either. We implement a burn method that removes amount token units from the owner’s balance and refunds the corresponding NEAR. As with minting, self.assert_owner() restricts the call to the current owner. The caller must attach exactly 1 yoctoNEAR, and amount is passed as a base-10 string in the smallest token units:
burn.rs
Burning reduces both the owner’s balance and the total supply by amount, then schedules a transfer of amount yoctoNEAR to the owner. For example, {"amount": "1000000000000000000000000"} burns token units representing 1 NEAR and refunds 1 NEAR. The owner must have enough tokens in their balance to burn the requested amount. The owner checks in both methods follow ownership transfers through own_propose_owner and own_accept_owner. Once the new owner accepts, only that account can mint or burn. Token holders who are not the current owner can still transfer tokens, but cannot mint or redeem them through these methods.

Testing the Contract

From the example repository, run:
The tests build the FT contract and DeFi receiver from source, then use near-sandbox and near-api to check minting, burning, ownership transfers, storage refunds, and cross-contract transfers.

Conclusion

Using near-sdk-contract-tools is a very simple and flexible way to create FT contract with minimal boilerplate which allows us to focus on the business logic. You can further extend this contract with more features like pausing, role-based access control, escrow pattern, and more by deriving corresponding macros from the package.
Need help?Visit the NEAR Discord or NEAR Forum if you have questions or need support.