Skip to main content
The best practice for implementing a fungible token contract is to use the contract-tools crate. However, if you need to implement your own, be careful to avoid common errors when handling ft_on_transfer and ft_resolve_transfer.

ft_on_transfer: accept only expected tokens

ft_on_transfer executes on the receiving contract. Its predecessor_account_id is the FT contract that transferred the tokens; sender_id is the account that initiated ft_transfer_call.

The vulnerable pattern

This receiver credits every token contract that calls it:
Pseudocode: vulnerable flow
An attacker can call the receiver through an unexpected FT contract. If the application treats that deposit as its expected token, the attacker can obtain a benefit with the wrong asset.

The safe pattern

Check the predecessor before using the deposit. Return all of an unexpected token as unused so the FT contract refunds it to the sender:
Pseudocode: safe flow
If your application supports several tokens, use an explicit allowlist and apply the correct rules for each token.

ft_resolve_transfer: refund only transferred tokens

ft_resolve_transfer executes on the FT contract itself after ft_on_transfer finishes. It must be private. On failure, the original amount should be refundable; on success, ft_on_transfer returns how many tokens it did not use.

The vulnerable pattern

Do not trust the requested refund without a bound:
Pseudocode: vulnerable flow
If the receiver reports more unused tokens than the original transfer, this can refund tokens the sender never spent. If the receiver holds other tokens, those tokens could be taken as part of the refund.

The safe pattern

Cap the refund by the original amount and by the receiver’s current balance:
Pseudocode: safe flow
The returned value is the amount the sender actually spent. Prefer the resolver in near-contract-standards instead of implementing this accounting yourself.

Review before deployment

  • In ft_on_transfer, check that the predecessor is an allowed FT contract.
  • Return the unused amount: 0 when all tokens were used, or amount when none were used.
  • Keep ft_resolve_transfer private.
  • Refund no more than the original transfer and no more than the receiver still holds.
  • Test unexpected FT contracts, failed receiver calls, malformed return values, partial use, and a requested refund larger than the transfer.