The EOS Network emits a fixed percentage of the out of circulation supply of EOS to various entities (miners, foundations, staking rewards, etc.) on the network constantly. The network has a built-in mechanism to distribute these rewards to the stakers of EOS. The staking rewards are distributed to the stakers of EOS in the form of REX tokens.
REX tokens are a representation of the amount of EOS that a user has staked on the network. REX tokens cannot be transferred to other users, but they can be sold back to the network for EOS.
As the network rewards are distributed to the REX staking pool, the price of REX tokens increases. This means that the conversion rate of EOS tokens to REX increases over time and never decreases.
The amount of REX tokens a user holds does not change, but the value of the REX tokens increases over time. This means that the user can always sell their REX tokens back to the network for more EOS than they initially staked.
Every 4 years the amount of tokens that is distributed to the various entities is halved. The current rate of
emission for staking rewards is 31,250,000 tokens per year. You can see in the chart below that tokens first flow
through the eosio.saving
account, then to the eosio.reward
account, and finally to the eosio.rex
account.
graph TD
eosio --> |EOS emissions | eosio.saving
eosio.saving --> |31,250,000 EOS per year| eosio.reward
eosio.reward --> weights_rewards{eosio.rex}
The contract that handles the staking rewards is the eosio
system contract.
Note: The
eosio
contract will soon be wrapped with another contract for a rebrand. Once this happens you can simply change your code to use the new contract account name, and change the ticker fromEOS
to the new name. All other parameters will stay the same.
The following actions are available on the eosio
contract:
Staking requires you to deposit EOS into the contract, and then to buy REX with that deposited EOS.
void deposit( const name& owner, const asset& amount );
void buyrex( const name& from, const asset& amount );
You need to complete both of these actions, and you can do them both within the same transaction.
Example transaction: https://unicove.com/en/eos/transaction/104910588aeaa372d4b2511c630aec316b5d85fa8940f286671bf12564ffda84
Note: This guide uses the WharfKit SDK to sign transactions. If you do not know how to set up WharfKit, please refer to the WharfKit documentation.
const result = session.transact({
actions: [
{
account: "eosio",
name: "deposit",
authorization: [session.permissionLevel],
data: {
owner: session.actor,
amount: "100.0000 EOS"
}
},
{
account: "eosio",
name: "buyrex",
authorization: [session.permissionLevel],
data: {
from: session.actor,
amount: "100.0000 EOS"
}
}
]
});
Unstaking will start the timer that matures your staked position. Until you trigger unstaking, your position is staked perpetually.
void mvfrsavings( const name& owner, const asset& rex );
Example transaction: https://unicove.com/en/eos/transaction/2feff4c6d76bbfa6abc5ee3d92a07097e3568aa9a61fa64d04147f824a4c9ae5
session.transact({
actions: [
{
account: "eosio",
name: "mvfrsavings",
authorization: [session.permissionLevel],
data: {
owner: session.actor,
rex: `100.0000 REX`
}
}
]
});
This will trigger a 21-day maturation period. The user still accumulates rewards during this period.
Once the 21-day maturation period is over, the user can then claim their rewards. They will first need to sell their rex position, and then withdraw the EOS.
void sellrex( const name& from, const asset& rex );
void withdraw( const name& owner, const asset& amount );
One point of note is that you have to specify the EOS amount
you want to withdraw, which you won't know until you
sell your REX position. You can calculate this, but it will not be readily available without calculation as a stored number
because the conversion rate potentially changes every block.
Example transaction: https://unicove.com/en/eos/transaction/892eed7631a33ceb4b93d99ad0dd8d06d586ed766dd0b99ecaa3a041dc16cc52
session.transact({
actions: [
{
account: "eosio",
name: "sellrex",
authorization: [session.permissionLevel],
data: {
from: session.actor,
rex: `100.0000 REX`
}
},
{
account: "eosio",
name: "withdraw",
authorization: [session.permissionLevel],
data: {
owner: session.actor,
amount: `100.0000 EOS`
}
}
]
});
void donatetorex( const name& payer, const asset& quantity, const std::string& memo );
This action allows any user to donate EOS to the REX pool. The donation pushes up the price of REX tokens, which benefits all REX holders. The user can specify the amount of EOS they want to donate and a memo.
This action is used by the system to donate REX as well, which makes all on-chain actions transparent.
Example transaction: https://unicove.com/en/eos/transaction/be4bd8a8a5c1172d2776616a4367c58b4974e1d29d1fe68bbbd2002dfe209965/traces
This wouldn't be an action you as an integrator needs.
You will need to fetch data from the chain in order to know how much REX or EOS the user has, and also to calculate various things like REX/EOS conversion rates, APR, etc.
You can fetch data from the chain using the get_table_rows
RPC call. All data will exist on the eosio
contract,
or the token contract.
I will be using JavaScript's fetch in the examples below, but you can use any HTTP client you prefer to fetch data, and if you're using JavaScript, I would recommend using a library like WharfKit.
REX pool data is stored in the rexpool
table. It includes information you need to calculate the REX/EOS conversion rate.
const response = await fetch('https://eos.greymass.com/v1/chain/get_table_rows', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
code: 'eosio',
scope: 'eosio',
table: 'rexpool',
json: true,
}),
}).then(x => x.json());
// Result:
// {
// rows: [
// {
// version: 0,
// total_lent: "24743362.9593 EOS",
// total_unlent: "148138217.9863 EOS",
// total_rent: "4118.5517 EOS",
// total_lendable: "172881580.9456 EOS",
// total_rex: "1389119024325.2628 REX",
// namebid_proceeds: "0.0000 EOS",
// loan_num: 505265,
// }
// ],
// more: false,
// next_key: "",
// }
You can also use WharfKit to get the data instead of fetch:
const response = await session.client.v1.chain.get_table_rows({
code: 'eosio',
scope: 'eosio',
table: 'rexpool',
json: true,
});
Note: From here on out, I will just show the body JSON object that you need to send to the
get_table_rows
RPC call.
The user's REX balance is stored in the rexbal
table.
You will need to specify the user's account name in both the lower_bound
and upper_bound
fields.
{
"code": "eosio",
"scope": "eosio",
"lower_bound": "someaccount",
"upper_bound": "someaccount",
"table": "rexbal",
"json": true
}
// Result:
//{
// "rows": [
// {
// "version": 0,
// "owner": "someaccount",
// "vote_stake": "100.0000 EOS",
// "rex_balance": "100000.0000 REX",
// "matured_rex": 0,
// "rex_maturities": [
// {
// "first": "2024-07-29T00:00:00",
// "second": "100000000"
// },
// {
// "first": "2106-02-07T06:28:15",
// "second": 10000
// }
// ]
// }
// ],
// "more": false,
// "next_key": ""
//}
You'll notice that there are two rex_maturities
objects. One object will have a first
value that is very far in
the future. This is the user's indefinitely staked REX position. Any other rex_maturities
object is a maturing REX
position, and if it is in the past, then it has passed the required maturation period and can be sold and claimed.
The user might also have a matured_rex
balance, which is another amount of REX that has matured and can be claimed.
To calculate the REX <> EOS conversion rate, you will need to use the rexpool
data.
function convertRexToEos(rex:number){
const pool = ... // get the pool here.
const S0 = parseFloat(pool.total_lendable.split(' ')[0]);
const R0 = parseFloat(pool.total_rex.split(' ')[0]);
const R1 = R0 + rex;
const S1 = (S0 * R1) / R0;
return parseFloat(parseFloat((S1 - S0).toString()).toFixed(4));
}
Note: You will notice the split function in the code above. This is because the
total_lendable
andtotal_rex
fields are strings that contain the amount and the token symbol. You will need to split the string to get the amount.
To do the reverse of the above, you will need to use the rexpool
data again.
function convertEosToRex(eos:number){
const pool = ... // get the pool here.
const S0 = parseFloat(pool.total_lendable.split(' ')[0]);
const S1 = S0 + eos;
const R0 = parseFloat(pool.total_rex.split(' ')[0]);
const R1 = (S1 * R0) / S0;
return parseFloat(parseFloat((R1 - R0).toString()).toFixed(4));
}
By far the easiest way to calculate the APR (since we know the static inflow amounts) is to take the annual reward, and the total amount of staked EOS and then calculate the percentage.
function calculateAPR(){
const pool = ... // get the pool here.
const annualReward = 31250000;
const totalStaked = parseInt(pool.total_lendable.split(' ')[0]);
return parseFloat(((annualReward / totalStaked) * 100).toString()).toFixed(2);
}
This will give you a fixed annual percentage rate. You can also calculate the daily rate by dividing the annual rate by 365.