본문으로 건너뛰기

Fungible Tokens (FT)

Besides the native NEAR token, NEAR accounts have access to a multitude of tokens to use thoughtout the ecosystem. Moreover, it is even possible for users to create their own fungible tokens.

In contrast with the NEAR native token, fungible token (FT) are not stored in the user's account. In fact, each FT lives in their own contract which is in charge of doing bookkeeping. This is, the contract keeps track of how many tokens each user has, and handles transfers internally.

FT

In order for a contract to be considered a FT-contract it has to follow the NEP-141 and NEP-148 standards. The NEP-141 & NEP-148 standards explain the minimum interface required to be implemented, as well as the expected functionality.


Token Factory

You can create an FT using the community tool Token Farm. Token farm is a token factory, you can interact with it through its graphical interface, or by making calls to its contract.

const args = {
args: {
owner_id: "bob.near",
total_supply: "1000000000",
metadata: {
spec: "ft-1.0.0",
name: "Test Token",
symbol: "test",
icon: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
decimals: 18,
},
},
account_id: "bob.near",
};

Near.call("tkn.near", "create_token", args, 300000000000000, "2234830000000000000000000");

The FT you create will live in the account <your_token_symbol>.tkn.near (e.g. test.tkn.near).


Deploying Your Own Contract

You can also create a fungible token by deploying and initializing a canonical FT contract.

On initialization you will define the token's metadata such as its name (e.g. Ethereum), symbol (e.g. ETH) and total supply (e.g. 10M). You will also define an owner, which will own the tokens total supply.

To initialize a FT contract you will need to deploy it and then call the new method defining the token's metadata.

near deploy <account-id> --wasmFile fungible_token.wasm

near call <account-id> new '{"owner_id": "<owner-account>", "total_supply": "1000000000000000", "metadata": { "spec": "ft-1.0.0", "name": "Example Token Name", "symbol": "EXLT", "decimals": 8 }}' --accountId <account-id>

Check the Contract Wizard to create a personalized FT contract!.


Querying Metadata

You can query the FT's metadata by calling the ft_metadata.

const tokenContract = "token.v2.ref-finance.near";
const tokenMetadata = Near.view(tokenContract, "ft_metadata", {});
Example response

{
"spec": "ft-1.0.0",
"name": "Ref Finance Token",
"symbol": "REF",
"icon": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='16 24 248 248' style='background: %23000'%3E%3Cpath d='M164,164v52h52Zm-45-45,20.4,20.4,20.6-20.6V81H119Zm0,18.39V216h41V137.19l-20.6,20.6ZM166.5,81H164v33.81l26.16-26.17A40.29,40.29,0,0,0,166.5,81ZM72,153.19V216h43V133.4l-11.6-11.61Zm0-18.38,31.4-31.4L115,115V81H72ZM207,121.5h0a40.29,40.29,0,0,0-7.64-23.66L164,133.19V162h2.5A40.5,40.5,0,0,0,207,121.5Z' fill='%23fff'/%3E%3Cpath d='M189 72l27 27V72h-27z' fill='%2300c08b'/%3E%3C/svg%3E%0A",
"reference": null,
"reference_hash": null,
"decimals": 18
}


Checking Balance

To know how many coins a user has you will need to query the method ft_balance_of.

정보

Remember about fungible token precision. You may need this value to show a response of balance requests in an understandable-to-user way in your app. How to get precision value (decimals) you may find above.

const tokenContract = "token.v2.ref-finance.near";
const userTokenBalance = Near.view(tokenContract, "ft_balance_of", {
account_id: "bob.near",
});
Example response

"3479615037675962643842"


Registering a User

In order for an user to own and transfer tokens they need to first register in the contract. This is done by calling storage_deposit and attaching 0.00125Ⓝ.

By calling this storage_deposit the user can register themselves or register other users.

Near.call(
tokenContract,
"storage_deposit",
{ account_id: "alice.near" },
undefined,
1250000000000000000000
);
정보

You can make sure a user is registered by calling storage_balance_of.

After a user calls the storage_deposit the FT will appear in their Wallets.


Transferring Tokens

To send FT to another account you will use the ft_transfer method, indicating the receiver and the amount of FT you want to send.

const tokenContract = "token.v2.ref-finance.near";
Near.call(
tokenContract,
"ft_transfer",
{
receiver_id: "alice.near",
amount: "100000000000000000",
},
undefined,
1
);

Attaching FTs to a Call

Natively, only NEAR tokens (Ⓝ) can be attached to a function calls. However, the FT standard enables to attach fungible tokens in a call by using the FT-contract as intermediary. This means that, instead of you attaching tokens directly to the call, you ask the FT-contract to do both a transfer and a function call in your name.

Let's assume that you need to deposit FTs on Ref Finance.

const tokenContract = "token.v2.ref-finance.near";
const result = Near.call(
tokenContract,
"ft_transfer_call",
{
receiver_id: "v2.ref-finance.near",
amount: "100000000000000000",
msg: "",
},
300000000000000,
1
);
Example response

'100000000000000000'

How it works:

  1. You call ft_transfer_call in the FT contract passing: the receiver, a message, and the amount.
  2. The FT contract transfers the amount to the receiver.
  3. The FT contract calls receiver.ft_on_transfer(sender, msg, amount)
  4. The FT contract handles errors in the ft_resolve_transfer callback.
  5. The FT contract returns you how much of the attached amount was actually used.

Handling Deposits (Contract Only)

If you want your contract to handle deposit in FTs you have to implement the ft_on_transfer method. When executed, such method will know:

  • Which FT was transferred, since it is the predecessor account.
  • Who is sending the FT, since it is a parameter
  • How many FT were transferred, since it is a parameter
  • If there are any parameters encoded as a message

The ft_on_transfer must return how many FT tokens have to be refunded, so the FT contract gives them back to the sender.

// Implement the contract structure
#[near(contract_state)]
impl Contract {}

#[near]
impl FungibleTokenReceiver for Contract {
// Callback on receiving tokens by this contract.
// `msg` format is either "" for deposit or `TokenReceiverMessage`.
fn ft_on_transfer(
&mut self,
sender_id: AccountId,
amount: U128,
msg: String,
) -> PromiseOrValue<U128> {
let token_in = env::predecessor_account_id();

assert!(token_in == self.ft_contract, "{}", "The token is not supported");
assert!(amount >= self.price, "{}", "The attached amount is not enough");

env::log_str(format!("Sender id: {:?}", sender_id).as_str());

if msg.is_empty() {
// Your internal logic here
PromiseOrValue::Value(U128(0))
} else {
let message =
serde_json::from_str::<TokenReceiverMessage>(&msg).expect("WRONG_MSG_FORMAT");
match message {
TokenReceiverMessage::Action {
buyer_id,
} => {
let buyer_id = buyer_id.map(|x| x.to_string());
env::log_str(format!("Target buyer id: {:?}", buyer_id).as_str());
// Your internal business logic
PromiseOrValue::Value(U128(0))
}
}
}
}
}

Additional Resources

  1. NEP-141 and NEP-148 standards
  2. FT Event Standards
  3. FT reference implementation
  4. Fungible Tokens 101 - a set of tutorials that cover how to create a FT contract using Rust.
Was this page helpful?