Skip to content
Retr0 Academy
Tokens & NFTs on NEAR

Tokens & NFTs on NEAR · 3 min read

Fungible Tokens (NEP-141)

Implement a fungible token with near-contract-standards: metadata, storage deposits, ft_transfer, and ft_transfer_call.

NEP-141 Fungible Token

NEP-141 is NEAR’s fungible-token standard. Wallets expect a fixed set of methods, plus NEP-148 metadata and NEP-145 storage deposits.

The supported way to implement this in Rust is near-contract-standards, not a hand-rolled map of balances.

What wallets call

MethodKindRole
ft_metadataViewName, symbol, decimals, icon
ft_total_supply / ft_balance_ofViewSupply and balances, as strings of yocto-units
storage_depositChange, payableRegister the receiver before the first transfer
ft_transferChange, 1 yoctoNEARSend tokens (sender is predecessor)
ft_transfer_callChange, 1 yoctoNEARSend, then call ft_on_transfer on the receiver contract

Amounts are strings, not numbers, so wallets can handle 24-decimal values.

A minimal contract

rust

use near_contract_standards::fungible_token::metadata::{FungibleTokenMetadata, FT_METADATA_SPEC};
use near_contract_standards::fungible_token::FungibleToken;
use near_contract_standards::fungible_token::FungibleTokenCore;
use near_contract_standards::storage_management::StorageManagement;
use near_sdk::{near, AccountId, NearToken, PanicOnDefault};
 
#[near(contract_state)]
#[derive(PanicOnDefault)]
pub struct Contract {
    token: FungibleToken,
    metadata: FungibleTokenMetadata,
}
 
#[near]
impl Contract {
    #[init]
    pub fn new(owner: AccountId, total_supply: NearToken) -> Self {
        let mut this = Self {
            token: FungibleToken::new(b"t"),
            metadata: FungibleTokenMetadata {
                spec: FT_METADATA_SPEC.to_string(),
                name: "Retr0 Token".to_string(),
                symbol: "R0T".to_string(),
                decimals: 24,
                icon: None,
                reference: None,
                reference_hash: None,
            },
        };
        this.token.internal_register_account(&owner);
        this.token.internal_deposit(&owner, total_supply);
        this
    }
}
 
// Implement FungibleTokenCore, FungibleTokenResolver, StorageManagement,
// and FungibleTokenMetadataProvider by delegating to self.token / self.metadata.
// The official ft-rs example in near-examples shows the full boilerplate.

Storage deposits

The first time an account receives tokens, the contract stores a balance row. That row must already be paid for with storage_deposit (typically ~0.00125 NEAR). If you skip it, ft_transfer panics. Marketplaces and wallets do this automatically; when you test with CLI you must call it yourself:

bash

near call r0t.alice.testnet storage_deposit '{"account_id":"bob.testnet"}' \
  --deposit 0.00125 --useAccount bob.testnet --networkId testnet
 
near call r0t.alice.testnet ft_transfer \
  '{"receiver_id":"bob.testnet","amount":"1000000000000000000000000"}' \
  --depositYocto 1 --useAccount alice.testnet --networkId testnet

The attached 1 yoctoNEAR on ft_transfer is a standard safety check so the sender must use a full-access key (or a carefully issued function-call key).

Try a live token

Read a NEP-141 token
<h1>NEP-141 token</h1>
<input id="ft" value="wrap.testnet">
<button id="go">Read token</button>
<pre id="out">Press Read token.</pre>

Edit this example in the Playground.

Open in Playground

Exercise

Write the near call you would use to register bob.testnet with storage_deposit and then send them 1 token (10^24 yocto-units if decimals are 24). Include the 1 yoctoNEAR deposit on ft_transfer.

Open in Playground

Saved in this browser.