Skip to main content
Fungible tokens on NEAR follow the NEP-141 standard. Each token is implemented as a smart contract that tracks account balances and exposes methods to query balances (ft_balance_of), transfer tokens (ft_transfer), and transfer tokens to another contract while calling its ft_on_transfer method (ft_transfer_call).
Please see the spec for the fungible token standard and an example implementation for reference details.

Token metadata

The ft_metadata method returns the token’s name, symbol, decimals, and optional display information such as its icon.

Decimals

decimals determines how to display the integer amounts in which the token is denominated. For a token with decimals: 6, "1000000" raw units represents 1 token, and "1500000" represents 1.5 tokens. meta-pool.near uses 24 decimals, so "1000000000000000000000000" raw units represents 1 STNEAR.
Use the token’s metadata when converting amounts, but keep raw balances and transfer arguments as integer strings.

Get balance

Call ft_balance_of with the account ID to get its raw token balance.
NEP-141 returns balances as base-10 integer strings to preserve amounts larger than JavaScript’s safe integer limit (2^53 - 1). Decode the JSON string and use integer arithmetic for balances and transfers.

Simple transfer

Before calling ft_transfer, register the receiver with the token contract so the contract can store its balance.

Storage registration

Check storage_balance_of for the receiver. A null result means it is unregistered; query storage_balance_bounds and attach its min amount in yoctoNEAR when calling storage_deposit. The queries below show that root.near is already registered with meta-pool.near, whose minimum is 0.00125 NEAR.
For an unregistered receiver, deposit the returned minimum:

Transfer tokens

Call ft_transfer with the receiver’s account ID and an integer-string amount in the token’s smallest unit. Attach exactly 1 yoctoNEAR. With STNEAR’s 24 decimals, the amount below transfers 1 STNEAR to root.near.
Require final_execution_status === "FINAL" and a successful token-call receipt before confirming the transfer. An insufficient token balance or an unregistered receiver causes execution failure; inspect the error before retrying.

Deposit accounting

For general deposit accounting, use an indexer to inspect NEP-300 events in successful finalized token-contract outcomes: EVENT_JSON: with standard: "nep141", version: "1.0.0", and event: "ft_transfer". Authenticate executor_id against the approved token contract; another contract can print the same log.

Transfer and call

The ft_transfer_call method allows a user to transfer tokens to a contract while simultaneously triggering a function call on the receiving contract. Let’s call ft_transfer_call function on ft contract (receiver) and examine successful and unsuccessful scenarios.

Successful transfer and call

Let’s send 10 raw FT units to a DEFI contract that uses only 9. These are smallest-unit amounts, not NEAR or necessarily ten displayed tokens; apply the token’s approved decimals for display.
Now, let’s try to follow the steps described in the previous section and determine if these transactions was successful. In addition to being successful, let’s analyze the various receipts in the series of cross-contract calls to determine how many fungible tokens were transferred. This will be the most complex case we’ll look at.
  1. Check that result » transaction_outcome » outcome » status has SuccessReceiptId as a key. If not, this transaction-processing step did not produce the expected receipt; inspect the actual failure and all available outcomes.
  2. Take the value of the SuccessReceiptId key. In the case above it’s Hw6z8kJ7CSaC6SgyQzcmXzNX9gq1gaAnLS169qgyZ2Vk.
  3. Now, under result » receipts loop through the array until you find a receipt where the receipt_id matches the value from step 2. (Note that in the receipt, under Actions there’s an element mentioning calling the method_name: "ft_transfer_call".) On the same level of JSON, there’s an args key. That’s a base64-encoded value of the arguments passed to the method. When decoded it is:
  1. Loop through result » receipts_outcome until finding the object where id is equal to the value from step 2. Similar to step 1, this object will also contain a status field that should contain the key SuccessReceiptId. For this example, follow SuccessReceiptId to the resolver. In general, inspect success/failure and receipt dependencies rather than treating the absence of this particular key as proof that no tokens moved. In the above example, this value is 4Tc8MsrJZSMpNZx7u4jSqxr3WhRzqxaNHxLJFqz8tUPR.
  2. Similar to the previous step, loop through the result » receipts_outcome until you find the object where the id matches the value from step 4. In that object check that outcome » status has the SuccessValue field. This resolver’s SuccessValue reports how many fungible-token units were used, not the unused amount returned by ft_on_transfer. Note that in the example above the value is Ijki, which is the base64-encoded version of "9". In this example, 10 raw token units were sent (from step 3), 9 were used, and 1 was refunded.
For additional clarity, let’s take a look at one more optional aspect. In step 4 we isolated an object in result » receipts_outcome. There’s an array of receipt_ids that’s particularly interesting. In this example, receipt EB69xtJiLRh9RNzAHgBGmom8551hrK2xSRreqbjvJgu5 executes ft_on_transfer. Identify that role from the receipt’s executor and method, not its position in the array. If we loop through the result » receipts_outcome and find this as the value for the id key, we’ll see what happened in the function ft_on_transfer which takes place in the contract receiving the fungible tokens. In this object the status » SuccessValue is IjEi which is the base64-encoded value of "1". In summary:
  1. A user called the fungible token contract with the method ft_transfer_call specifying the receiver account, how many tokens to send, and custom info.
  2. The receiver account implemented ft_on_transfer, returning "1" to the callback function on the fungible token contract.
  3. The fungible token contract’s callback is ft_resolve_transfer and receives this value of "1". It attempts to refund the 1 unused unit, subject to the receiver’s remaining token balance, and subtracts the actual refund from the 10 initially transferred. It then returns to the user how many tokens were used in this back-and-forth series of cross-contract calls: "9".
Wait for finalized resolver execution before releasing a transfer-and-call deposit. An initial transfer event can be followed by a refund event. For a conventional NEP-141 implementation: A failed callback does not roll back the initial token receipt. The resolver can refund only the available balance; forwarded or spent tokens and account unregistration can change the result. Inspect finalized transfer/refund/burn effects and reconcile the final balance. Support this route only with a reviewed receiving-contract routing and callback policy.

Failed transfer and call

Let’s try to send more tokens than the account has:
Let’s examine this response.
  • result » transaction_outcome » outcome » status » SuccessReceiptId is 83AdQ16bpAC7BEUyF7zoRsAgeNW7HHmjhZLvytEsrygo
  • Find the outcome whose id matches that receipt ID, then inspect outcome.status.Failure. A missing outcome is pending; array position 0 is not a reliable receipt identifier.