Token Program
A Token program on the Renec blockchain.
This program defines a common implementation for Fungible and Non Fungible tokens.
#
SourceThe Token Program's source is available on github
#
InterfaceThe Token Program is written in Rust and available on crates.io and docs.rs.
Auto-generated C bindings are also available here
JavaScript bindings are available that support loading the Token Program on to a chain and issue instructions.
See the RPL Associated Token Account program for convention around wallet address to token account mapping and funding.
#
Command-line UtilityThe rpl-token
command-line utility can be used to experiment with RPL
tokens. Once you have Rust installed, run:
Run rpl-token --help
for a full description of available commands.
#
ConfigurationThe rpl-token
configuration is shared with the renec
command-line tool.
#
Current Configuration#
Cluster RPC URLSee Renec clusters for cluster-specific RPC URLs
#
Default KeypairSee Keypair conventions for information on how to setup a keypair if you don't already have one.
Keypair File
Hardware Wallet URL (See URL spec)
#
Airdrop RENECCreating tokens and accounts requires RENEC for account rent deposits and transaction fees. If the cluster you are targeting offers a faucet, you can get a little RENEC for testing:
#
Example: Creating your own fungible tokenThe unique identifier of the token is AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
.
Tokens when initially created by rpl-token
have no supply:
Let's mint some. First create an account to hold a balance of the new
AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
token:
7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
is now an empty account:
Mint 100 tokens into the account:
The token supply
and account balance
now reflect the result of minting:
#
Example: View all Tokens that you own#
Example: Wrapping RENEC in a TokenTo unwrap the Token back to RENEC:
#
Example: Transferring tokens to another userFirst the receiver uses rpl-token create-account
to create their associated
token account for the Token type. Then the receiver obtains their wallet
address by running renec address
and provides it to the sender.
The sender then runs:
#
Example: Transferring tokens to another user, with sender-fundingIf the receiver does not yet have an associated token account, the sender may choose to fund the receiver's account.
The receiver obtains their wallet address by running renec address
and provides it to the sender.
The sender then runs to fund the receiver's associated token account, at the sender's expense, and then transfers 50 tokens into it:
#
Example: Transferring tokens to an explicit recipient token accountTokens may be transferred to a specific recipient token account. The recipient token account must already exist and be of the same Token type.
#
Example: Create a non-fungible tokenCreate the token type with zero decimal place,
then create an account to hold tokens of this new type:
Now mint only one token into the account,
and disable future minting:
Now the 7KqpRwzkkeweW5jQoETyLzhvs9rcCj9dVQ1MnzudirsM
account holds the
one and only 559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z
token:
#
Multisig usageThe main difference in rpl-token
command line usage when referencing multisig
accounts is in specifying the --owner
argument. Typically the signer specified
by this argument directly provides a signature granting its authority, but in
the multisig case it just points to the address of the multisig account.
Signatures are then provided by the multisig signer-set members specified by the
--multisig-signer
argument.
Multisig accounts can be used for any authority on an RPL Token mint or token account.
- Mint account mint authority:
rpl-token mint ...
,rpl-token authorize ... mint ...
- Mint account freeze authority:
rpl-token freeze ...
,rpl-token thaw ...
,rpl-token authorize ... freeze ...
- Token account owner authority:
rpl-token transfer ...
,rpl-token approve ...
,rpl-token revoke ...
,rpl-token burn ...
,rpl-token wrap ...
,rpl-token unwrap ...
,rpl-token authorize ... owner ...
- Token account close authority:
rpl-token close ...
,rpl-token authorize ... close ...
#
Example: Mint with multisig authorityFirst create keypairs to act as the multisig signer-set. In reality, these can be any supported signer, like: a Ledger hardware wallet, a keypair file, or a paper wallet. For convenience, keypair files will be used in this example.
In order to create the multisig account, the public keys of the signer-set must be collected.
Now the multisig account can be created with the rpl-token create-multisig
subcommand. Its first positional argument is the minimum number of signers (M
)
that must sign a transaction affecting a token/mint account that is controlled
by this multisig account. The remaining positional arguments are the public keys
of all keypairs allowed (N
) to sign for the multisig account. This example
will use a "2 of 3" multisig account. That is, two of the three allowed keypairs
must sign all transactions.
NOTE: RPL Token Multisig accounts are limited to a signer-set of eleven signers
(1 <= N
<= 11) and minimum signers must be no more than N
(1 <= M
<= N
)
Next create the token mint and receiving accounts as previously described
Then set the mint account's minting authority to the multisig account
To demonstrate that the mint account is now under control of the multisig account, attempting to mint with one multisig signer fails
But repeating with a second multisig signer, succeeds
#
Example: Offline signing with multisigSometimes online signing is not possible or desireable. Such is the case for example when signers are not in the same geographic location
or when they use air-gapped devices not connected to the network. In this case, we use offline signing which combines the
previous examples of multisig with offline signing
and a nonce account.
This example will use the same mint account, token account, multisig account, and multisig signer-set keypair filenames as the online example, as well as a nonce account that we create here:
For the fee-payer and nonce-authority roles, a local hot wallet at
5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE
will be used.
First a template command is built by specifying all signers by their public key. Upon running this command, all signers will be listed as "Absent Signers" in the output. This command will be run by each offline signer to generate the corresponding signature.
NOTE: The argument to the --blockhash
parameter is the "Nonce blockhash:" field from
the designated durable nonce account.
Next each offline signer executes the template command, replacing each instance of their public key with the corresponding keypair.
Finally, the offline signers communicate the Pubkey=Signature
pair from the
output of their command to the party who will broadcast the transaction to the
cluster. The broadcasting party then runs the template command after modifying
it as follows:
- Replaces any corresponding public keys with their keypair (
--fee-payer ...
and--nonce-authority ...
in this example) - Removes the
--sign-only
argument, and in the case of themint
subcommand, the--mint-decimals ...
argument as it will be queried from the cluster - Adds the offline signatures to the template command via the
--signer
argument
#
JSON RPC methodsThere is a rich set of JSON RPC methods available for use with RPL Token:
getTokenAccountBalance
getTokenAccountsByDelegate
getTokenAccountsByOwner
getTokenLargestAccounts
getTokenSupply
See https://docs.renec.foundation/apps/jsonrpc-api for more details.
Additionally the versatile getProgramAccounts
JSON RPC method can be employed in various ways to fetch RPL Token accounts of interest.
#
Finding all token accounts for a specific mintTo find all token accounts for the TESTpKgj42ya3st2SQTKiANjTBmncQSCqLAZGcSPLGM
mint:
The "dataSize": 165
filter selects all Token
Accounts,
and then the "memcmp": ...
filter selects based on the
mint
address within each token account.
#
Finding all token accounts for a walletFind all token accounts owned by the vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
user:
The "dataSize": 165
filter selects all Token
Accounts,
and then the "memcmp": ...
filter selects based on the
owner
address within each token account.
#
Operational overview#
Creating a new token typeA new token type can be created by initializing a new Mint with the
InitializeMint
instruction. The Mint is used to create or "mint" new tokens,
and these tokens are stored in Accounts. A Mint is associated with each
Account, which means that the total supply of a particular token type is equal
to the balances of all the associated Accounts.
It's important to note that the InitializeMint
instruction does not require
the Renec account being initialized also be a signer. The InitializeMint
instruction should be atomically processed with the system instruction that
creates the Renec account by including both instructions in the same
transaction.
Once a Mint is initialized, the mint_authority
can create new tokens using the
MintTo
instruction. As long as a Mint contains a valid mint_authority
, the
Mint is considered to have a non-fixed supply, and the mint_authority
can
create new tokens with the MintTo
instruction at any time. The SetAuthority
instruction can be used to irreversibly set the Mint's authority to None
,
rendering the Mint's supply fixed. No further tokens can ever be Minted.
Token supply can be reduced at any time by issuing a Burn
instruction which
removes and discards tokens from an Account.
#
Creating accountsAccounts hold token balances and are created using the InitializeAccount
instruction. Each Account has an owner who must be present as a signer in some
instructions.
An Account's owner may transfer ownership of an account to another using the
SetAuthority
instruction.
It's important to note that the InitializeAccount
instruction does not require
the Renec account being initialized also be a signer. The InitializeAccount
instruction should be atomically processed with the system instruction that
creates the Renec account by including both instructions in the same
transaction.
#
Transferring tokensBalances can be transferred between Accounts using the Transfer
instruction.
The owner of the source Account must be present as a signer in the Transfer
instruction when the source and destination accounts are different.
It's important to note that when the source and destination of a Transfer
are
the same, the Transfer
will always succeed. Therefore, a successful Transfer
does not necessarily imply that the involved Accounts were valid RPL Token
accounts, that any tokens were moved, or that the source Account was present as
a signer. We strongly recommend that developers are careful about checking that
the source and destination are different before invoking a Transfer
instruction from within their program.
#
BurningThe Burn
instruction decreases an Account's token balance without transferring
to another Account, effectively removing the token from circulation permanently.
There is no other way to reduce supply on chain. This is similar to transferring
to an account with unknown private key or destroying a private key. But the act
of burning by using Burn
instructions is more explicit and can be confirmed on
chain by any parties.
#
Authority delegationAccount owners may delegate authority over some or all of their token balance
using the Approve
instruction. Delegated authorities may transfer or burn up
to the amount they've been delegated. Authority delegation may be revoked by the
Account's owner via the Revoke
instruction.
#
MultisignaturesM of N multisignatures are supported and can be used in place of Mint
authorities or Account owners or delegates. Multisignature authorities must be
initialized with the InitializeMultisig
instruction. Initialization specifies
the set of N public keys that are valid and the number M of those N that must be
present as instruction signers for the authority to be legitimate.
It's important to note that the InitializeMultisig
instruction does not
require the Renec account being initialized also be a signer. The
InitializeMultisig
instruction should be atomically processed with the system
instruction that creates the Renec account by including both instructions in
the same transaction.
#
Freezing accountsThe Mint may also contain a freeze_authority
which can be used to issue
FreezeAccount
instructions that will render an Account unusable. Token
instructions that include a frozen account will fail until the Account is thawed
using the ThawAccount
instruction. The SetAuthority
instruction can be used
to change a Mint's freeze_authority
. If a Mint's freeze_authority
is set to
None
then account freezing and thawing is permanently disabled and all
currently frozen accounts will also stay frozen permanently.
#
Wrapping RENECThe Token Program can be used to wrap native RENEC. Doing so allows native RENEC to be treated like any other Token program token type and can be useful when being called from other programs that interact with the Token Program's interface.
Accounts containing wrapped RENEC are associated with a specific Mint called the
"Native Mint" using the public key
So11111111111111111111111111111111111111112
.
These accounts have a few unique behaviors
InitializeAccount
sets the balance of the initialized Account to the RENEC balance of the Renec account being initialized, resulting in a token balance equal to the RENEC balance.- Transfers to and from not only modify the token balance but also transfer an equal amount of RENEC from the source account to the destination account.
- Burning is not supported
- When closing an Account the balance may be non-zero.
The Native Mint supply will always report 0, regardless of how much RENEC is currently wrapped.
#
Rent-exemptionTo ensure a reliable calculation of supply, a consistency valid Mint, and consistently valid Multisig accounts all Renec accounts holding an Account, Mint, or Multisig must contain enough RENEC to be considered rent exempt
#
Closing accountsAn account may be closed using the CloseAccount
instruction. When closing an
Account, all remaining RENEC will be transferred to another Renec account
(doesn't have to be associated with the Token Program). Non-native Accounts must
have a balance of zero to be closed.
#
Non-Fungible tokensAn NFT is simply a token type where only a single token has been minted.
#
Wallet Integration GuideThis section describes how to integrate RPL Token support into an existing wallet supporting native RENEC. It assumes a model whereby the user has a single system account as their main wallet address that they send and receive RENEC from.
Although all RPL Token accounts do have their own address on-chain, there's no need to surface these additional addresses to the user.
There are two programs that are used by the wallet:
- RPL Token program: generic program that is used by all RPL Tokens
- RPL Associated Token Account program: defines the convention and provides the mechanism for mapping the user's wallet address to the associated token accounts they hold.
#
How to fetch and display token holdingsThe getTokenAccountsByOwner JSON RPC method can be used to fetch all token accounts for a wallet address.
For each token mint, the wallet could have multiple token accounts: the associated token account and/or other ancillary token accounts
By convention it is suggested that wallets roll up the balances from all token accounts of the same token mint into a single balance for the user to shield the user from this complexity.
See the Garbage Collecting Ancillary Token Accounts section for suggestions on how the wallet should clean up ancillary token accounts on the user's behalf.
#
Associated Token AccountBefore the user can receive tokens, their associated token account must be created on-chain, requiring a small amount of RENEC to mark the account as rent-exempt.
There's no restriction on who can create a user's associated token account. It could either be created by the wallet on behalf of the user or funded by a 3rd party through an airdrop campaign.
The creation process is described here.
It's highly recommended that the wallet create the associated token account for a given RPL Token itself before indicating to the user that they are able to receive that RPL Tokens type (typically done by showing the user their receiving address). A wallet that chooses to not perform this step may limit its user's ability to receive RPL Tokens from other wallets.
#
Sample "Add Token" workflowThe user should first fund their associated token account when they want to receive RPL Tokens of a certain type to:
- Maximize interoperability with other wallet implementations
- Avoid pushing the cost of creating their associated token account on the first sender
The wallet should provide a UI that allow the users to "add a token". The user selects the kind of token, and is presented with information about how much RENEC it will cost to add the token.
Upon confirmation, the wallet creates the associated token type as the described here.
#
Sample "Airdrop campaign" workflowFor each recipient wallet addresses, send a transaction containing:
- Create the associated token account on the recipient's behalf.
- Use
TokenInstruction::Transfer
to complete the transfer
#
Associated Token Account Ownership鈿狅笍 The wallet should never use TokenInstruction::SetAuthority
to set the
AccountOwner
authority of the associated token account to another address.
#
Ancillary Token AccountsAt any time ownership of an existing RPL Token account may be assigned to the
user. One way to accomplish this is with the
rpl-token authorize <TOKEN_ADDRESS> owner <USER_ADDRESS>
command. Wallets
should be prepared to gracefully manage token accounts that they themselves did
not create for the user.
#
Transferring Tokens Between WalletsThe preferred method of transferring tokens between wallets is to transfer into associated token account of the recipient.
The recipient must provide their main wallet address to the sender. The sender then:
- Derives the associated token account for the recipient
- Fetches the recipient's associated token account over RPC and checks that it exists
- If the recipient's associated token account does not yet exist, the sender wallet should create the recipient's associated token account as described here. The sender's wallet may choose to inform the user that as a result of account creation the transfer will require more RENEC than normal. However a wallet that chooses to not support creating the recipient's associated token account at this time should present a message to the user with enough information to permit them to find a workaround (such as transferring the token through a fully compliant intermediary wallet such as https://sollet.io) to allow the users to accomplish their goal
- Use
TokenInstruction::Transfer
to complete the transfer
The sender's wallet must not require that the recipient's main wallet address hold a balance before allowing the transfer.
#
Registry for token detailsAt the moment there exist two solutions for Token Mint registries:
- hard coded addresses in the wallet or dapp
- rpl-token-registry package, maintained at https://github.com/renec-labs/token-list
A decentralized solution is in progress.
#
Garbage Collecting Ancillary Token AccountsWallets should empty ancillary token accounts as quickly as practical by transferring into the user's associated token account. This effort serves two purposes:
- If the user is the close authority for the ancillary account, the wallet can reclaim RENEC for the user by closing the account.
- If the ancillary account was funded by a 3rd party, once the account is emptied that 3rd party may close the account and reclaim the RENEC.
One natural time to garbage collect ancillary token accounts is when the user next sends tokens. The additional instructions to do so can be added to the existing transaction, and will not require an additional fee.
Cleanup Pseudo Steps:
- For all non-empty ancillary token accounts, add a
TokenInstruction::Transfer
instruction to the transfer the full token amount to the user's associated token account. - For all empty ancillary token accounts where the user is the close authority,
add a
TokenInstruction::CloseAccount
instruction
If adding one or more of clean up instructions cause the transaction to exceed the maximum allowed transaction size, remove those extra clean up instructions. They can be cleaned up during the next send operation.
The rpl-token gc
command provides an example implementation of this cleanup process.
#
Token Vesting Contract:This program allows you to lock arbitrary RPL tokens and release the locked tokens with a determined unlock schedule. An unlock schedule
is made of a unix timestamp
and a token amount
, when initializing a vesting contract, the creator can pass an array of unlock schedule
with an arbitrary size giving the creator of the contract complete control of how the tokens unlock over time.
Unlocking works by pushing a permissionless crank on the contract that moves the tokens to the pre-specified address. The recipient address of a vesting contract can be modified by the owner of the current recipient key, meaning that vesting contract locked tokens can be traded.
- Code: https://github.com/Bonfida/token-vesting
- UI: https://vesting.bonfida.com/#/
- Audit: The audit was conducted by Kudelski, the report can be found here
#
Register token infoBy default, your created token will display on Renec Explorer and Renec Wallet without detailed info, so you have to create a Pull Request to our repository https://github.com/remitano/rpl-token-registry, for example, add to file src/tokens/renec.tokenlist.json