NEP-245: Multi token
NEP-245 multi token standard on NEAR: many FT and NFT types in one contract, mt_transfer, batch transfers, mt_transfer_call, ERC-1155 and NEAR Intents.
Intermediate6 min read3-question check
NEP-245 lets one contract manage many token types at once — each identified by a string token_id, each with its own balances. A token ID with a supply of one behaves like an NFT; one with a large supply behaves like a fungible token. It is NEAR’s counterpart to ERC-1155 and is the natural fit for game items, editions, and any system that wraps many assets.
Its most prominent user today is NEAR Intents: the intents contract tracks users’ deposited assets as multi-token balances, with token IDs that name the underlying asset (for example nep141:wrap.near for wNEAR deposited from its NEP-141 contract).
Core methods#
| Method | Kind | What it does | Deposit |
|---|---|---|---|
mt_transfer(receiver_id, token_id, amount, approval?, memo?) | change | Move amount of one token ID. | exactly 1 yoctoNEAR |
mt_batch_transfer(receiver_id, token_ids, amounts, approvals?, memo?) | change | Move several token IDs to one receiver in one call. | exactly 1 yoctoNEAR |
mt_transfer_call(receiver_id, token_id, amount, approval?, memo?, msg) | change | Transfer, then call receiver_id.mt_on_transfer(...); unused amounts are refunded. | exactly 1 yoctoNEAR |
mt_batch_transfer_call(receiver_id, token_ids, amounts, approvals?, memo?, msg) | change | Batch version of the above. | exactly 1 yoctoNEAR |
mt_balance_of(account_id, token_id) / mt_batch_balance_of(account_id, token_ids) | view | Balance(s) as strings. | — |
mt_supply(token_id) / mt_batch_supply(token_ids) | view | Total supply per token ID (or null if unknown). | — |
mt_token(token_ids) | view | Token objects (token_id, owner_id — owner is meaningful for NFT-like IDs). | — |
mt_on_transfer(sender_id, previous_owner_ids, token_ids, amounts, msg) | change (on the receiver) | Returns, per token, the amount to refund. | — |
The spec also defines optional extensions — metadata, approval management and enumeration — following the same pattern as the NFT family. The approval argument is a tuple [owner_id, approval_id]: an approved account names whose tokens it is moving and the approval ID it was granted. Events (mt_mint, mt_transfer, mt_burn) use the NEP-297 format with standard nep245.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IERC1155 {
function balanceOf(address account, uint256 id) external view returns (uint256);
function safeTransferFrom(
address from, address to, uint256 id, uint256 amount, bytes calldata data
) external;
function safeBatchTransferFrom(
address from, address to,
uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data
) external;
function setApprovalForAll(address operator, bool approved) external;
}
// receiver hook: return a magic value to ACCEPT
interface IERC1155Receiver {
function onERC1155Received(
address operator, address from, uint256 id, uint256 value, bytes calldata data
) external returns (bytes4);
}use near_sdk::json_types::U128;
use near_sdk::{env, near, require, AccountId, PanicOnDefault, PromiseOrValue};
// A contract that RECEIVES multi tokens via mt_transfer_call.
#[near(contract_state)]
#[derive(PanicOnDefault)]
pub struct Vault {
mt_contract: AccountId,
}
#[near]
impl Vault {
// NEP-245 receiver hook: return the amount to refund, per token
pub fn mt_on_transfer(
&mut self,
sender_id: AccountId,
previous_owner_ids: Vec<AccountId>,
token_ids: Vec<String>,
amounts: Vec<U128>,
msg: String,
) -> PromiseOrValue<Vec<U128>> {
let _ = (sender_id, previous_owner_ids, msg);
require!(env::predecessor_account_id() == self.mt_contract, "unknown MT contract");
require!(token_ids.len() == amounts.len(), "length mismatch");
// ...credit the deposits...
// keep everything: refund 0 of each token
PromiseOrValue::Value(vec![U128(0); amounts.len()])
}
}safeTransferFrom(from, to, id, amount, data) ↔ mt_transfer_call(receiver_id, token_id, amount, approval, memo, msg). There is no from: the sender is the caller, or the owner named in the approval tuple.
ERC-1155 receivers return a selector to accept; NEP-245 receivers return refund amounts, like NEP-141’s ft_on_transfer. Token IDs are strings, so nep141:wrap.near-style IDs are possible.
Check yourself
3 questions · progress saved in this browser