Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Follow along with this guide and leverage your Bitcoin by minting tBTC.
Here are some things you will need before you start the minting process:
✅ Bitcoin (BTC)
✅ Ethereum (ETH) for Ethereum transaction gas costs
✅ Bitcoin compatible wallet
✅ Ethereum compatible wallet
If you don't already have one, you'll need to set up a Bitcoin wallet. A solid option to use is Green Wallet. You can download it here:
After you've created your wallet, select Bitcoin
as your network.
🎉 You're ready to mint! Go to the tBTC dapp here: https://dashboard.threshold.network/tBTC/mint
Connect your preferred Ethereum wallet to the tBTC dapp by selecting Connect Wallet.
Navigate to the tBTC Bridge page. This is represented in the lefthand navigation by the tBTC icon.
Enter a Bitcoin wallet address as the BTC Recovery Address. Click Generate Deposit Address to create your unique deposit address.
Make sure to download the JSON file by clicking Download. The JSON file contains a wallet public key, a refund public key, and a refund lock time. You need to keep this JSON file until you receive your tBTC tokens.
In your Bitcoin wallet, take a picture of the QR code of the generated Deposit Address in the tBTC dapp. It will look something like this:
Send your BTC deposit to this Deposit Address from your Bitcoin wallet. The minimum deposit is 0.01 BTC.
Return to the tBTC dapp. After your funds are sent, the screen will automatically advance to Step 3.
To initiate minting, click on Confirm deposit & mint.
Sign the transaction in your Ethereum wallet.
Your mint is now in progress! You don't need to remain in the dapp to wait for your tBTC tokens to mint. You can see the status of your mint in the dapp. The first stage requires 1 confirmation on the Bitcoin Network before advancing to the next step.
Now you will see the following while waiting for a Minter to assess the minting initialization:
Next, you will see the following while waiting for a Guardian to examine the minting request:
Success! You can see all transaction links on this screen.
Make sure to add the tBTC token address to your Ethereum wallet by clicking token address.
🎉 You've successfully minted tBTC! 🎉
Threshold Network powers tBTC, the Bitcoin standard for DeFi—a trust-minimized way to bring BTC liquidity into the crypto ecosystem while always maintaining a direct settlement path back to native Bitcoin.
Unlike other BTC derivatives or wrapped tokens, tBTC is designed to be as close to Bitcoin as possible while enabling seamless participation in DeFi. No complex staking mechanics, no dependency on a single entity—just pure BTC liquidity that always settles back to Bitcoin.
Threshold Network's powers tBTC and is the value accrual asset used to distribute profits from bridge fee via token buybacks.
Learn the fundamentals of the Threshold Network to get a deeper understanding of our main features:
Advanced configuration options and tBTC v2 staking client options
Here you'll find the version of the Threshold dapp.
To get familiarized with API of contracts related to the DAO, go .
Threshold USD (thUSD) is a stablecoin soft-pegged against USD, backed by ETH and tBTC as collateral. For more details, refer to the THUSD overview.
With thUSD, you can now:
Open collateral vaults seamlessly on BOB Network using bridged tBTC or ETH
Mint thUSD and utilize it across the BOB Network ecosystem
Start by connecting your preferred wallet (e.g., MetaMask) at app.gobob.xyz/wallet and bridging your tBTC at app.gobob.xyz/bridge.
Access Wallet Setup
Navigate to app.gobob.xyz/wallet.
Connect Your Wallet
Choose your preferred wallet (e.g., MetaMask) and follow the prompts to connect it to the BOB Network.
Switch to BOB Network
Click on the option "Switch to BOB." This will automatically add the BOB Network to your wallet and connect your wallet to the BOB Network.
Manual Setup
Alternatively, you can manually set up your wallet with the following settings:
Chain ID: 60808
Gas Token: ETH
RPC URL:
https://rpc.gobob.xyz/
WS URL:
wss://rpc.gobob.xyz
Explorer:
https://explorer.gobob.xyz/
Prepare your machine to install the tBTC v2 staking client
The client requires an Ethereum Key File of an Operator Account to connect to the Ethereum chain. This account is created in a subsequent step using Geth (GoEthereum).
The Ethereum Key File is expected to be encrypted with a password. The password has to be provided in a prompt after the client starts or configured as a KEEP_ETHEREUM_PASSWORD
environment variable.
The Operator Account has to maintain a positive Ether balance at all times. We strongly advise you monitor the account and top-up when its balance gets below 0.5 Ether.
Please do NOT reuse an operator account that is being used for TACo or other applications.
To create a new Ethereum account, install Geth (GoEthereum) and create a new account using the command below. This account will subsequently be referred to as the Operator Account.
geth account new --keystore /home/$USER/.operator-key
When prompted, provide a password to protect the operator key file.
Avoid passwords that contain the following characters: ', ", `, $ These characters may be interpreted as part of the configuration which can lead to undesirable outcomes that may be extremely time intensive to correct.
Once the process completes, your public key will be displayed. Take note of your Operator Account public key.
DO NOT LOSE THE PASSWORD TO THE OPERATOR ACCOUNT.
Your Operator Account will need to maintain a positive ETH balance at all times to ensure proper operation and availability of your tBTC v2 node.
A tBTC v2 node can be set up using either a docker or binary installation.
Optional logging configuration for the tBTC v2 staking client.
bytes32 VETO_POWER
constructor(contract T _token, contract IVotesHistory _staking, contract TimelockController _timelock, address _vetoer, uint256 _quorumNumerator, uint256 _proposalThresholdNumerator, uint256 votingDelay, uint256 votingPeriod, uint64 votingExtension) public
function cancel(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) external returns (uint256)
function propose(address[] targets, uint256[] values, bytes[] calldatas, string description) public returns (uint256)
function quorum(uint256 blockNumber) public view returns (uint256)
function proposalThreshold() public view returns (uint256)
function getVotes(address account, uint256 blockNumber) public view returns (uint256)
function state(uint256 proposalId) public view returns (enum IGovernor.ProposalState)
function supportsInterface(bytes4 interfaceId) public view returns (bool)
function _execute(uint256 proposalId, address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) internal
function _cancel(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) internal returns (uint256)
function _executor() internal view returns (address)
function proposalDeadline(uint256 proposalId) public view virtual returns (uint256)
function _castVote(uint256 proposalId, address account, uint8 support, string reason) internal virtual returns (uint256)
A wrapper around OpenZeppelin's SafeERC20Upgradeable
but specific to the T token. Use this library in upgradeable contracts. If your contract is non-upgradeable, then the traditional SafeERC20
works. The motivation is to prevent upgradeable contracts that use T from depending on the Address
library, which can be problematic since it uses delegatecall
, which is discouraged by OpenZeppelin for use in upgradeable contracts.
This implementation force-casts T to IERC20Upgradeable
to make it work with SafeERC20Upgradeable
.
function safeTransfer(contract T token, address to, uint256 value) internal
function safeTransferFrom(contract T token, address from, address to, uint256 value) internal
bytes32 VETO_POWER
address manager
constructor(contract IVotesHistory _staking, contract TimelockController _timelock, contract TokenholderGovernor tokenholderGovernor, address vetoer) public
function cancel(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) external returns (uint256)
function propose(address[] targets, uint256[] values, bytes[] calldatas, string description) public returns (uint256)
function quorum(uint256 blockNumber) public view returns (uint256)
function proposalThreshold() public view returns (uint256)
function getVotes(address account, uint256 blockNumber) public view returns (uint256)
function state(uint256 proposalId) public view returns (enum IGovernor.ProposalState)
function supportsInterface(bytes4 interfaceId) public view returns (bool)
function _execute(uint256 proposalId, address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) internal
function _cancel(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) internal returns (uint256)
function _executor() internal view returns (address)
Returns the address of the entity that acts as governance for this contract.
By default, Governor assumes this is either the Governor contract itself, or a timelock if there's one configured. We override this here for the StakerGovernor contract so it's the Tokenholder DAO's Timelock, which we obtain at constructor time.
To get familiarized with Random Bitcoin contracts API, go here.
This file documents a contract which is not yet deployed to Mainnet.
Governable contract.
A constructor is not defined, which makes the contract compatible with upgradable proxies. This requires calling explicitly _transferGovernance
function in a child contract.
address governance
event GovernanceTransferred(address oldGovernance, address newGovernance)
modifier onlyGovernance()
function transferGovernance(address newGovernance) external virtual
Transfers governance of the contract to newGovernance
.
function _transferGovernance(address newGovernance) internal virtual
A Decentralized, Permissionless Bitcoin Bridge to DeFi
Existing solutions that bridge Bitcoin to Ethereum require users to send their Bitcoin to an intermediary, in exchange for an ERC-20 token that represents the original asset. This centralized model requires you to trust a third party and is susceptible to censorship, threatening the premise of Bitcoin as sovereign, secure, permissionless digital asset.
The second generation of tBTC is a truly decentralized (and scalable) bridge between Bitcoin and Ethereum. It provides Bitcoin holders secure and open access to the broader DeFi ecosystem. tBTC v2 allows you to unlock your Bitcoin’s value to borrow and lend, mint stablecoins, provide liquidity, and much more.
Instead of centralized intermediaries, tBTC uses a randomly selected group of operators running nodes on the Threshold Network to secure deposited Bitcoin through threshold cryptography. That means tBTC requires a threshold majority agreement before operators perform any action with your Bitcoin. By rotating the selection of operators weekly, tBTC protects against any individual or group of operators colluding to fraudulently seize the underlying deposits. By relying on an honest-majority-assumption, we can calculate the likelihood any wallet comprised of a quorum of dishonest operators (for a deeper discussion of the probability calculations, see here).
Unlike other solutions on the market, users of tBTC trust math, not hardware or people.
Threshold is community-driven and governed by an eponymous DAO that includes the constituencies of both the NuCypher and Keep networks. The Threshold DAO has two primary bodies: the Tokenholder DAO and the Elected Council. The goal of this two-pronged structure is to enhance representation while ensuring accountability. Each of these governance bodies holds the other accountable, similar to the system of checks and balances found in most constitutional governments. They also hold separate responsibilities that are embedded in the governance structure.
From an implementation perspective, the Tokenholder DAO is based on the Governor Bravo governance model (in particular, using OpenZeppelin Governance). The Tokenholder DAO is on Ethereum Mainnet at 0xd101f2B25bCBF992BdF55dB67c104FE7646F5447
. The Elected Council is implemented as a Gnosis Safe contract, with a 6-of-9 configuration, also deployed on Ethereum Mainnet, at 0x9F6e831c8F8939DC0C830C6e492e7cEf4f9C2F5f
.
A technical architecture diagram is show below.
A backstop for assets secured by the tBTC protocol
Threshold Coverage Pool has been sunset via the DAO ratified proposal.
The Threshold Coverage Pool served as a backstop for assets secured by the tBTC protocol. In the event that secured Bitcoin were lost from the protocol, assets from the coverage pool were to be withdrawn by the risk manager, converted to BTC, and put back into the protocol to achieve the supply peg as closely as possible. The risk manager for the coverage pool was the multisig.
Over time, it became clear that this backstop model was sub-optimal given the relationship between tBTC and T - in the case of a BTC collateral loss event, T's value would also decline, greatly reducing its effectiveness as an insurance backstop. Consequently, the explicit Threshold Coverage Pool has been sunset with the approval of TIP-064.
Under the newly implemented "implicit coverage pool" model, tBTC is backed by the full faith and credit of Threshold DAO - by its treasury assets, which currently include T, ETH, wBTC, tBTC, stablecoins, CVX, veCRV, and associated LP tokens, etc.
A Bitcoin-backed Stablecoin
Threshold USD (thUSD) is a stablecoin soft-pegged against USD and backed by ETH and tBTC as collateral, with a minimum collateral ratio of 110%.
With either BTC or ETH as your collateral, you'll be able to borrow thUSD at very low cost.
Threshold USD is a modified fork of built to be self-sustained through a PCV ("Protocol Controlled Value"). There is no equivalent of LQTY token in Threshold USD. Instead all profits accrue into the PCV. Since there is no token, is completed through an .
The result of protocol owning its own liquidity ("PCV") is a more predictable trajectory and sustainability long-term. The stability pool is funded by the PCV instead of user deposits. Thanks to this, no funds are wasted on rewards to passive LQTY stakers (rented liquidity) and those funds can instead by re-injected into the stability pool. As the protocol grows and accrue fees, the stability pool will be consistently topped up.
Liquity is limited to ETH as the only type of collateral. Threshold USD expand to support BTC as collateral for loans through the decentralized BTC wrapper tBTC. In the future other types of collateral may be considered for on-boarding if they are sufficiently decentralized.
Each collateral will be deployed with its own contracts and each new collateral will require a DAO vote with a lengthy time delay before deployment. By having each collateral be its own contract any collateral can easily be deprecated by disallowing minting on that specific contract.
All contracts related to thUSD can be found .
Redemptions are a mechanism to return thUSD to a value of 1USD if it falls below it
The ability to redeem thUSD for tBTC at face value (i.e. 1 thUSD for $1 of tBTC) and the minimum collateral ratio of 110% create a price floor and price ceiling (respectively) through arbitrage opportunities. We call these "hard peg mechanisms" since they are based on direct processes.
thUSD also benefits from less direct mechanisms for USD parity — called "soft peg mechanisms". One of these mechanisms is parity as a Schelling point. Since Threshold USD treats thUSD as being equal to USD, parity between the two is an implied equilibrium state of the protocol. Another of these mechanisms is the borrowing fee on new debts. As redemptions increase (implying thUSD is below $1), so too does the baseRate — making borrowing less attractive which keeps new thUSD from hitting the market and driving the price below $1.
Liquity (on which thUSD Protocol is based on) did a thorough analysis on the price stability of LUSD (). This analysis is equally relevant for thUSD.
A redemption is the process of exchanging thUSD for tBTC at face value, as if 1 thUSD is exactly worth $1. That is, for x thUSD you get x Dollars worth of tBTC in return.
Users can redeem their thUSD for tBTC at any time without limitations. However, a redemption fee might be charged on the redeemed amount.
For example, if the current redemption fee is 1%, the price of tBTC is $50,000 and you redeem 10,000 thUSD, you would get 0.198 tBTC (0.2 tBTC minus a redemption fee of 0.002 tBTC).
Note that the redeemed amount is taken into account for calculating the base rate and might have an impact on the redemption fee, especially if the amount is large.
To illustrate how Redemptions work, below you can see the mechanism in action for LUSD, when its value went below 1 USD. The Borrowing rate (red line) immediately spiked from 0.5% up to around 1.8% and redemptions (blue bars) started to happen, i.e. users would buy LUSD at less than 1 USD and redeem for ETH in Vaults as if it was valued at 1 USD, arbitraging the difference, reducing the supply of LUSD and increasing its price.
No, redemptions are a completely separate mechanism. All one has to do to pay back their debt is adjust their Vault's debt and collateral.
Under normal operation, the redemption fee is given by the formula (baseRate + 0.5%) * tBTCdrawn
Redemption fees are based on the baseRate state variable in Threshold USD, which is dynamically updated. The baseRate increases with each redemption, and decays according to time passed since the last fee event - i.e. the last redemption or issuance of thUSD.
Upon each redemption:
baseRate is decayed based on time passed since the last fee event
baseRate is incremented by an amount proportional to the fraction of the total thUSD supply that was redeemed
The redemption fee is given by (baseRate + 0.5%) * tBTCdrawn
If your Vault is redeemed against, you do not incur a net loss. However, you will lose some of your tBTC exposure. Your Vault's collateral ratio will also improve after a redemption.
The best way to avoid being redeemed against is by maintaining a high collateral ratio relative to the rest of the Vault's in the system. Remember: The riskiest Vault (i.e. lowest collateralized Vaults) are first in line when a redemption takes place.
A Bitcoin-backed Stablecoin
A certain network effect is required for a stablecoin to function well. Bootstrapping is the process of ensuring the protocol has gained enough traction to become self-sufficient.
In the past this was usually accomplished by being an early adopter of coins, such as buying Bitcoin early and benefiting from the lower price. DeFi revolutionized the bootstrapping concept by enabling the bootstrapping of capital through yield-farming (aka rented liquidity).
Liquity Protocol (creators of the LUSD stablecoin) bootstrapped by issuing LQTY tokens to depositors in the stability pool. This incentivized people to take out loans and deposit to the stability pool.
For normal, healthy operations of the protocol, it's vital that there is sufficient funds in the stability pool to cover all liquidations. However the LQTY incentive resulted in far more deposits than required, essentially creating wasted capital. Furthermore there are concerns on how sustainable Liquity will be after the initial 100 million LQTY has been issued and the pool is only sustained through liquidations rewards.
In Threshold USD we take a different approach. A newer concept in DeFi known as Protocol Controlled Value (PCV) is the idea that the protocol itself can own liquidity and use that to improve the protocol. This is a new alternative to renting it elsewhere (aka yield-farming) and has the benefit of long-term sustainability.
But the PCV has to come from somewhere. In thUSD we resolve this by issuing an to the PCV which then deposit these funds directly to the stability pool. Read more about this here:
That lets us bootstrap the stability pool at zero cost to the protocol.
But not only that, by eliminating the LQTY token, all profits (from interest, redemption fee, etc) will go directly into the PCV. Compared to Liquity, where profits are distributed among LQTY holders, we have eliminated a massive drain on the system, at no cost.
Having the stability pool funded on its own is enough for the protocol to function, and we can expect some users that want to borrow against their tBTC to take part, but it's not going to create major adoption, and for thUSD to function well it needs to be stable at ~$1, that requires people to put up liquidity at decentralized exchanges.
In order to draw in more users and create liquidity for thUSD, we will issue rewards to pools on other platforms (such as a thUSD stablecoin pool on Curve). This will incentivize users to take debt and deposit into Curve, with the added benefit of improved liquidity and resiliency.
How fast we want to grow can be almost entirely adjusted by how much reward is sent to the pool.
This part is indeed rented liquidity with all its drawbacks, but remember that Threshold USD profits are sent to the PCV instead of LQTY holders: as the protocol grows in popularity, profits from the protocol itself can be used to fund these rewards, thus creating a self-sustaining feedback loop.
On top of that, Threshold DAO will store some of its reserves in stablecoins, so the Threshold DAO can initiate a PCV with deposits on Curve. That will result in more stability for thUSD which is important for adoption.
Access the thUSD App Interface:
Navigate to and connect your preferred wallet on BOB Network.
Open a New Vault:
Go to the “Borrow” menu, choose tBTC or ETH as your collateral, and select "Open a Vault".
Deposit tBTC or ETH:
Specify the amount of tBTC or ETH you wish to deposit as collateral. Ensure the deposited amount meets the minimum collateral requirement of $2,000 ($1,800 minimum debt + $200 liquidation reserve) + transaction fees.
Set the collateral amount:
Set your desired collateral amount. Ensure it meets the minimum required ratio of 110% of your debt.
Mint thUSD:
Enter the amount of thUSD you wish to mint based on your collateral and collateralization ratio.
Review the details of your vault, including the collateral amount, collateralization ratio, and minted thUSD.
In case of tBTC as collateral, approve tBTC expenditure.
Confirm, and authorize the transaction to mint thUSD.
Manage Your Vault:
Monitor your vault on to ensure it remains adequately collateralized. You can adjust collateral and debt position to adjust the collateralization ratio or repay thUSD as needed.
Forum discussion: A Threshold community member posts a potential proposal on the Threshold governance forums. Community members who post the initial proposal are encouraged to get active in Meta governance discussions within the community on forums and in Discord to earn support for the proposal. There is no minimum token threshold required to create a new proposal on the forums. Community mods reserve the right to moderate spam proposals during this stage by deleting the proposal from the forums.
Temperature Check: Once a potential proposal has enough traction and discussion within the community, it can proceed for a temperature check via an off-chain . If a proposal receives majority support during the temperature check, it is eligible to move onto the next step.
Proposal Creation: A community member holding enough vote weight (at least 0.25% of T total supply) submits the proposal on-chain. The proposal enters a 2-day delay period before voting officially begins.
Vote Period: Proposal voting remains open for 10 days. If the proposal passes with enough quorum (at least 1.5% of T total supply), it moves onto the next step; if the proposal fails, it is canceled. Creators and supporters of the proposal may bring a modified proposal forward again but it must be sufficiently different and pass requirements outlined in previous steps.
Timelock Period: Once a proposal is approved, the Governor Bravo smart contracts include an additional timelock delay of 2 days. Anyone can interact with the Governor Bravo smart contracts to queue an approved proposal into the Timelock contract.
Execution: After the timelock delay, anyone can execute an approved proposal.
Council vetos: During the on-chain phase, the Elected Council can veto any proposal. This is intended to be an extra security mechanism in the event that a dangerous proposal passes.
Late quorum prevention: The 10-days voting period is automatically extended when quorum is reached late, to prevent governance attacks that try to reach quorum at the last minute; in case a proposal reaches quorum less than 2 days before the deadline, the proposal deadline is extended for 2 more days from the moment the quorum was reached.
Off-chain proposals: Some proposals do not imply any on-chain action (e.g. Council and Guild elections). In these cases, there is a Snapshot vote that takes 5 days for regular proposals and 7 days for elections, with no associated on-chain proposal.
To participate in DAO governance, token holders must set a governance delegate address. Vote delegation is the process of granting a delegate the power to vote on your behalf, using your voting weight, on DAO governance issues. The delegate can be yourself (self-delegation) or a third party. A configured delegate can be changed or revoked at any time.
Third-party delegates are volunteers who actively participate in Threshold governance and wish to grow their influence over critical DAO decisions. Delegates provide ETH addresses to which other Threshold token holders and Threshold stakers can delegate their vote weight. Third-party delegates are subsequently responsible for voting on Threshold governance proposals with this vote weight that others have assigned to them.
Community members who are unsure or uninterested in governance and do not want to actively participate in proposals, discussions, and voting, can instead delegate to active DAO members who have demonstrated a commitment to Threshold. Delegating their vote allows token holders and stakers to have an indirect say in the DAO without being involved in day-to-day governance activities.
Community members interested in serving as delegates can self-nominate .
The process for delegating is explained in the following subsection. It's not currently possible to delegate tokens deposited as LP in AMM pools (e.g. Curve), although this is an area for future improvement.
How to delegate liquid T tokens
Liquid T holders can delegate their token weights to themselves or a third party for voting on governance proposals.
1. Go to .
2. Connect your wallet.
3. Click "Set Up Delegation".
4. Select your "Delegation Type" - either to yourself or a third party.
5. Enter "Delegate Address" and click on "Delegate Votes".
6. Sign the transaction.
Required network configuration for the tBTC v2 staking client.
The node has to be accessible publicly to establish and maintain connections with bootstrap nodes and discovered peers. The node exposes metrics and diagnostics services for network health monitoring purposes. Update firewall rules as necessary, including application level firewalls for the following ports
The network port must be exposed publicly for peers to connect to your node.
The status port must be exposed publicly for rewards allocation.
An Announced Address is a layered addressing information (multiaddress
/multiaddr
) announced to the Threshold Network that is used by peers to connect with your node, e.g.: /dns4/bootstrap-0.test.keep.network/tcp/3919
or /ip4/104.154.61.116/tcp/3919
.
If the machine you’re running your node is not exposing a public IP (e.g. it is behind NAT) you should set the network.AnnouncedAddresses
(flag: --network.announcedAddresses
) configuration property to addresses (ip4
or dns4
) under which your node is reachable for the public.
To read more about multiaddress
see the .
Setup persistent data directories for the client.
The client requires two persistent directories. These directories will store configuration files and data generated and used by the client. It is highly recommended to create frequent backups of these directories. Loss of these data may be catastrophic and may lead to slashing.
The tBTC v2 client will create two subdirectories within the storage directory: keystore
and work
. You do not need to create these.
The keystore
subdirectory contains sensitive key material data generated by the client. Loosing the keystore
data is a serious protocol offense and leads to slashing and potentially losing funds.
The work
directory contains data generated by the client that should persist the client restarts or relocations. If the work
data are lost the client will be able to recreate them, but it is inconvenient due to the time needed for the operation to complete and may lead to losing rewards.
Assuming for the root user with the command provided in the step, the operator-key file should be located in the /.operator-key
directory.
Contained within the operator-key
directory is the account key file (operator key file), its name will be similar to the following:
UTC--2018-11-01T06-23-57.810787758Z--fa3da235947aab49d439f3bcb46effd1a7237e32
copy (not move!) this account key file to the config
directory created above
Find answers to some of the most commonly asked questions here.
Certain errors may be reported by your node during the early stages of Chaosnet and the tBTC 2 launch. See a sample below:
This is expected during this stage of the chaosnet. When the pool is locked, new operators cannot join but sortition (selecting operators from the pool) is possible. When the pool is unlocked, new operators can join but the sortition is not possible. Once the first set of beta operators is registered, the pool will get locked for some time to allow the sortition. After some time, when another set of beta operators are added, the pool will be unlocked again.
You can learn about APIs of contracts related to the DAO under the following links:
T network staking contract supports existing KEEP stakes by allowing KEEP stakers to use their stakes in T network and weights them based on KEEP<>T token ratio. KEEP stake owner is cached in T staking contract and used to restrict access to all functions only owner or operator should call. To cache KEEP stake owner in T staking contract, T staking contract first needs to resolve the owner.
Resolving liquid KEEP stake owner is easy. Resolving token grant stake owner is complicated and not possible to do on-chain from a contract external to KEEP TokenStaking contract. Keep TokenStaking knows the grant ID but does not expose it externally.
KeepStake contract addresses this problem by exposing operator-owner mappings snapshotted off-chain based on events and information publicly available from KEEP TokenStaking contract and KEEP TokenGrant contract. Additionally, it gives the Governance ability to add new mappings in case they are ever needed; in practice, this will be needed only if someone decides to stake their KEEP token grant in KEEP network after 2021-11-11 when the snapshot was taken.
Operator-owner pairs were snapshotted 2021-11-11 in the following way:
Fetch all TokenStaking events from KEEP staking contract.
Filter out undelegated operators.
Filter out canceled delegations.
Fetch grant stake information from KEEP TokenGrant for that operator to determine if we are dealing with grant delegation.
Fetch grantee address from KEEP TokenGrant contract.
Check if we are dealing with ManagedGrant by looking for all created ManagedGrants and comparing their address against grantee address fetched from TokenGrant contract.
Allows the Governance to set new operator-managed grant pair. This function should only be called for managed grants if the snapshot does include this pair.
Allows the Governance to set new operator-grantee pair. This function should only be called for non-managed grants if the snapshot does include this pair.
Resolves KEEP stake owner for the provided operator address. Reverts if could not resolve the owner.
Staker DAO voting power extraction from staked T positions,
Read the voting weight from the snapshot mechanism in the T staking contracts. Note that this also tracks legacy stakes (NU/KEEP).
See {IGovernor-getVotes}
Compute the total voting power for the Staker DAO.
This file documents a contract which is not yet deployed to Mainnet.
Library for handling calls to random beacon consumer.
Sets callback contract.
Executes consumer specified callback for the relay entry request.
This file documents a contract which is not yet deployed to Mainnet.
Wraps the modular exponent pre-compile introduced in Byzantium. Returns base^exponent mod p.
Calculates and returns the square root of a mod p if such a square root exists. The modulus p must be an odd prime. If a square root does not exist, function returns 0.
Calculates the Legendre symbol of the given a mod p.
Before initializing the tBTC SDK instance in your project, you need to answer the following questions:
What Bitcoin network and which tBTC contracts do you plan to interact with?
Do you want to perform just read-only actions or send transactions as well?
Answering those questions will allow initializing the SDK in the right way. The SDK is prepared to handle common use cases but provides some flexibility as well. This guide explains that in detail.
is a decentralized, permissionless Bitcoin bridge developed by the Threshold Network designed to bring Bitcoin (BTC) liquidity to Ethereum and other EVM-compatible networks. It allows Bitcoin holders to mint tBTC, an ERC-20 token fully backed 1:1 by BTC, enabling seamless participation in Ethereum's DeFi ecosystem without relying on centralized intermediaries.
By integrating tBTC, developers can bridge the gap between Bitcoin's liquidity and Ethereum's smart contract capabilities. tBTC provides a decentralized, secure and permissionless way to bring BTC into the Ethereum ecosystem enabling innovative DeFi applications and expanding possibilities for cross-chain interactions.
Utilizing the provided SDKs and developer resources you can seamlessly integrate tBTC into your projects and contribute to the growth of decentralized finance.
2022-10-10T18:30:42.571Z WARN keep-beacon beacon/node.go:78 selecting group not possible: [cannot select group in the sortition pool: [got error [execution reverted: Sortition pool unlocked] while resolving original error [execution reverted: Sortition pool unlocked]]] {"seed": "0x29c60250c292e108e6abf4dcc76cb161d8ae8e803c4d4419eaff6e97f514b080"}
function grantee() external view returns (address)
contract IKeepTokenStaking keepTokenStaking
mapping(address => address) operatorToManagedGrant
mapping(address => address) operatorToGrantee
constructor(contract IKeepTokenStaking _keepTokenStaking) public
function setManagedGrant(address operator, address managedGrant) external
function setGrantee(address operator, address grantee) external
function resolveOwner(address operator) external view returns (address)
function resolveSnapshottedManagedGrantees(address operator) internal view returns (address)
function resolveSnapshottedGrantees(address operator) internal pure returns (address)
contract IVotesHistory staking
constructor(contract IVotesHistory tStakingAddress) internal
function getVotes(address account, uint256 blockNumber) public view virtual returns (uint256)
account
address
Delegate account with T staking voting power
blockNumber
uint256
The block number to get the vote balance at
function _getPastTotalSupply(uint256 blockNumber) internal view virtual returns (uint256)
blockNumber
uint256
The block number to get the voting power at
struct Data {
contract IRandomBeaconConsumer callbackContract;
}
event CallbackFailed(uint256 entry, uint256 entrySubmittedBlock)
function setCallbackContract(struct Callback.Data self, contract IRandomBeaconConsumer callbackContract) internal
self
struct Callback.Data
callbackContract
contract IRandomBeaconConsumer
Callback contract.
function executeCallback(struct Callback.Data self, uint256 entry, uint256 callbackGasLimit) internal
self
struct Callback.Data
entry
uint256
The generated random number.
callbackGasLimit
uint256
Callback gas limit.
function requestRelayEntry(contract IRandomBeaconConsumer callbackContract) external
callbackContract
contract IRandomBeaconConsumer
Beacon consumer callback contract.
function modExp(uint256 base, uint256 exponent, uint256 p) internal view returns (uint256 o)
function modSqrt(uint256 a, uint256 p) internal view returns (uint256)
function legendre(uint256 a, uint256 p) internal view returns (int256)
[0]
int256
Returns 1 if a is a quadratic residue mod p, -1 if it is a non-quadratic residue, and 0 if a is 0.
function __beaconCallback(uint256 relayEntry, uint256 blockNumber) external
relayEntry
uint256
Relay entry (random number) produced by Keep Random Beacon.
blockNumber
uint256
Block number at which the relay entry was submitted to the chain.
Network
network.port
TCP
3919
Status
clientInfo.port
TCP
9601
cd /home/$USER/
mkdir keep
cd keep
mkdir storage config
cd /home/$USER/.operator-key
ls -la
cp name_of_account_key_file /home/$USER/keep/config/name_of_account_key_file
This is a mechanism to reduce systemic risk to thUSD by ensuring a total collateral ratio across all Vaults remain above 150%
Recovery Mode kicks in when the Total Collateral Ratio (TCR) of the system falls below 150%.
During Recovery Mode, Vaults with a collateral ratio below 150% can be liquidated.
Moreover, the system blocks borrower transactions that would further decrease the TCR. New thUSD may only be issued by adjusting existing Vaults in a way that improves their collateral ratio, or by opening a new Vault with a collateral ratio>=150%. In general, if an existing Vault's adjustment reduces its collateral ratio, the transaction is only executed if the resulting TCR is above 150%.
The Total Collateral Ratio or TCR is the ratio of the Dollar value of the entire system collateral at the current ETH:USD price, to the entire system debt. In other words, it's the sum of the collateral of all Vaults expressed in USD, divided by the debt of all Vaults expressed in thUSD.
The goal of Recovery Mode is to incentivize borrowers to behave in ways that promptly raise the TCR back above 150%, and to incentivize thUSD holders to replenish the Stability Pool.
Economically, Recovery Mode is designed to encourage collateral top-ups and debt repayments, and also itself acts as a self-negating deterrent: the possibility of it occurring actually guides the system away from ever reaching it. Recovery Mode is not a desirable state for the system.
While Recovery Mode has no impact on the redemption fee, the borrowing fee is set to 0% to maximally encourage borrowing (within the limits described above).
By increasing your collateral ratio to 150% or greater, your Vault will be protected from liquidation. This can be done by adding collateral, repaying debt, or both.
Yes, you can be liquidated below 150% if your Vault's collateral ratio is smaller than 150%. In order to avoid liquidation in Normal Mode and Recovery Mode, a user should keep their collateral ratio above 150%.
In Recovery Mode, liquidation loss is capped at 110% of a Vault's collateral. Any remainder, i.e. the collateral above 110% (and below the TCR), can be reclaimed by the liquidated borrower using the standard web interface.
This means that a borrower will face the same liquidation “penalty” (10%) in Recovery Mode as in Normal Mode if their Vault gets liquidated.
Ensure your wallet has sufficient ETH for transaction fees. You can fund your wallet on BOB by transferring tBTC or ETH from the Ethereum Mainnet.
Note: The minimum amount to mint thUSD on BOB is $2,000 ($1,800 minimum debt + $200 liquidation reserve) worth of tBTC or ETH + transaction fees.
Set up Wallet:
Ensure you have a wallet connected to the Ethereum Mainnet with sufficient tBTC or ETH for bridging.
Access Bridge Interface:
Navigate to the BOB bridge interface at app.gobob.xyz/bridge.
Connect Wallet:
Connect your wallet on the Ethereum Mainnet network to the bridge interface.
Select tBTC or ETH:
Choose tBTC or ETH as the asset you want to bridge.
Enter Amount:
Specify the amount of tBTC or ETH you want to bridge.
Confirm Transaction:
Review the transaction details, including fees, and confirm the transaction.
Authorize Transaction:
Approve the transaction in your wallet.
Wait for Confirmation:
Wait for the transaction to be confirmed on the Ethereum blockchain.
Verify Receipt:
Once confirmed, check your wallet on the BOB Network to verify the receipt of the bridged tBTC or ETH.
Note: When bridging assets back from the BOB Network, please know that the process can take up to 7 days.
Additionally to the DAO governance bodies, there are three community-led guilds with different focus areas: the Marketing Guild, the Integrations Guild, and the Treasury Guild. Each guild is managed by an elected committee and holds regular, rotating elections.
Join a guild (via Discord's #dao-contribute
channel) and work together with other Threshold DAO members based on your interests and expertise!
The Threshold Treasury Guild is responsible for effectively managing the Threshold DAO treasury. This includes growing Protocol Owned Liquidity (POL), researching / implementing best practice treasury management strategies, executing ecosystem liquidity incentives, diversifying the treasury, etc.
The core mission of the Threshold Integrations Guild is to build successful, synergistic and long-lasting relationships with other protocols, DAOs and external organizations.
The Threshold Marketing Guild is responsible for general Threshold marketing, growing our network of contributors, onboarding new members to the Threshold DAO, educating people about the Threshold’s value, services and use cases, and more.
Alternative application authorization methods to using the Threshold Dashboard.
An operator-registering transaction can be submitted with the Keep Client if the staking provider address key file is available.
export KEEP_CLIENT_CONFIG_DIR=$(pwd)/config
export KEEP_CLIENT_ETHEREUM_WS_URL="<Ethereum API WS URL>"
export STAKING_PROVIDER_KEY_FILE_PASSWORD="<Staking Provider Account Key File Password>"
export STAKING_PROVIDER_KEY_FILE_NAME="<Staking Provider Account Key File Name>"
export OPERATOR_ADDRESS="<Operator Account Address>"
docker run \
--volume $KEEP_CLIENT_CONFIG_DIR:/mnt/keep-client/config \
--env KEEP_CLIENT_ETHEREUM_PASSWORD=$STAKING_PROVIDER_KEY_FILE_PASSWORD
us-docker.pkg.dev/keep-test-f3e0/public/keep-client:latest \
ethereum \
--ethereum.url $KEEP_CLIENT_ETHEREUM_WS_URL \
--ethereum.keyFile /mnt/keep-client/config/$STAKING_PROVIDER_KEY_FILE_NAME \
beacon random-beacon register-operator --submit \
$OPERATOR_ADDRESS
export KEEP_CLIENT_CONFIG_DIR=$(pwd)/config
export KEEP_CLIENT_ETHEREUM_WS_URL="<Ethereum API WS URL>"
export STAKING_PROVIDER_KEY_FILE_PASSWORD="<Staking Provider Account Key File Password>"
export STAKING_PROVIDER_KEY_FILE_NAME="<Staking Provider Account Key File Name>"
export OPERATOR_ADDRESS="<Operator Account Address>"
docker run \
--volume $KEEP_CLIENT_CONFIG_DIR:/mnt/keep-client/config \
--env KEEP_CLIENT_ETHEREUM_PASSWORD=$STAKING_PROVIDER_KEY_FILE_PASSWORD
us-docker.pkg.dev/keep-test-f3e0/public/keep-client:latest \
ethereum \
--ethereum.url $KEEP_CLIENT_ETHEREUM_WS_URL \
--ethereum.keyFile /mnt/keep-client/config/$STAKING_PROVIDER_KEY_FILE_NAME \
ecdsa wallet-registry register-operator --submit
$OPERATOR_ADDRESS
An operator-registering transactions can be submitted with Etherscan.
For each of the RandomBeacon
and WalletRegistry
contracts perform the following steps:
Find the address of the contract and open it on Etherscan (see below).
Go to Contract
→ Write Contract
tab.
Connect your wallet with Connect to Web3
button.
Submit the registerOperator
function with your Operator address as an argument.
Generic interface for an application. Application is an external smart contract or a set of smart contracts utilizing functionalities offered by Threshold Network. Applications authorized for the given staking provider are eligible to slash the stake delegated to that staking provider.
event RewardsWithdrawn(address stakingProvider, uint96 amount)
Event emitted by withdrawRewards
function.
function withdrawRewards(address stakingProvider) external
Withdraws application rewards for the given staking provider. Rewards are withdrawn to the staking provider's beneficiary address set in the staking contract.
Emits RewardsWithdrawn
event.
function authorizationIncreased(address stakingProvider, uint96 fromAmount, uint96 toAmount) external
Used by T staking contract to inform the application that the authorized amount for the given staking provider increased. The application may do any necessary housekeeping. The application must revert the transaction in case the authorization is below the minimum required.
function authorizationDecreaseRequested(address stakingProvider, uint96 fromAmount, uint96 toAmount) external
Used by T staking contract to inform the application that the authorization decrease for the given staking provider has been requested. The application should mark the authorization as pending decrease and respond to the staking contract with approveAuthorizationDecrease
at its discretion. It may happen right away but it also may happen several months later. If there is already a pending authorization decrease request for the application, and the application does not agree for overwriting it, the function should revert.
function involuntaryAuthorizationDecrease(address stakingProvider, uint96 fromAmount, uint96 toAmount) external
Used by T staking contract to inform the application the authorization has been decreased for the given staking provider involuntarily, as a result of slashing. Lets the application to do any housekeeping neccessary. Called with 250k gas limit and does not revert the transaction if involuntaryAuthorizationDecrease
call failed.
function availableRewards(address stakingProvider) external view returns (uint96)
Returns the amount of application rewards available for withdrawal for the given staking provider.
function minimumAuthorization() external view returns (uint96)
The minimum authorization amount required for the staking provider so that they can participate in the application.
Tokenholder DAO voting power extraction from both liquid and staked T token positions, including legacy stakes (NU/KEEP).
contract IVotesHistory token
contract IVotesHistory staking
constructor(contract IVotesHistory tokenAddress, contract IVotesHistory tStakingAddress) internal
function getVotes(address account, uint256 blockNumber) public view virtual returns (uint256)
Read the voting weight from the snapshot mechanism in the token and staking contracts. For Tokenholder DAO, there are currently two voting power sources:
Liquid T, tracked by the T token contract
Stakes in the T network, tracked by the T staking contract. Note that this also tracks legacy stakes (NU/KEEP); legacy stakes count for tokenholders' voting power, but not for the total voting power of the Tokenholder DAO (see {_getPastTotalSupply}).
See {IGovernor-getVotes}
account
address
Tokenholder account in the T network
blockNumber
uint256
The block number to get the vote balance at
function _getPastTotalSupply(uint256 blockNumber) internal view virtual returns (uint256)
Compute the total voting power for Tokenholder DAO. Note how it only uses the token total supply as source, as native T tokens that are staked continue existing, but as deposits in the staking contract. However, legacy stakes can't contribute to the total voting power as they're already implicitly counted as part of Vending Machines' liquid balance; hence, we only need to read total voting power from the token.
blockNumber
uint256
The block number to get the vote power at
This file documents a contract which is not yet deployed to Mainnet.
function concatStorage(bytes _preBytes, bytes _postBytes) internal
function equalStorage(bytes _preBytes, bytes _postBytes) internal view returns (bool)
function concat(bytes _preBytes, bytes _postBytes) internal pure returns (bytes)
function slice(bytes _bytes, uint256 _start, uint256 _length) internal pure returns (bytes res)
function toAddress(bytes _bytes, uint256 _start) internal pure returns (address)
function toUint8(bytes _bytes, uint256 _start) internal pure returns (uint8)
function toUint(bytes _bytes, uint256 _start) internal pure returns (uint256)
function equal(bytes _preBytes, bytes _postBytes) internal pure returns (bool)
function toBytes32(bytes _source) internal pure returns (bytes32 result)
function keccak256Slice(bytes _bytes, uint256 _start, uint256 _length) internal pure returns (bytes32 result)
Based on ProxyAdmin
, an auxiliary contract in OpenZeppelin's upgradeability approach meant to act as the admin of a TransparentUpgradeableProxy
. This variant allows an additional actor, the "deputy", to perform upgrades, which originally can only be performed by the ProxyAdmin's owner. See OpenZeppelin's documentation for TransparentUpgradeableProxy
for more details on why a ProxyAdmin is recommended.
address deputy
event DeputyUpdated(address previousDeputy, address newDeputy)
modifier onlyOwnerOrDeputy()
constructor(contract StakerGovernor dao, address _deputy) public
function setDeputy(address newDeputy) external
function upgrade(contract TransparentUpgradeableProxy proxy, address implementation) public virtual
Upgrades proxy
to implementation
. This contract must be the admin of proxy
, and the caller must be this contract's owner or the deputy.
function upgradeAndCall(contract TransparentUpgradeableProxy proxy, address implementation, bytes data) public payable virtual
Upgrades proxy
to implementation
and calls a function on the new implementation. This contract must be the admin of proxy
, and the caller must be this contract's owner or the deputy.
function _setDeputy(address newDeputy) internal
function _checkCallerIsOwnerOrDeputy() internal view
Sweeping is an accounting optimization for tracking bitcoin deposits in tBTC contracts. It involves periodically consolidating recent bitcoin deposits into a single Unspent Transaction Output (UTXO) for a more optimal commitment cadence to Ethereum. This process simplifies tracking associated public keys and refund time locks while also helping prevent supply peg disruption. Typically, Sweeping runs every 8 hours depending on the volume and size of deposits.
When a user makes a deposit, they're locking up the funds using our specific locking script. This creates an Unspent Transaction Output (UTXO).
Typically, the job of a bitcoin "wallet" (wallets don't exist in the same way that they do on ethereum) is to scan the blockchain for particular locking script patterns that it thinks it can unlock (like a P2PKH script associated to your public key) and sum up the relevant funds. When you want to send someone else bitcoin, the wallet software figures out which UTXOs to unlock to send to them so that you don't have to.
If we wanted to be able to redeem bitcoin deposits, we would need to track which deposits are associated to which public keys the same way that wallet software does; this is a headache especially from a bitcoin mining fee perspective.
Further, our locking script includes this idea of a time-locked refund. If the funds are not touched by a certain amount of time (9 months at the time of writing; <locktime>
in the script), we allow the depositor to reclaim their deposit. We do this as an effective escape hatch for if something goes wrong with the protocol or deposit.
As a result of this, we give ourselves the same time limit for collecting the deposit. If we don't move the funds by that time, a user could deposit 5 BTC, receive 5 TBTC, and then 9 months later reclaim their original 5 BTC without redeeming the 5 BTC. This would break the TBTC supply peg.
Sweeping aims to solve both of these problems simultaneously as well as serves as a performance optimization. Every 8 hours, we take all of the recent deposits, unlock them, and then send the funds using a normal P2PKH script back to the wallet itself, consolidating the UTXOs into a single UTXO.
The result is that all of the scattered deposits are "swept" into a single "pile" represented by one UTXO that we can commit to on ethereum.
Here's a deposit for 10.3 BTC. That deposit is swept in transaction 0db1929c2fa41649264b95b9162bc7a12f43cb455a91bae7177667a53290d7e8 (along with many other deposits). That was one of three sweeps performed by that wallet (as of writing).
That transaction created a UTXO worth 1.51614471 BTC. The next transaction
Used the old UTXO (the first input) as well as all of the recent deposits to create a new UTXO (the output on the right) worth 15.42475511 BTC. The final transaction
Follows the same pattern. It uses the old UTXO (the first input) as well as the recent deposits to create a new UTXO worth 59.52818486 BTC.
B. Protocol is a third-party decentralized backstop liquidity protocol aiming to make lending platforms more stable.
In Liquity Protocol there's a concept called "Stability Pool". The purpose of the stability pool is to purchase liquidated collateral (tBTC) at a discount by using Threshold USD (thUSD) as payment.
Users deposits thUSD into the stability pool and their funds are pooled with other depositors. If a pool consist of 900,000 thUSD and a user deposits 100,000 thUSD, that user is entitled to 10% of the collateral seized.
The issue with stability pool is that as liquidations occurs, thUSD is traded to tBTC, but never back to thUSD. If left untouched, the pool will eventually only consist of tBTC and no further liquidations can take place against the pool. Furthermore, by acquiring tBTC collateral, the value for depositors in the pool becomes subject to the price of BTC.
Users are therefore incentivized to quickly sell of the tBTC for thUSD to avoid taking on price risk, but in Liquity, this process is manual. Users have to manually open the UI and withdraw tBTC, sell it for thUSD and then re-deposit the thUSD. This is both a time consuming and gas costly operation, best suited for whales and users with their own bots.
Even worse, a DAO cannot realistically operate in the current stability pool model because voting to move and sell funds becomes impractical, costly and slow.
In order to establish a non-human, algorithmic, fully automated solution to compound profits we look to B.Protocol.
Instead of depositing directly into the stability pool, we can use B.Protocol's wrapping smart contract interface, which deposits the thUSD to the stability pool on behalf of all users (and the PCV).
Once liquidation happens, the discounted tBTC is automatically offered for sale by B.Protocol’s Backstop AMM (B.AMM). This is done according to a deterministic formula, which takes into account the current tBTC and thUSD inventory, and the current BTC-USD market price (which is taken from Chainlink). Whenever the sale occurs, the smart contract deposits the returned thUSD back to the stability pool.
If there are no takers for the offer on the B.AMM, Gelato Keepers will arbitrage through popular DEX pairs to fulfill the order.
This way the PCV becomes self-sufficient and able to operate indefinitely without outside interference.
B.Protocol Team will deploy a non-upgradeable pool that is Threshold USD compatible.
The pool will be using Chainlink BTC/USD oracle (same as Threshold USD).
The pool will not deploy idle funds in any yield farming applications.
Threshold USD PCV will whitelist the smart contract for the B.Protocol pool and the Threshold Network DAO (T DAO) will vote to initiate a deposit of thUSD to the B.Protocol pool. The deposit is moved from the PCV smart contract to B.Protocol and can be moved back to the PCV at any time, without limitation, as long as the T DAO votes to do so. If there is tBTC in the pool at the time of withdraw, the tBTC may also be transferred back to the PCV.
The B.AMM contract's "A" parameter ("A" is amplification factor, higher value means less slippage) is operated through a co-ownership between T DAO and B.Protocol, where B.Protocol propose "A" value and T DAO can approve or reject.
The discount rate is set to 4% on deployment, but the max rate should be configurable up to 10% in a joint governance mechanism between T DAO & B.Protocol.
All profits from liquidations will auto-compound within the B. Protocol pool.
Keeper
As a backup mechanism, B. Protocol will deploy Gelato keepers that can route.
Example:
tBTC -> WBTC -> USDC -> thUSD
In this example funds could be routed through Curve Finance pools (both stablecoin and wrapped bitcoin pools).
In addition, funds could be routed through uniswap (v2 or v3) WBTC or USDC paired pools.
Other routes could be:
tBTC -> WBTC -> DAI -> thUSD
tBTC -> WBTC -> USDT -> thUSD
The keeper is funded by the DAO and any profit it makes is distributed back to the PCV.
The following diagram presents the architecture of the tBTC SDK and its place in the ecosystem:
As you can see, the SDK consists of several major parts:
TBTC
component
Feature services
Shared libraries
TBTC
componentThe TBTC
component is the main entry point to the SDK. It provides different ways to initialize the SDK that are supposed to address the common use cases (see Initialize SDK guide to learn more). Moreover, the TBTC
component gives access to the SDK core features through feature services as well as low-level direct access to the underlying tBTC smart contracts and Bitcoin network client.
The role of the SDK feature services is to provide seamless access to the core features of the tBTC bridge. The most important feature services of the SDK are:
Deposits: deposit and mint flow for BTC depositors
Redemptions: unmint and redeem flow for TBTC redeemers
Maintenance: authorized maintenance actions for maintainers (e.g. optimistic minting guardians)
Shared libraries are reusable modules that provide cross-cutting concerns leveraged by multiple feature services and the TBTC
component. The main shared libraries of the SDK are:
Bitcoin: interfaces and utilities necessary to interact with the Bitcoin chain
Contracts: chain-agnostic interfaces allowing to interact with tBTC smart contracts
Electrum: Electrum-based implementation of the Bitcoin client
Ethereum: implementations of tBTC smart contract interfaces for Ethereum chain
Utils: general utility functions
tBTC SDK is a TypeScript library that provides effortless access to the fundamental features of the tBTC Bitcoin bridge. The SDK allows developers to integrate tBTC into their own applications and offer the power of trustless tokenized Bitcoin to their users.
The SDK documentation consists of the following parts:
For a high-level overview of the tBTC protocol, please see the tBTC Bitcoin Bridge section.
The system was designed from the outset to be fully permissionless - anyone with the minimum amount of T stake, could run a node and have a of being a custodian.
In order to effectively ship the product, we launched with a permissioned set of Signers, as well as permissioned sets of Guardians and Minters. This document will explain the ideas behind those changes and what we're doing about it going forward.
tBTC currently uses a permissioned set of signers (aka ) operating under an honest-majority assumption.
The permissioned honest-majority signer set will remain in place until custody is upgraded to a 1-of-N trust assumption using BitVM2 (or an equivalent design).
To select the 100 signers for a wallet, tBTC selects from the permissioned list of Beta Stakers. This decision was made for several reasons:
During the early days of the system; the testnet and phases, it is important to ensure that signers are available to help test changes and quickly respond to critical bugs. Having direct access to individuals known to the development team is critical in this context.
The underlying signature algorithm, , cannot identify misbehaving signers.
The point about GG18 is crucial. Without being able to identify misbehaving signers, a small, sophisticated, malicious minority can make it difficult to sign Bitcoin transactions in a timely manner. If we were able to identify the misbehaving signers, we could exclude them directly from the signing process.
Although alternative algorithms exist for identifying misbehaving signers, they are not yet viable for production use for one or more of the following reasons:
They are not yet codebases.
They are not open source.
They exist only as whitepapers.
To address this issue, we have been developing a proof-of-concept for and exploring .
Recent advances in Bitcoin bridge design, specifically BitVM2, suggest the possibility of improving custody from an honest-majority assumption to a 1-of-N trust assumption. Threshold Network is closely monitoring the development of this design and various implementations, with the medium-term intent of upgrading tBTC's underlying custody mechanics once the technology is sufficiently mature.
Guardians and Minters are a permissioned set of high-trust public operators with their reputations on the line.
Guardians are responsible for validating mint and redemption requests, and have the ability to veto malicious or fraudulent requests. If we were to make these lists permissionless, a malicious Minter could flood the system with fake minting requests for the Guardians to deal with, or a malicious Guardian could veto every proposed mint to halt growth.
If a Guardian or Minter misbehaves, the can vote to remove them.
Minters serve a convenience function by enabling "fast minting." Importantly, they do not have the ability to gatekeep mint requests; any deposit that all Minters refuse to approve or that a Guardian refuses to accept can still be minted every eight hours by the signers. This ensures that the system remains resilient and able to handle situations in which a malicious actor attempts to disrupt the minting process.
Mechanisms to keep thUSD solvent under changing collateral price
The Stability Pool is the first line of defense in maintaining system solvency. It achieves that by acting as the source of liquidity to repay debt from liquidated Vaults—ensuring that the total thUSD supply always remains backed.
When any Vault is liquidated, an amount of thUSD corresponding to the remaining debt of the Vault is burned from the Stability Pool’s balance to repay its debt. In exchange, the entire collateral from the Vault is transferred to the Stability Pool.
The Stability Pool is funded by users transferring thUSD into it (called Stability Providers). Over time Stability Providers lose a pro-rata share of their thUSD deposits, while gaining a pro-rata share of the liquidated collateral. However, because Vaults are likely to be liquidated at just below 110% collateral ratios, it is expected that Stability Providers will receive a greater dollar-value of collateral relative to the debt they pay off.
Stability Pool Providers will make liquidation gains.
To ensure that the entire Stablecoin supply remains fully backed by collateral, Vaults that fall under the minimum collateral ratio of 110% will be closed (liquidated).
The debt of the Vault is canceled and absorbed by the Stability Pool and its collateral distributed among Stability Providers.
The owner of the Vault still keeps the full amount of thUSD borrowed but loses ~10% value overall hence it is critical to always keep the ratio above 110%, ideally above 150%.
Anybody can liquidate a Vault as soon as it drops below the Minimum Collateral Ratio of 110%. The initiator receives a gas compensation (200 thUSD + 0.5% of the Vaults collateral) as reward for this service.
The liquidation of Vaults is connected with certain gas costs which the initiator has to cover. The cost per Vault was reduced by implementing batch liquidations of up to 160 - 185 Vaults but with the aim of ensuring that liquidations remain profitable even in times of soaring gas prices the protocol offers a gas compensation given by the following formula:
gas compensation = 200 thUSD + 0.5% of Vault's collateral (ETH)
The 200 thUSD is funded by a Liquidation Reserve while the variable 0.5% part (in tBTC) comes from the liquidated collateral, slightly reducing the liquidation gain for Stability Providers.
As liquidations happen just below a collateral ratio of 110%, you will most likely experience a net gain whenever a Vault is liquidated.
Let’s say there is a total of 1,000,000 thUSD in the Stability Pool and your deposit is 100,000 thUSD.
Now, a Vault with debt of 200,000 thUSD and collateral of 10.9 tBTCis liquidated at a tBTC price of $20,000, and thus at a collateral ratio of 109% (= 100% * (10.9 * 20,000) / 200,000). Given that your pool share is 10%, your deposit will go down by 10% of the liquidated debt (20,000 thUSD), i.e. from 100,000 to 80,000 thUSD. In return, you will gain 10% of the liquidated collateral, i.e. 1.09 tBTC, which is currently worth $21,800. Your net gain from the liquidation is $1,800.
Note that depositors can immediately withdraw the collateral received from liquidations and sell it to reduce their exposure to tBTC, if the USD value of tBTC is expected to decrease (for an exception see the next section).
As a general rule, you can withdraw the deposit made to the Stability Pool at any time. There is no minimum lockup duration. However, withdrawals are temporarily suspended whenever there are liquidatable Vault with a collateral ratio below 110% that have not been liquidated yet.
The protocol uses price feed, falling back to the tBTC:USD oracle under the following (extreme) conditions:
Chainlink price has not been updated for more than 4 hours
Chainlink response call reverts, returns an invalid price or an invalid timestamp
The price change between two consecutive Chainlink price updates is >50%.
While liquidations will occur at a collateral ratio well above 100% most of the time, it is theoretically possible that a Vault gets liquidated below 100% in a flash crash or due to an oracle failure. In such a case, you may experience a loss since the collateral gain will be smaller than the reduction of your deposit.
If thUSD is trading above $1, liquidations may become unprofitable for Stability Providers even at collateral ratios higher than 100%. However, this loss is hypothetical since thUSD is expected to return to the peg, so the “loss” only materializes if you had withdrawn your deposit and sold the thUSD at a price above $1.
Please note that although the system is diligently audited, a hack or a bug that results in losses for the users can never be fully excluded.
If the Stability Pool is empty, the system uses a secondary liquidation mechanism called redistribution. In such a case, the system redistributes the debt and collateral from liquidated Vaults to all other existing Vaults. The redistribution of debt and collateral is done in proportion to the recipient Vault's collateral amount.
This document explains the basic installation and configuration for the tBTC v2 staking client.
This document is intended for members of the community who would like to run their own tBTC v2 node.
Please review this document in its entirety prior to beginning setup of your node to familiarize yourself with the general setup process.
The Threshold Network expects certain capabilities for each node running on the network. To help attain these capabilities consider the following criteria:
It is paramount that tBTC v2 nodes remain available to the Network. We strongly encourage a stable and redundant internet connection.
Equally important is machine uptime and reliability. A VPS is strongly recommended.
A connection to a production grade self-hosted or third party Ethereum node deployment.
Persistent and redundant storage that will survive a VM or container rotation, and a disk failure.
Each node running on the network requires a unique Ethereum Operator Account. The account has to maintain a positive Ether balance at all times.
Each node running on the network requires a unique IP address or a unique application port running under the same IP.
Your operating environment will ultimately dictate what machine type to go with. This is particularly relevant if you’re running a containerized solution where multiple applications are sharing VM resources. The below types are sufficient for running one instance of the tBTC v2 Node.
The preferred OS is Ubuntu.
A Keep Node requires a connection to a WebSocket Ethereum API. You should obtain a WS API URL from a service provider (e.g. , , ) or run your own Ethereum node (e.g. ).
Starting from version v2.0.0-m3
, the Keep Node interacts with the Bitcoin network through an Electrum protocol server. You can use one of the publicly available servers or run your own. The URL of the chosen server should be passed under the bitcoin.electrum
section of the configuration. If no Electrum server is set explicitly in the bitcoin.electrum
configuration section, the Keep Node will randomly pick one server from its embedded server list, appropriate for the Bitcoin network the Keep Node is currently running against.
The client expects configuration options to be passed as CLI flags or specified in a . If you specify an option by using a parameter on the command line, it will override the value read from the configuration file.
Threshold Network T token
By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it requires users to delegate to themselves to activate checkpoints and have their voting power tracked.
The EIP-712 typehash for the delegation struct used by delegateBySig
.
Delegates votes from signatory to delegatee
Delegate votes from msg.sender
to delegatee
.
Hook that is called before any transfer of tokens. This includes minting and burning.
Calling conditions:
when from
and to
are both non-zero, amount
of from
's tokens will be to transferred to to
.
when from
is zero, amount
tokens will be minted for to
.
when to
is zero, amount
of from
's tokens will be burned.
from
and to
are never both zero.
Change delegation for delegator
to delegatee
.
You can learn about APIs of contracts related to the Random Beacon under the following links:
This file documents a contract which is not yet deployed to Mainnet.
A stub contract that will be used temporarily until the real-world random beacon client implementation is ready.
Authorized addresses that can request a relay entry.
Arbitrary relay entry. Initially set to the Euler's number. It's updated after each relay entry request.
Executes the callback with an arbitrary relay entry number.
The caller must be an authorized requester.
Authorizes a requester of the relay entry.
Client information exposed by the tBTC v2 staking client.
The client exposes metrics and diagnostics on a configurable port (default: 9601
) under /metrics
and /diagnostics
resources.
The data can be consumed by Prometheus to monitor the state of a node.
The client exposes the following metrics:
connected peers count,
connected bootstraps count,
Ethereum client connectivity status (if a simple read-only CALL can be executed).
Metrics are enabled once the client starts. It is possible to customize the port at which metrics endpoint is exposed as well as the frequency with which the metrics are collected.
Exposed metrics contain the value and timestamp at which they were collected.
Example metrics endpoint call result:
The client exposes the following diagnostics:
list of connected peers along with their network id and Ethereum operator address,
information about the client’s network id and Ethereum operator address.
Diagnostics are enabled once the client starts. It is possible to customize the port at which diagnostics endpoint is exposed.
Example diagnostics endpoint call result:
Here you can find instructions explaining how to use the SDK in your own project.
To install the tBTC SDK in your project, run:
Please note that you will also need to install the library to initialize a signer or provider. To do so, invoke:
Here is a brief example demonstrating the use of the SDK in Ethereum:
Here is a brief example demonstrating the use of the SDK in some L2, e.g. Arbitrum:
bytes32 DELEGATION_TYPEHASH
constructor() public
function delegateBySig(address signatory, address delegatee, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external
signatory
address
delegatee
address
The address to delegate votes to
deadline
uint256
The time at which to expire the signature
v
uint8
The recovery byte of the signature
r
bytes32
Half of the ECDSA signature pair
s
bytes32
Half of the ECDSA signature pair
function delegate(address delegatee) public virtual
delegatee
address
The address to delegate votes to
function beforeTokenTransfer(address from, address to, uint256 amount) internal
function delegate(address delegator, address delegatee) internal virtual
mapping(address => bool) authorizedRequesters
uint256 entry
event RequesterAuthorizationUpdated(address requester, bool isAuthorized)
function requestRelayEntry(contract IRandomBeaconConsumer callbackContract) external
callbackContract
contract IRandomBeaconConsumer
Beacon consumer callback contract - Wallet Registry
function setRequesterAuthorization(address requester, bool isAuthorized) external
$ curl localhost:9601/metrics
# TYPE connected_peers_count gauge
connected_peers_count 108 1623235129569
# TYPE connected_bootstrap_count gauge
connected_bootstrap_count 10 1623235129569
# TYPE eth_connectivity gauge
eth_connectivity 1 1623235129789
$ curl localhost:9601/diagnostics
{
"client_info" {
"ethereum_address":"0xDcd4199e22d09248cA2583cBDD2759b2acD22381",
"network_id":"16Uiu2HAkzYFHsqbwt64ZztWWK1hyeLntRNqWMYFiZjaKu1PZgikN"
},
"connected_peers": [
{"ethereum_address":"0x3712C6fED51CECA83cA953f6FF3458f2339436b4","network_id":"16Uiu2HAkyYtzNoWuF3ULaA7RMfVAxvfQQ9YRvRT3TK4tXmuZtaWi"},
{"ethereum_address":"0x4bFa10B1538E8E765E995688D8EEc39C717B6797","network_id":"16Uiu2HAm9d4MG4LNrwkFmugD2pX7frm6ZmA4vE3EFAEjk7yaoeLd"},
{"ethereum_address":"0x650A9eD18Df873cad98C88dcaC8170531cAD2399","network_id":"16Uiu2HAkvjVWogUk2gq6VTNLQdFoSHXYpobJdZyuAYeoWD66e8BD"},
...
]
}
contract ReimbursementPool reimbursementPool
event ReimbursementPoolUpdated(address newReimbursementPool)
modifier refundable(address receiver)
modifier onlyReimbursableAdmin()
function updateReimbursementPool(contract ReimbursementPool _reimbursementPool) external
AWS
c5.large
Azure
F2s v2
Google Cloud
n2-highcpu-2
Self-hosted
2 vCPU / 2 GB RAM / 1 GiB Persistent Storage
The SDK depends on ethers v5. Proper support for newer ethers versions is
not guaranteed right now.
// Import SDK entrypoint component.
import { TBTC } from "@keep-network/tbtc-v2.ts"
// Create an instance of ethers signer.
const signer = (...)
// Initialize the SDK for Ethereum only.
const sdk = await TBTC.initializeMainnet(signer)
// Access SDK features.
sdk.deposits.(...)
sdk.redemptions.(...)
// Access tBTC smart contracts directly.
sdk.tbtcContracts.(...)
// Access Bitcoin client directly.
sdk.bitcoinClient.(...)
// Import SDK entrypoint component.
import { TBTC } from "@keep-network/tbtc-v2.ts"
// Create an instance of ethers provider.
const ethProvider = (...)
// Create an instance of ethers signer.
const arbitrumSigner = (...)
// Initialize the SDK for Ethereum only.
const sdk = await TBTC.initializeMainnet(ethProvider, true)
// Initialize it for any L2 (E.g., Arbitrum)
await sdk.initializeCrossChain("Arbitrum", arbitrumSigner);
// Access SDK features.
sdk.deposits.(...)
sdk.redemptions.(...)
// Access tBTC smart contracts directly.
sdk.tbtcContracts.(...)
// Access Bitcoin client directly.
sdk.bitcoinClient.(...)
yarn add @keep-network/tbtc-v2.ts
npm i @keep-network/tbtc-v2.ts
yarn add ethers@legacy-v5
npm i ethers@legacy-v5
The primary wallet responsibility is taking care of Bitcoins deposited by tBTC users. Whenever a wallet needs to move some Bitcoins due to a protocol action (e.g. deposit sweep or redemption), it must assemble an appropriate Bitcoin transaction and sign each transaction's input using the tECDSA signing algorithm. During the tECDSA signing, randomly selected 51-of-100 wallet signers must collaborate to produce a proper signature.
A single tECDSA signing attempt consists of several steps. Each step has a maximum duration expressed in the specific number of host chain (e.g. Ethereum) blocks:
Signers readiness announcement phase which takes exactly 6 blocks
Signature production & advertisement phase which takes up to 30 blocks
Cooldown period that takes exactly 5 blocks
That means a single signing attempt can take up to 41 blocks (~8 minutes on Ethereum, assuming 12 seconds as the average block time). If the signature is not produced during that time, wallet signers give up the signing attempt and retry.
A wallet that is asked to sign the given message (e.g. Bitcoin transaction input) does not limit itself to a single tECDSA signing attempt. Due to the distributed nature of the signing algorithm, a single signing attempt may fail due to, for example, some unexpected network problems affecting inter-signer communication. If the first signing attempt fails, another set of randomly selected 51 wallet signers retry signing during the next attempt. Subsequent signing attempts are scheduled on fixed intervals, based on the block the first attempt started at, and the known maximum duration of a single attempt (41 blocks). For example, if the first attempt started at the block B, subsequent attempts will be scheduled as follows:
Attempt 2 -> B + 41
Attempt 3 -> B + 82
Attempt N -> B + (N-1) * 41
The wallet always executes 5 signing attempts at maximum. Such a count of maximum attempts is an acceptable trade-off between a reasonable signing timeout and a sufficient tolerance for misbehaving wallet signers. Executing 5 signing attempts allows trying 5 different 51-signer subsets. This is enough to produce a signature even for an unlikely scenario of 2 misbehaving wallet signers. In that case, 51 signers must be selected out of the honest 98 signers. That means the probability of selecting a successful subset is:
P = (98 choose 51) / (100 choose 51) = ~0.24
which means 5 selections are required in the worst case.
Combining the 5 signing attempts at maximum with up to 41 blocks per single attempt means the wallet will try to sign the given message for 5*41 = 205 blocks (~40 minutes on Ethereum, assuming 12 seconds as the average block time). If the signature is not produced during that time, wallet signers give up ultimately.
Wallets have the ability to sign multiple messages (e.g. multiple inputs of a single Bitcoin transaction) in a serial manner, one message after another. In such a case, the whole signing process is successful only if all messages in the batch obtained their signatures.
As mentioned in the previous section, the maximum allowed time for signing a single message is 205 blocks (~40 minutes on Ethereum). That means the maximum time to sign a batch of N messages is N*205 blocks. During that time, the wallet cannot perform any other work as signing is computationally heavy. This factor must be taken into account while building wallet actions that leverage signing.
The tBTC protocol defines a finite set of wallet actions:
Heartbeat
Deposit sweep
Redemption
Moving funds
Moved funds sweep
Each action involves wallet signing under the hood. The time necessary to complete the given action depends on how many signatures must be produced during execution. Knowing the worst-case time necessary to produce a single signature (205 blocks) and the number of signatures that may be required by the given action, we can set the right time boundaries for specific actions. This information is crucial for proper wallet coordination.
Let's use the Deposit sweep action to show how signing affects wallet actions. This action is initiated by a coordinator that submits a deposit sweep proposal through the WalletCoordinator
chain contract. An expected outcome of that action is a Bitcoin transaction with multiple inputs (swept deposits) and one output, submitted to the Bitcoin chain. That means the action's execution time strongly depends on the number of deposits suggested as part of the given sweep proposal. The WalletCoordinator
contract has some governable parameters that impact the Deposit sweep action:
depositSweepMaxSize
that sets the maximum number of deposits within a single sweep proposal
depositSweepProposalValidity
which determines the proposal's validity time. This is an exclusive time the wallet obtains to execute the given sweep and no other actions can be taken meanwhile
In other words, the wallet can sweep up to depositSweepMaxSize
deposits and has depositSweepProposalValidity
time to do so.
Signing is the most important and complex step of the Deposit sweep action and the reference client implementation sets the signing timeout to be depositSweepProposalValidity - 1 hour
for all inputs of the sweep transaction. Producing a signature for a single deposit input may take up to 205 blocks (5 attempts underneath). This is ~40 minutes assuming Ethereum's average block time of 12 seconds. Having that in mind, the aforementioned WalletCoordinator
governable parameters must be chosen with care as they have a strong impact on signing execution:
If depositSweepMaxSize = 5
and depositSweepProposalValidity = 4 hours
, the wallet will try to sign 5 inputs in 3 hours which gives ~36 minutes per input. As 5 signing attempts take ~40 minutes, this configuration will allow for 4 attempts per input
If depositSweepMaxSize = 10
and depositSweepProposalValidity = 4 hours
, the wallet will try to sign 10 inputs in 3 hours which gives ~18 minutes per input. As 5 signing attempts take ~40 minutes, this configuration will allow for 2 attempts per input
If depositSweepMaxSize = 20
and depositSweepProposalValidity = 4 hours
, the wallet will try to sign 20 inputs in 3 hours which gives ~9 minutes per input. As 5 signing attempts take ~40 minutes, this configuration will allow for 1 attempt per input
This page will guide you through Binary setup steps.
Choose either Docker installation OR Binary installation.
See GitHub for the latest release: https://github.com/keep-network/keep-core/releases
Under Assets, copy the path to the compressed binary and use the command below to download the file. Make relevant adjustments to version as necessary:
cd /home/keep
wget https://github.com/keep-network/keep-core/releases/download/XX.X.X-XX/keep-client-mainnet-XX.X.X-XX-linux-amd64.tar.gz
After the download completes, use the command below to display the contents of the folder:
ls -la
Extract the compressed file:
tar xzvf downloaded-file-name-here.tar.gz
To launch the tBTC v2 client, several configuration flags and environmental values need to be set. For simplicity, a bash script can be used rather than typing or pasting all the flags into the console.
The following flags must be set at minimum:
--ethereum.url "wss://mainnet-ETH-enpoint-here"
--storage.dir "/home/keep/storage"
--ethereum.keyFile "/home/keep/config/UTC--Your-Operator-Key-Name"
# to configure your node to use your machine's public IP
--network.announcedAddresses "/ip4/your.ipv4.address.here/tcp/3919"
# to configure your node to use DNS, provide your DNS in the following
# format
# /dns4/bootstrap-1.test.keep.network/tcp/3919
For a complete list of client commands and flags, see CLI Options.
To launch the client, execute the following:
./keep-client start --ethereum.url "wss://mainnet-ETH-enpoint-here" --storage.dir "/home/keep/storage" --ethereum.keyFile "/home/keep/config/UTC--Your-Operator-Key-Name" --network.announcedAddresses "/ip4/your.ipv4.address.here/tcp/3919"
If everything is configured correctly, the client will request the password for the Operator Account. Supply the password and press Enter.
You should see the following shortly:
▓▓▌ ▓▓ ▐▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓ ▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓ ▐▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓
▓▓▓▓▓▓▄▄▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▄▄▄▄ ▓▓▓▓▓▓▄▄▄▄ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▀▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓▀▀▀▀ ▓▓▓▓▓▓▀▀▀▀ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀
▓▓▓▓▓▓ ▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌
▓▓▓▓▓▓▓▓▓▓ █▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓
Trust math, not hardware.
-----------------------------------------------------------------------------------
| Keep Client Node |
| |
| Version: Version: vX.X.X-XX (4d745f6d0) |
| |
| Operator: 0x_your_operator_address |
| |
| Port: 3919 |
| IPs : /ip4/111.222.333.444/tcp/3919/ipfs/redacted |
| |
| Contracts: |
| RandomBeacon : 0x5499f54b4A1CB4816eefCf78962040461be3D80b |
| WalletRegistry : 0x46d52E41C2F300BC82217Ce22b920c34995204eb |
| TokenStaking : 0x01B67b1194C75264d06F808A921228a95C765dd7 |
-----------------------------------------------------------------------------------
Congratulations, your node is up and running.
Update procedure for the tBTC v2 client depends on installation method
The following instructions assume your docker install followed these installation instructions.
To update a tBTC node:
Pull the new image
docker pull keepnetwork/keep-client:latest
Restart the tBTC service
sudo systemctl restart tbtcv2
To free system resources, run
sudo docker container prune
sudo docker image prune
Examine logs to ensure the node started correctly. Find Docker instance identification; it'll be a random combination of words, e.g. stinky_brownie
:
sudo docker ps
Use specific identification and substitute accordingly; specify a path and file name for the log file:
sudo docker logs stinky_brownie >& /path/to/output/file
Display the log file
cat /path/to/output/file
Look for the following and take note of the version:
▓▓▌ ▓▓ ▐▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓ ▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓ ▐▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓
▓▓▓▓▓▓▄▄▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▄▄▄▄ ▓▓▓▓▓▓▄▄▄▄ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▀▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓▀▀▀▀ ▓▓▓▓▓▓▀▀▀▀ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀
▓▓▓▓▓▓ ▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌
▓▓▓▓▓▓▓▓▓▓ █▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓
Trust math, not hardware.
-----------------------------------------------------------------------------------
| Keep Client Node |
| |
| Version: vX.X.X-XX (4d745f6d0) |
| |
| Operator: 0x_your_operator_address |
| |
| Port: 3919 |
| IPs : /ip4/111.222.333.444/tcp/3919/ipfs/redacted |
| |
| Contracts: |
| RandomBeacon : 0x5499f54b4A1CB4816eefCf78962040461be3D80b |
| WalletRegistry : 0x46d52E41C2F300BC82217Ce22b920c34995204eb |
| TokenStaking : 0x01B67b1194C75264d06F808A921228a95C765dd7 |
-----------------------------------------------------------------------------------
Alternatively, verify your client updated by visiting your status page:
http://111.222.333.444:9601/metrics
Compare the displayed version number to the version number you are expecting, i.e.:
client_info{version="vX.X.X-XX"}
This install method requires downloading the latest version of the binary. Follow the installation steps provided here.
This file documents a contract which is not yet deployed to Mainnet.
struct Claim {
uint64 groupId;
uint256[] inactiveMembersIndices;
bytes signatures;
uint256[] signingMembersIndices;
}
uint256 groupThreshold
The minimum number of group members needed to interact according to the protocol to produce a valid inactivity claim.
uint256 signatureByteSize
Size in bytes of a single signature produced by member supporting the inactivity claim.
function verifyClaim(contract SortitionPool sortitionPool, struct BeaconInactivity.Claim claim, bytes groupPubKey, uint256 nonce, uint32[] groupMembers) external view returns (uint32[] inactiveMembers)
Verifies the inactivity claim according to the rules defined in Claim
struct documentation. Reverts if verification fails.
Group members hash is validated upstream in RandomBeacon.notifyOperatorInactivity()
sortitionPool
contract SortitionPool
Sortition pool reference
claim
struct BeaconInactivity.Claim
Inactivity claim
groupPubKey
bytes
Public key of the group raising the claim
nonce
uint256
Current nonce for group used in the claim
groupMembers
uint32[]
Identifiers of group members
inactiveMembers
uint32[]
Identifiers of members who are inactive
function validateMembersIndices(uint256[] indices, uint256 groupSize) internal pure
Validates members indices array. Array is considered valid if its size and each single index are in [1, groupSize] range, indexes are unique, and sorted in an ascending order. Reverts if validation fails.
indices
uint256[]
Array to validate
groupSize
uint256
Group size used as reference
Review available CLI Options below.
$ keep-client start --help
Starts the Keep Client in the foreground
Usage:
keep-client start [flags]
Flags:
--ethereum.url string WS connection URL for Ethereum client.
--ethereum.keyFile string The local filesystem path to Keep operator account keyfile.
--ethereum.miningCheckInterval duration The time interval in seconds in which transaction mining status is checked. If the transaction is not mined within this time, the gas price is increased and transaction is resubmitted. (default 1m0s)
--ethereum.maxGasFeeCap wei The maximum gas fee the client is willing to pay for the transaction to be mined. If reached, no resubmission attempts are performed. (default 500 gwei)
--ethereum.requestPerSecondLimit int Request per second limit for all types of Ethereum client requests. (default 150)
--ethereum.concurrencyLimit int The maximum number of concurrent requests which can be executed against Ethereum client. (default 30)
--ethereum.balanceAlertThreshold wei The minimum balance of operator account below which client starts reporting errors in logs. (default 500000000 gwei)
--bitcoin.electrum.url scheme://hostname:port URL to the Electrum server in format: scheme://hostname:port.
--bitcoin.electrum.connectTimeout duration Timeout for a single attempt of Electrum connection establishment. (default 10s)
--bitcoin.electrum.connectRetryTimeout duration Timeout for Electrum connection establishment retries. (default 1m0s)
--bitcoin.electrum.requestTimeout duration Timeout for a single attempt of Electrum protocol request. (default 30s)
--bitcoin.electrum.requestRetryTimeout duration Timeout for Electrum protocol request retries. (default 2m0s)
--bitcoin.electrum.keepAliveInterval duration Interval for connection keep alive requests. (default 5m0s)
--network.bootstrap Run the client in bootstrap mode.
--network.peers strings Addresses of the network bootstrap nodes.
-p, --network.port int Keep client listening port. (default 3919)
--network.announcedAddresses strings Overwrites the default Keep client address announced in the network. Should be used for NAT or when more advanced firewall rules are applied.
--network.disseminationTime int Specifies courtesy message dissemination time in seconds for topics the node is not subscribed to. Should be used only on selected bootstrap nodes. (0 = none)
--storage.dir string Location to store the Keep client key shares and other sensitive data.
--clientInfo.port int Client Info HTTP server listening port. (default 9601)
--clientInfo.networkMetricsTick duration Client Info network metrics check tick in seconds. (default 1m0s)
--clientInfo.ethereumMetricsTick duration Client info Ethereum metrics check tick in seconds. (default 10m0s)
--tbtc.preParamsPoolSize int tECDSA pre-parameters pool size. (default 1000)
--tbtc.preParamsGenerationTimeout duration tECDSA pre-parameters generation timeout. (default 2m0s)
--tbtc.preParamsGenerationDelay duration tECDSA pre-parameters generation delay. (default 10s)
--tbtc.preParamsGenerationConcurrency int tECDSA pre-parameters generation concurrency. (default 1)
--tbtc.keyGenerationConcurrency int tECDSA key generation concurrency. (default number of cores)
--developer.bridgeAddress string Address of the Bridge smart contract
--developer.lightRelayAddress string Address of the LightRelay smart contract
--developer.lightRelayMaintainerProxyAddress string Address of the LightRelayMaintainerProxy smart contract
--developer.randomBeaconAddress string Address of the RandomBeacon smart contract
--developer.tokenStakingAddress string Address of the TokenStaking smart contract
--developer.walletRegistryAddress string Address of the WalletRegistry smart contract
--developer.walletCoordinatorAddress string Address of the WalletCoordinator smart contract
Global Flags:
-c, --config string Path to the configuration file. Supported formats: TOML, YAML, JSON.
--developer Developer network
--testnet Testnet network
Environment variables:
KEEP_ETHEREUM_PASSWORD Password for Keep operator account keyfile decryption.
LOG_LEVEL Space-delimited set of log level directives; set to "help" for help.
Wallets are created periodically based on governance. changes the time between wallets and reads it. The time between new wallets is held in walletCreationPeriod
in number of seconds, so the current value of 1209600
represents 14 days.
In order for the wallet to move funds, it produces signatures using a , requiring 51-of-100 Signers to cooperate.
The 100 signers on each wallet are chosen with our , and the randomness is provided by the .
The probability that a is chosen to be a Signer is equal to their percentage of the total TBTC Stake. Each Signer is chosen independently. The same Staker can be a signer on the same wallet multiple times. The same Staker can be a Signer on multiple wallets simultaneously.
For simplicity, say there are only three Stakers: Alice, Bob, and Carol. Alice has 250M T, Bob has 400M T, and Carol has 350M T staked, so they own 25%, 40% and 35% of the stake respectively. That means, Alice has a 25% chance to be a Signer, Bob has a 40% chance, and Carol has a 35% chance. Each Signer is selected independently.
The Sortition Pool is more complex (because of heavy gas optimization), but reasoning about it could look like this:
For each Signer, we're going to generate a random number between 1 and 100. Alice is the Signer if the number is in [1, 25]. Bob is the Signer if the number is in [26, 65]. Carol is the Signer if the number is in [66, 100]. For example, if our first is 33, our first Signer would be Bob. We can generate 100 random numbers: (75, 51, 13, 48, 36, 62, 46, 65, 97, 67...)
, and then use that to determine Signers: (Carol, Bob, Alice, Bob, Bob, Bob, Bob, Bob, Carol, Carol...)
.
This example illustrates a few properties mentioned earlier:
Each Signer is selected independently. Whether or not Carol is the first signer has no influence on Carol being the second Signer.
The chance that you become a particular Signer is equal to your share of the Stake.
If Carol has a 35% chance of being a particular Signer, what are chances that Carol has at least 51 of the 100 seats (and could control the wallet by herself)?
The number of Signers that Carol controls on a wallet is modeled by the . From wikipedia:
A Binomial distribution with parameters
n
andp
is the of the number of successes in a sequence ofn
, each asking a , and each with its own -valued : success (with probabilityp
) or failure (with probability1−p
).
This is exactly our situation! From Carol's perspective, there will be n=100
independent experiments, each asking a yes-no question (does this Signer belong to Carol), each with a 35% probability.
That means we can use a to answer our question:
It's the last figure: "Cumulative probability: P(X>51)" that's relevant. That says there is a ~0.074% chance that Carol would have a controlling Share of any particular wallet.
The next important question is "The probability that Carol controls a Wallet is low, but it only needs to happen once for things to be bad. What is the probability that she controls any wallet in the next 2 years?"
A wallet is generated every 14 days, so over the next 2 years, the system would generate ~52 wallets. Each wallet has a 1 - 0.00074 = .99926
or 99.926% chance of not being controlled by Carol. That means we can exponentiate:
Carol would have a ~3.8% chance of getting control of a wallet in 2 years. That's with her owning 35% of the total Stake! Alice has a much lower chance. She has a 0.0002131%
chance of controlling a wallet, which means that over the course of 52 wallets, she has a 0.02131%
chance of controlling any wallet in 2 years.
As of writing, the *largest* Staker controls .
The only thing that a Staker accomplishes by splitting up their Stake into multiple identities is making the system appear to be more diverse.
For example, say that Alice split up her Stake equally into 5 accounts: Alice1, Alice2, Alice3, Alice4, and Alice5. Now, rather than Alice having a 25% chance to be a Signer, each account has a 5% chance which collectively add up to 25%.
To use the example from earlier: Alice has 250M T, Bob has 400M T, and Carol has 350M T staked. Alice has her T split into 50M for each identity. Alice1 would own 50M/1000M = 5%.
Keeping with the simplification of assigning numbers from 1 to 100 to identities, Alice1 would get [1, 5], Alice2 gets [6, 10], Alice3 gets [11, 15], Alice4 gets [16, 20], Alice5 gets [21, 25], Bob gets [26, 65], Carol gets [66, 100]. If we use the same random numbers as before, (75, 51, 13, 48, 36, 62, 46, 65, 97, 67...)
, then we make the assignments as (Carol, Bob, Alice3, Bob, Bob, Bob, Bob, Bob, Carol, Carol...)
. Nothing has effectively changed!
This is a space to consolidate all the DAO rules, guidelines and all the efforts we are leading to improve our ways of work
DAO rules were created with the inception of the DAO, and are mostly reflected in our forum, we refer as early rules to TIP-002, TIP-007, TIP-013, TIP-020, TIP-026 and TIP-032.
TIP-002, outlined by Matt Luongo, describes the creation of the DAO, the different bodies and the general ways we interact with each other.
TIP-007, outlined by Arj, describes the requirement of a minimum stake size for the Threshold Network stakers, at the moment of the network genesis.
TIP-013, outlined by Will, describes the creation of the DAO Guilds, and its inner workings.
TIP-020, outlined by Arj, describes the incentives for Threshold Network stakers at the interim era.
TIP-026, outlined by Will, outlines our governance processes, and creates a template to be followed when creating TIPs.
TIP-032, outlined by Arj, supersedes TIP-020 and establishes the reward allocation for stakers between the DAO products.
In 2022, the DAO lead an effort to improve our governance processes, leading to the creation and vote of several DAO rules. This endeavour was lead by the , a group of 3 people volunteered from each of the guilds + the DAO PM. The outcome of this workgroup was the creation of TIP-031 and TIP-034.
TIP-031, outlined by the rules committee, establishes the rules for the DAO Guilds elections and management.
TIP-034, outlined by the rules committee, extends TIP-026 by providing a more defined view to the way proposals are created for the DAO, outlining best practices and guidelines.
GP-021, outlined by ZeroInFo, details the expansion of the Integrations Guild Committee, increasing 2 seats.
TIP-049, outlined by John Packel, describes the restructuring of the Marketing Guild Committee.
TC-003, outlined by Luna5, outlines the alignment of the Council elections with the Guilds on the month of March, and removes the staggered elections for the Council.
Abstract contract to handle governance parameters
Based on GovernorVotesQuorumFraction
, but without being opinionated on what's the source of voting power, and extended to handle proposal thresholds too. See OpenZeppelin's GovernorVotesQuorumFraction, GovernorVotes and GovernorSettings for reference.
Update the voting delay. This operation can only be performed through a governance proposal. Emits a VotingDelaySet
event.
Update the voting period. This operation can only be performed through a governance proposal. Emits a VotingPeriodSet
event.
Compute the required amount of voting power to reach quorum
Compute the required amount of voting power to create a proposal at the last block height
This function is implemented to comply with Governor API but we we will actually use proposalThreshold(uint256 blockNumber)
, as in our DAOs the threshold amount changes according to supply.
Compute the required amount of voting power to create a proposal
module:user-config
Delay, in number of block, between the proposal is created and the vote starts. This can be increassed to leave time for users to buy voting power, of delegate it, before the voting of a proposal starts.
module:user-config
Delay, in number of blocks, between the vote start and vote ends.
NOTE: The {votingDelay} can delay the start of the vote. This must be considered when setting the voting duration compared to the voting delay.
Compute the past total voting power at a particular block
uint256 FRACTION_DENOMINATOR
uint64 AVERAGE_BLOCK_TIME_IN_SECONDS
uint256 quorumNumerator
uint256 proposalThresholdNumerator
event QuorumNumeratorUpdated(uint256 oldQuorumNumerator, uint256 newQuorumNumerator)
event ProposalThresholdNumeratorUpdated(uint256 oldThresholdNumerator, uint256 newThresholdNumerator)
event VotingDelaySet(uint256 oldVotingDelay, uint256 newVotingDelay)
event VotingPeriodSet(uint256 oldVotingPeriod, uint256 newVotingPeriod)
constructor(uint256 quorumNumeratorValue, uint256 proposalNumeratorValue, uint256 initialVotingDelay, uint256 initialVotingPeriod) internal
function updateQuorumNumerator(uint256 newQuorumNumerator) external virtual
function updateProposalThresholdNumerator(uint256 newNumerator) external virtual
function setVotingDelay(uint256 newVotingDelay) external virtual
function setVotingPeriod(uint256 newVotingPeriod) external virtual
function quorum(uint256 blockNumber) public view virtual returns (uint256)
blockNumber
uint256
The block number to get the quorum at
function proposalThreshold() public view virtual returns (uint256)
function proposalThreshold(uint256 blockNumber) public view returns (uint256)
blockNumber
uint256
The block number to get the proposal threshold at
function votingDelay() public view virtual returns (uint256)
function votingPeriod() public view virtual returns (uint256)
function _updateQuorumNumerator(uint256 newQuorumNumerator) internal virtual
function _updateProposalThresholdNumerator(uint256 proposalNumerator) internal virtual
function _setVotingDelay(uint256 newVotingDelay) internal virtual
function _setVotingPeriod(uint256 newVotingPeriod) internal virtual
function _getPastTotalSupply(uint256 blockNumber) internal view virtual returns (uint256)
blockNumber
uint256
The block number to get the vote power at
TIP-002
TIP-007
TIP-013
TIP-020
TIP-026
TIP-032
TIP-031
TIP-034
GP-021
TIP-049
Key aspects and parameters of borrowing thUSD
Threshold USD protocol offers low interest loans and is more capital efficient than other borrowing systems (i.e. less collateral is needed for the same loan). Instead of selling tBTC to have liquid funds, you can use the protocol to lock up your tBTC, borrow against the collateral to withdraw thUSD, and then repay your loan at a future date.
For example: Borrowers speculating on future tBTC price increases can use the protocol to leverage their tBTC positions up to 11 times, increasing their exposure to price changes. This is possible because thUSD can be borrowed against tBTC, sold on the open market to purchase more tBTC — rinse and repeat.*
*Note: This is NOT a recommendation for how to use Threshold USD. Leverage can be very risky as you can lose all your funds and should be used only by those with experience and for speculative amounts.
Collateral is any asset which a borrower must provide to take out a loan, acting as a security for the debt.
Currently, Threshold USD supports tBTC and ETH as collateral but additional collaterals could be added as voted by governance, as the code allows for it. The reasoning behind having these two collaterals is that they represent the two most decentralized assets in the Ethereum Ecosystem.
To borrow you must open a Vault and deposit a certain amount of collateral (tBTC) to it. Then you can draw thUSD up to a collateral ratio of 110%. A minimum debt of 2,000 thUSD is required. This minimum debt threshold prevents an attacker from creating a large number of Vaults that are then unprofitable to liquidate.
A Vault is where you take out and maintain your loan. Each Vault is linked to an Ethereum address and each address can have just one Vault. If you are familiar with Troves or CDPs from other platforms, Vault are similar in concept.
Vaults maintain two balances: one is an asset (tBTC) acting as collateral and the other is a debt denominated in thUSD. You can change the amount of each by adding collateral or repaying debt. As you make these balance changes, your Vault’s collateral ratio changes accordingly.
You can close your Vault at any time by fully paying off your debt.
For the base situation, see the section “Does Threshold USD charge any fees?”
Every time you draw thUSD from your Vault, a one-off borrowing fee is charged on the drawn amount and added to your debt. Please note that the borrowing fee is variable (and determined algorithmically) and has a minimum value of 0.5% under normal operation. The fee is 0% during Recovery Mode.
A 200 thUSD Liquidation Reserve charge will be applied as well, but returned to you upon repayment of debt.
When a Loan is opened, the borrowing fee is added to the debt of the Vault and is given by a baseRate . The fee rate is confined to a range between 0.5% and 5% and is multiplied by the amount of liquidity drawn by the borrower.
For example: The borrowing fee stands at 0.5% and the borrower wants to receive 4,000 thUSD to their wallet. Being charged a borrowing fee of 20.00 thUSD, the borrower will incur a debt of 4,220 thUSD after the Liquidation Reserve and issuance fee are added.
Loans issued by the protocol do not have a repayment schedule. You can leave your Vault open and repay your debt any time, as long as you maintain a collateral ratio of at least 110%.
This is the ratio between the Dollar value of the collateral in your Vault and its debt in thUSD. The collateral ratio of your Vault will fluctuate over time as the price of tBTC changes. You can influence the ratio by adjusting your Vault’s collateral and/or debt — i.e. adding more tBTC collateral or paying off some of your debt.
For example: Let’s say the current price of tBTC is $30,000 and you decide to deposit 1 tBTC. If you borrow 10,000 thUSD, then the collateral ratio for your Vault would be 200%.
(1tBTC * $30,000/10,000 thUSD)*100 = 300%
If you instead took out 15,000 thUSD that would put your ratio at 200%.
The minimum collateral ratio (or MCR for short) is the lowest ratio of debt to collateral that will not trigger a liquidation under normal operations (aka Normal Mode). This is a protocol parameter that is set to 110%. So if your Vault has a debt of 10,000 thUSD, you would need at least $11,000 worth of tBTC posted as collateral to avoid being liquidated.
To avoid liquidation during Recovery Mode, it is recommended to keep the ratio comfortably above 150% (e.g. 200% or better 250%).
You lose your collateral as your debt is paid off through liquidation, i.e. you will no longer be able to retrieve your collateral by repaying your debt. A liquidation thus results in a net loss of 9.09% (= 100% * 10 / 110) of your collateral’s Dollar value.
When you open a Vault and draw a loan, 200 thUSD is set aside as a way to compensate gas costs for the transaction sender in the event your Vault gets liquidated. The Liquidation Reserve is fully refundable if your Vault is not liquidated, and is given back to you when you close your Vault by repaying your debt. The Liquidation Reserve counts as debt and is taken into account for the calculation of a Vault's collateral ratio, slightly increasing the actual collateral requirements.
When thUSD is redeemed, the tBTC provided to the redeemer is allocated from the Vault(s) with the lowest collateral ratio (even if it is above 110%). If at the time of redemption you have the Vault with the lowest ratio, you will give up some of your collateral, but your debt will be reduced accordingly.
The USD value by which your tBTC collateral is reduced corresponds to the nominal thUSD amount by which your Vault’s debt is decreased. You can think of redemptions as if somebody else is repaying your debt and retrieving an equivalent amount of your collateral. As a positive side effect, redemptions improve the collateral ratio of the affected Vaults, making them less risky.
Redemptions that do not reduce your debt to 0 are called partial redemptions, while redemptions that fully pay off a Vault’s debt are called full redemptions. In such a case, your Vault is closed, and you can claim your collateral surplus and the Liquidation Reserve at any time.
Let’s say you own a Vault with 2 tBTC collateralized and a debt of 32,000 thUSD. The current price of tBTC is $20,000. This puts your collateral ratio (CR) at 125% (= 100% * (2 * 20,000) / 32,000). Let’s imagine this is the lowest CR in the Threshold USD system and look at two examples of a partial redemption and a full redemption:
Somebody redeems 12,000 thUSD for 0.6 tBTC and thus repays 12,000 thUSD of your debt, reducing it from 32,000 thUSD to 20,000 thUSD. In return, 0.6 tBTC, worth $12,000, is transferred from your Vault to the redeemer. Your collateral goes down from 2 to 1.4 tBTC, while your collateral ratio goes up from 125% to 140% (= 100% * (1.4 * 20,000) / 20,000).
Somebody redeems 60,000 thUSD for 3 tBTC. Given that the redeemed amount is larger than your debt minus 200 thUSD (set aside as a Liquidation Reserve), your debt of 32,000 thUSD is entirely cleared and your collateral gets reduced by $30,000 of tBTC, leaving you with a collateral of 0.5 tBTC (= 2 - 30,000 / 20,000).
By making liquidation instantaneous and more efficient, Threshold USD needs less collateral to provide the same security level as similar protocols that rely on lengthy auction mechanisms to sell off collateral in liquidations.
You can sell the borrowed thUSD on the market for tBTC and use the latter to top up the collateral of your Vault. That allows you to draw and sell more thUSD, and by repeating the process you can reach the desired leverage ratio.
Note: This is NOT a recommendation for how to use Threshold USD. Leverage can be very risky and should be used only by those with experience and still will most probably end up with a Liquidation, so this should be avoided.
If Vaults are liquidated and the Stability Pool is empty (or gets emptied due to the liquidation), every borrower will receive a portion of the liquidated collateral and debt as part of a redistribution process.
As part of bootstrapping tBTC utility, Threshold Network allow-lists authorized stakers to participate in tBTC minting and redemptions to ensure system continuity during progressive decentralization. These allow-listed stakers are called Beta Stakers. The goal with the Beta Staker program is to ensure key operations run properly without interruptions during early growth.
Note that this is a form of 'progressive decentralization', but with a clear, simple and already deployed pathway for the decentralization depth to increase – i.e. by adding more independent stakers to the allow list.
As of the latest contract query, there are now 35 Beta Stakers, an increase from 20 as previously reported in November 2023. A few notable professional staking providers can be found requesting DAO approval here.
Beta Staker nodes form a permissioned set that is responsible for creating tBTC wallets that custody BTC. Running a tBTC Beta Staker node is a significant commitment with specific requirements. This document aims to highlight such requirements, technical recommendations, and possible costs of operating a Beta Staker tBTC node.
To stay informed about current node activity and overall network health please visit tbtcscan.com/operators or check status.threshold.network. These resources provide up-to-date statistics on node counts, stake distribution and operational metrics.
Running a Beta Staker node is a long-term commitment. Once the given node is added to the Beta Stakers set, it must remain operational until the end of the Beta Staker Program (~12 months).
Operators of Beta Staker nodes are expected to be extremely responsive, especially regarding upgrades requested by software contributors. Ideally, they should be able to upgrade their nodes within 24 hours of notification.
Operators of Beta Staker nodes must be technically capable. They are responsible for ensuring high availability (more than 96% uptime) and security of the node.
A Beta Staker node performs more computationally expensive operations (DKG, threshold signing, etc) compared to a standard node. To ensure a high level of service, a Beta Staker node requires a machine with:
4 CPUs
4 GB of RAM
1 GB of persistent disk space
80 Mbps of network bandwidth
Linux OS
In addition to the above:
The underlying machine’s operating system and tools must be up to date, especially regarding recent security patches.
Backups of the persistent disk must be performed on an ongoing basis. This is crucial to ensure the safety of mission-critical data (e.g. key material). The usage of an additional encryption layer for backups is highly recommended.
The network layer must provide a unique public IPv4 address allowing the node to be reached from the external network. A proper firewall configuration must be in place and ensure a safe perimeter allowing inbound traffic on two ports (communication and diagnostics). The internet connection must be stable and failover scenarios must be taken into account.
A Beta Staker node requires access to an Ethereum node. It is highly recommended to set up a private Ethereum stack (e.g. Geth + Prysm) and use public providers as failover (e.g. Alchemy, Infura, etc). Such a setup is crucial for network diversity and decentralization as it helps avoid the single-point-of-failure risk associated with public providers. Beta Staker operators should use their own judgement for selecting stack components. Specific technical requirements for a private Ethereum stack depend on the software used. For example, a stack based on Geth + Prysm requires 4 CPU / 16 GB RAM / 2 TB SSD.
A Beta Staker node requires an Electrum protocol server to interact with Bitcoin. It is highly recommended to set up a private stack consisting of an Electrum server (e.g. Fulcrum, ElectrumX, Electrs, etc) paired with a Bitcoin node (e.g. Bitcoin Core) and use public servers as failover. The reasons are exactly the same as for a private Ethereum stack. Beta Staker operators should use their own judgement for selecting stack components. Specific technical requirements for a private Bitcoin stack depend on the software used. For example, a stack based on Fulcrum + Bitcoin Core requires 8 CPU / 16 GB RAM / 2 TB SSD.
It is highly recommended to set up a private monitoring stack to ensure observability and alerting. The monitoring system should supervise the node itself as well as Ethereum and Bitcoin stacks. Beta Staker operators should use their own judgement for selecting stack components. Specific technical requirements for a monitoring stack depend on the software used. For example, a stack based on Grafana + Prometheus requires 2 CPU / 4 GB RAM / 20 GB HDD.
The actual monthly costs of running a Beta Staker node depends on the hosting model (VPS vs self-hosting) and infrastructure configuration. The following estimation aims to provide a ballpark figure. It also assumes that all aforementioned technical requirements and recommendations are taken into account.
Key system assumptions
Beta Staker node requires 4 CPUs / 4 GB RAM / 1 GB HDD
Ethereum stack requires 4 CPUs / 16 GB RAM / 2 TB SSD
Bitcoin stack requires 8 CPUs / 16 GB RAM / 2 TB SSD
Monitoring stack requires 2 CPUs / 4 GB RAM / 20 GB HDD
External storage for backups (node and auxiliary stacks) is 1 TB HDD
Network layer ensures 100 Mbps of bandwidth and includes all necessary equipment (firewall, public IPs, etc)
The above assumptions can be used to divide costs into 4 categories:
Computing costs: This category includes the costs of running 18 CPUs and 40 GB RAM in total. There are a number of possible configurations to provide such computing capacity. Using the VPS-based model as an example, the cost of 3x 8CPU/16GB VMs on leading cloud service providers starts from ~$400/month.
Storage costs: This category includes the costs of using 4 TB SSD and ~1 TB HDD of storage in total. For the VPS-based model, part of this cost is often included in the cost of VMs, but some providers charge for the storage separately with rates like ~$0.08/GB (e.g., Amazon EBS). In that case, it is safe to assume that storage costs start from ~$200/month.
Network costs: This category includes the cost of 100 Mbps of bandwidth and all auxiliary equipment and features like a firewall or public IPs. For the VPS-based model, such bandwidth is often provided out of the box with the VMs, but a firewall and public IPs may be charged separately. Moreover, some providers may apply an additional charge on egress traffic. It is safe to assume that network costs start from ~$50/month.
Other costs: This category includes all maintenance and other costs depending on the hosting model and infrastructure configuration. For the VPS-based model, such costs can be OS licenses, automatic backup, additional management tools, and so on. For the self-hosting model, these can be costs of internet connection, power failover (e.g., UPS), auxiliary network equipment, and similar. This category of costs must be factored into the total monthly cost.
In summary, the cost for running a Beta Staker node using the VPS-based model starts at approximately $650/month. Assuming a safety margin for other costs, a rough cost estimate is $800/month for running a setup in accordance with the technical requirements and recommendations.
Estimating the general cost for the self-hosting model is non-trivial due to the variety of possible configurations. The total monthly cost can be similar to the VPS-based model but may also incur additional hidden costs such as hardware maintenance
Above assumptions and figures should be used cautiously as it is only intended to provide a general sense of the infrastructure costs. Each prospective Beta Staker node operator should conduct their own estimates based on their individual circumstances.
Last but not least, the presented estimate refers only to the infrastructure-related costs of operating a tBTC node. This estimate DOES NOT include human operational costs. Each Beta Staker operator must include this overhead when making their own estimates.
Compilation of current multisigs for the Threshold Council and Guilds
Ethereum
0x9F6e831c8F8939DC0C830C6e492e7cEf4f9C2F5f
Eth: Foundation
0xf642Bd6A9F76294d86E99c2071cFE2Aa3B61fBDa
Arbitrum
0x9F6e831c8F8939DC0C830C6e492e7cEf4f9C2F5f
Optimism
0x7fB50BBabeDEE52b8760Ba15c0c199aF33Fc2EfA
Polygon
0x9F6e831c8F8939DC0C830C6e492e7cEf4f9C2F5f
Base
0x518385dd31289F1000fE6382b0C65df4d1Cd3bfC
Solana
814TqVmhQGKB3srSLRuMcH6m8qWFHRSbNpRxC5Xnador
Starknet
0x0314A42C20b0364C6df5Af35E0914dd65F24a61F3563a8bc2A9D664938f37c4F
N/A (Argent's Multisig)
Ethereum
0x71E47a4429d35827e0312AA13162197C23287546
Arbitrum
0x6c2bd894bD166DCF053045a4883Db49EAE3FC01C
Optimism
0x126940ea97b328E57E9e2285D546a677b2f38300
Polygon
0xc3Bf49eBA094AF346830dF4dbB42a07dE378EeB6
Base
0x3b8f79458b6d303B47D56734B00CC6941443bd95
Solana
4bZiv1J33tJHiWSHPU96wABAZbPnbJ9M2zCmH9Hs12SW
Ethereum
eth:0x2ff7aB212cD6FEaE21bAc5300465E149FB6b85a9
Polygon
matic:0x5bD70E385414C8dCC25305AeB7E542d8FC70e667
Ethereum
eth:0xd55c4261145EA1752662faA0485AfBC8C431b0CA
Polygon
0xe663AD6295d47ce33F9b65950761Ecba6654E5E8
Use a config file to store client configuration.
Application configuration can be stored in a file and passed to the application with the --config
flag.
Example:
./keep-client --config /path/to/your/config.toml start
Configuration files in formats TOML, YAML and JSON are supported.
Sample configuration file:
# This is a sample TOML configuration file for the Keep client.
[ethereum]
URL = "ws://127.0.0.1:8546"
KeyFile = "/Users/someuser/ethereum/data/keystore/UTC--2018-03-11T01-37-33.202765887Z--AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAA"
# Uncomment to override the defaults for transaction status monitoring.
# MiningCheckInterval is the interval in which transaction
# mining status is checked. If the transaction is not mined within this
# time, the gas price is increased and transaction is resubmitted.
#
# MiningCheckInterval = 60 # 60 sec (default value)
# MaxGasFeeCap specifies the maximum gas fee cap the client is
# willing to pay for the transaction to be mined. The offered transaction
# gas cost can not be higher than the max gas fee cap value. If the maximum
# allowed gas fee cap is reached, no further resubmission attempts are
# performed. This property should be set only for Ethereum. In case of
# legacy non-EIP-1559 transactions, this field works in the same way as
# `MaxGasPrice` property.
#
# MaxGasFeeCap = "500 Gwei" # 500 Gwei (default value)
# Uncomment to enable Ethereum node rate limiting. Both properties can be
# used together or separately.
#
# RequestsPerSecondLimit sets the maximum average number of requests
# per second which can be executed against the Ethereum node.
# All types of Ethereum node requests are rate-limited,
# including view function calls.
#
# RequestsPerSecondLimit = 150
# ConcurrencyLimit sets the maximum number of concurrent requests which
# can be executed against the Ethereum node at the same time.
# This limit affects all types of Ethereum node requests,
# including view function calls.
#
# ConcurrencyLimit = 30
# BalanceAlertThreshold defines a minimum value of the operator's account
# balance below which the client will start reporting errors in logs.
# A value can be provided in `wei`, `Gwei` or `ether`, e.g. `7.5 ether`,
# `7500000000 Gwei`.
#
# BalanceAlertThreshold = "0.5 ether" # 0.5 ether (default value)
[bitcoin.electrum]
# URL to the Electrum server in format: `scheme://hostname:port`.
# Should be uncommented only when using a custom Electrum server. Otherwise,
# one of the default embedded servers is selected randomly at startup.
# URL = "tcp://127.0.0.1:50001"
# Timeout for a single attempt of Electrum connection establishment.
# ConnectTimeout = "10s"
# Timeout for Electrum connection establishment retries.
# ConnectRetryTimeout = "1m"
# Timeout for a single attempt of Electrum protocol request.
# RequestTimeout = "30s"
# Timeout for Electrum protocol request retries.
# RequestRetryTimeout = "2m"
# Interval for connection keep alive requests.
# KeepAliveInterval = "5m"
[network]
Bootstrap = false
Peers = [
"/ip4/127.0.0.1/tcp/3919/ipfs/16Uiu2HAmFRJtCWfdXhZEZHWb4tUpH1QMMgzH1oiamCfUuK6NgqWX",
]
Port = 3920
# Uncomment to override the node's default addresses announced in the network
# AnnouncedAddresses = ["/dns4/example.com/tcp/3919", "/ip4/80.70.60.50/tcp/3919"]
# Uncomment to enable courtesy message dissemination for topics this node is
# not subscribed to. Messages will be forwarded to peers for the duration
# specified as a value in seconds.
# Message dissemination is disabled by default and should be enabled only
# on selected bootstrap nodes. It is not a good idea to enable dissemination
# on non-bootstrap node as it may clutter communication and eventually lead
# to blacklisting the node. The maximum allowed value is 90 seconds.
#
# DisseminationTime = 90
[storage]
Dir = "/my/secure/location"
# ClientInfo exposes metrics and diagnostics modules.
#
# Metrics collects and exposes information useful for external monitoring tools usually
# operating on time series data.
# All values exposed by metrics module are quantifiable or countable.
#
# The following metrics are available:
# - connected peers count
# - connected bootstraps count
# - eth client connectivity status
#
# Diagnostics module exposes the following information:
# - list of connected peers along with their network id and ethereum operator address
# - information about the client's network id and ethereum operator address
[clientInfo]
Port = 9601
# NetworkMetricsTick = 60
# EthereumMetricsTick = 600
# Uncomment to overwrite default values for TBTC config.
#
# [tbtc]
# PreParamsPoolSize = 3000
# PreParamsGenerationTimeout = "2m"
# PreParamsGenerationDelay = "10s"
# PreParamsGenerationConcurrency = 1
# KeyGenConcurrency = 1
# Developer options to work with locally deployed contracts
#
# [developer]
# TokenStakingAddress = "0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
# RandomBeaconAddress = "0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
# WalletRegistryAddress = "0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
# BridgeAddress = "0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
This file documents a contract which is not yet deployed to Mainnet.
Library managing the state of stake authorizations for the operator contract and the presence of operators in the sortition pool based on the stake authorized for them.
struct Parameters {
uint96 minimumAuthorization;
uint64 authorizationDecreaseDelay;
uint64 authorizationDecreaseChangePeriod;
}
struct AuthorizationDecrease {
uint96 decreasingBy;
uint64 decreasingAt;
}
struct Data {
struct BeaconAuthorization.Parameters parameters;
mapping(address => address) stakingProviderToOperator;
mapping(address => address) operatorToStakingProvider;
mapping(address => struct BeaconAuthorization.AuthorizationDecrease) pendingDecreases;
}
event OperatorRegistered(address stakingProvider, address operator)
event AuthorizationIncreased(address stakingProvider, address operator, uint96 fromAmount, uint96 toAmount)
event AuthorizationDecreaseRequested(address stakingProvider, address operator, uint96 fromAmount, uint96 toAmount, uint64 decreasingAt)
event AuthorizationDecreaseApproved(address stakingProvider)
event InvoluntaryAuthorizationDecreaseFailed(address stakingProvider, address operator, uint96 fromAmount, uint96 toAmount)
event OperatorJoinedSortitionPool(address stakingProvider, address operator)
event OperatorStatusUpdated(address stakingProvider, address operator)
function setParameters(struct BeaconAuthorization.Data self, uint96 _minimumAuthorization, uint64 _authorizationDecreaseDelay, uint64 _authorizationDecreaseChangePeriod) external
Updates authorization-related parameters.
self
struct BeaconAuthorization.Data
_minimumAuthorization
uint96
New value of the minimum authorization for the beacon. Without at least the minimum authorization, staking provider is not eligible to join and operate in the network.
_authorizationDecreaseDelay
uint64
New value of the authorization decrease delay. It is the time in seconds that needs to pass between the time authorization decrease is requested and the time the authorization decrease can be approved, no matter the authorization decrease amount.
_authorizationDecreaseChangePeriod
uint64
New value of the authorization decrease change period. It is the time in seconds, before authorization decrease delay end, during which the pending authorization decrease request can be overwritten. If set to 0, pending authorization decrease request can not be overwritten until the entire authorizationDecreaseDelay
ends. If set to value equal authorizationDecreaseDelay
, request can always be overwritten.
function registerOperator(struct BeaconAuthorization.Data self, address operator) public
Used by staking provider to set operator address that will operate a node. The given staking provider can set operator address only one time. The operator address can not be changed and must be unique. Reverts if the operator is already set for the staking provider or if the operator address is already in use. Reverts if there is a pending authorization decrease for the staking provider.
function authorizationIncreased(struct BeaconAuthorization.Data self, address stakingProvider, uint96 fromAmount, uint96 toAmount) external
Used by T staking contract to inform the beacon that the authorized stake amount for the given staking provider increased.
Reverts if the authorization amount is below the minimum.
The function is not updating the sortition pool. Sortition pool state needs to be updated by the operator with a call to joinSortitionPool
or updateOperatorStatus
.
Should only be callable by T staking contract.
function authorizationDecreaseRequested(struct BeaconAuthorization.Data self, address stakingProvider, uint96 fromAmount, uint96 toAmount) public
Used by T staking contract to inform the beacon that the authorization decrease for the given staking provider has been requested.
Reverts if the amount after deauthorization would be non-zero and lower than the minimum authorization.
Reverts if another authorization decrease request is pending for the staking provider and not enough time passed since the original request (see authorizationDecreaseChangePeriod
).
If the operator is not known (registerOperator
was not called) it lets to approveAuthorizationDecrease
immediately. If the operator is known (registerOperator
was called), the operator needs to update state of the sortition pool with a call to joinSortitionPool
or updateOperatorStatus
. After the sortition pool state is in sync, authorization decrease delay starts.
After authorization decrease delay passes, authorization decrease request needs to be approved with a call to approveAuthorizationDecrease
function.
If there is a pending authorization decrease request, it is overwritten, but only if enough time passed since the original request. Otherwise, the function reverts.
Should only be callable by T staking contract.
function approveAuthorizationDecrease(struct BeaconAuthorization.Data self, contract IStaking tokenStaking, address stakingProvider) public
Approves the previously registered authorization decrease request. Reverts if authorization decrease delay have not passed yet or if the authorization decrease was not requested for the given staking provider.
function involuntaryAuthorizationDecrease(struct BeaconAuthorization.Data self, contract IStaking tokenStaking, contract SortitionPool sortitionPool, address stakingProvider, uint96 fromAmount, uint96 toAmount) external
Used by T staking contract to inform the beacon the authorization has been decreased for the given staking provider involuntarily, as a result of slashing.
If the operator is not known (registerOperator
was not called) the function does nothing. The operator was never in a sortition pool so there is nothing to update.
If the operator is known, sortition pool is unlocked, and the operator is in the sortition pool, the sortition pool state is updated. If the sortition pool is locked, update needs to be postponed. Every other staker is incentivized to call updateOperatorStatus
for the problematic operator to increase their own rewards in the pool.
Should only be callable by T staking contract.
function joinSortitionPool(struct BeaconAuthorization.Data self, contract IStaking tokenStaking, contract SortitionPool sortitionPool) public
Lets the operator join the sortition pool. The operator address must be known - before calling this function, it has to be appointed by the staking provider by calling registerOperator
. Also, the operator must have the minimum authorization required by the beacon. Function reverts if there is no minimum stake authorized or if the operator is not known. If there was an authorization decrease requested, it is activated by starting the authorization decrease delay.
function updateOperatorStatus(struct BeaconAuthorization.Data self, contract IStaking tokenStaking, contract SortitionPool sortitionPool, address operator) public
Updates status of the operator in the sortition pool. If there was an authorization decrease requested, it is activated by starting the authorization decrease delay. Function reverts if the operator is not known.
function isOperatorUpToDate(struct BeaconAuthorization.Data self, contract IStaking tokenStaking, contract SortitionPool sortitionPool, address operator) external view returns (bool)
Checks if the operator's authorized stake is in sync with operator's weight in the sortition pool. If the operator is not in the sortition pool and their authorized stake is non-zero, function returns false.
function eligibleStake(struct BeaconAuthorization.Data self, contract IStaking tokenStaking, address stakingProvider) external view returns (uint96)
Returns the current value of the staking provider's eligible stake. Eligible stake is defined as the currently authorized stake minus the pending authorization decrease. Eligible stake is what is used for operator's weight in the pool. If the authorized stake minus the pending authorization decrease is below the minimum authorization, eligible stake is 0.
This function can be exposed to the public in contrast to the second variant accepting decreasingBy
as a parameter.
function eligibleStake(struct BeaconAuthorization.Data self, contract IStaking tokenStaking, address stakingProvider, uint96 decreasingBy) public view returns (uint96)
Returns the current value of the staking provider's eligible stake. Eligible stake is defined as the currently authorized stake minus the pending authorization decrease. Eligible stake is what is used for operator's weight in the pool. If the authorized stake minus the pending authorization decrease is below the minimum authorization, eligible stake is 0.
This function is not intended to be exposes to the public. decreasingBy
must be fetched from pendingDecreases
mapping and it is passed as a parameter to optimize gas usage of functions that call eligibleStake
and need to use AuthorizationDecrease
fetched from pendingDecreases
for some additional logic.
function pendingAuthorizationDecrease(struct BeaconAuthorization.Data self, address stakingProvider) public view returns (uint96)
Returns the amount of stake that is pending authorization decrease for the given staking provider. If no authorization decrease has been requested, returns zero.
function remainingAuthorizationDecreaseDelay(struct BeaconAuthorization.Data self, address stakingProvider) external view returns (uint64)
Returns the remaining time in seconds that needs to pass before the requested authorization decrease can be approved. If the sortition pool state was not updated yet by the operator after requesting the authorization decrease, returns type(uint64).max
.
This file documents a contract which is not yet deployed to Mainnet.
Implementations of common elliptic curve operations on Ethereum's (poorly named) alt_bn128 curve. Whenever possible, use post-Byzantium pre-compiled contracts to offset gas costs. Note that these pre-compiles might not be available on all (eg private) chains.
struct G1Point {
uint256 x;
uint256 y;
}
struct gfP2 {
uint256 x;
uint256 y;
}
struct G2Point {
struct AltBn128.gfP2 x;
struct AltBn128.gfP2 y;
}
uint256 p
uint256 g1x
Gets generator of G1 group. Taken from go-ethereum/crypto/bn256/cloudflare/curve.go
uint256 g1y
uint256 g2xx
Gets generator of G2 group. Taken from go-ethereum/crypto/bn256/cloudflare/twist.go
uint256 g2xy
uint256 g2yx
uint256 g2yy
uint256 twistBx
Gets twist curve B constant. Taken from go-ethereum/crypto/bn256/cloudflare/twist.go
uint256 twistBy
uint256 hexRootX
Gets root of the point where x and y are equal.
uint256 hexRootY
function g1YFromX(uint256 x) internal view returns (uint256)
g1YFromX computes a Y value for a G1 point based on an X value. This computation is simply evaluating the curve equation for Y on a given X, and allows a point on the curve to be represented by just an X value + a sign bit.
function g1HashToPoint(bytes m) internal view returns (struct AltBn128.G1Point)
Hash a byte array message, m, and map it deterministically to a point on G1. Note that this approach was chosen for its simplicity and lower gas cost on the EVM, rather than good distribution of points on G1.
function g1Decompress(bytes32 m) internal view returns (struct AltBn128.G1Point)
Decompress a point on G1 from a single uint256.
function g1Add(struct AltBn128.G1Point a, struct AltBn128.G1Point b) internal view returns (struct AltBn128.G1Point c)
Wraps the point addition pre-compile introduced in Byzantium. Returns the sum of two points on G1. Revert if the provided points are not on the curve.
function isG1PointOnCurve(struct AltBn128.G1Point point) internal view returns (bool)
Returns true if G1 point is on the curve.
function scalarMultiply(struct AltBn128.G1Point p_1, uint256 scalar) internal view returns (struct AltBn128.G1Point p_2)
Wraps the scalar point multiplication pre-compile introduced in Byzantium. The result of a point from G1 multiplied by a scalar should match the point added to itself the same number of times. Revert if the provided point isn't on the curve.
function pairing(struct AltBn128.G1Point p1, struct AltBn128.G2Point p2, struct AltBn128.G1Point p3, struct AltBn128.G2Point p4) internal view returns (bool result)
Wraps the pairing check pre-compile introduced in Byzantium. Returns the result of a pairing check of 2 pairs (G1 p1, G2 p2) (G1 p3, G2 p4)
function getP() internal pure returns (uint256)
function g1() internal pure returns (struct AltBn128.G1Point)
function g2() internal pure returns (struct AltBn128.G2Point)
function g2YFromX(struct AltBn128.gfP2 _x) internal pure returns (struct AltBn128.gfP2 y)
g2YFromX computes a Y value for a G2 point based on an X value. This computation is simply evaluating the curve equation for Y on a given X, and allows a point on the curve to be represented by just an X value + a sign bit.
function g1Compress(struct AltBn128.G1Point point) internal pure returns (bytes32)
Compress a point on G1 to a single uint256 for serialization.
function g2Compress(struct AltBn128.G2Point point) internal pure returns (bytes)
Compress a point on G2 to a pair of uint256 for serialization.
function g1Unmarshal(bytes m) internal pure returns (struct AltBn128.G1Point)
Unmarshals a point on G1 from bytes in an uncompressed form.
function g1Marshal(struct AltBn128.G1Point point) internal pure returns (bytes)
Marshals a point on G1 to bytes form.
function g2Unmarshal(bytes m) internal pure returns (struct AltBn128.G2Point)
Unmarshals a point on G2 from bytes in an uncompressed form.
function g2Decompress(bytes m) internal pure returns (struct AltBn128.G2Point)
Decompress a point on G2 from a pair of uint256.
function gfP2Add(struct AltBn128.gfP2 a, struct AltBn128.gfP2 b) internal pure returns (struct AltBn128.gfP2)
Returns the sum of two gfP2 field elements.
function gfP2Multiply(struct AltBn128.gfP2 a, struct AltBn128.gfP2 b) internal pure returns (struct AltBn128.gfP2)
Returns multiplication of two gfP2 field elements.
function gfP2Pow(struct AltBn128.gfP2 _a, uint256 _exp) internal pure returns (struct AltBn128.gfP2 result)
Returns gfP2 element to the power of the provided exponent.
function gfP2Square(struct AltBn128.gfP2 a) internal pure returns (struct AltBn128.gfP2)
function gfP2Cube(struct AltBn128.gfP2 a) internal pure returns (struct AltBn128.gfP2)
function gfP2CubeAddTwistB(struct AltBn128.gfP2 a) internal pure returns (struct AltBn128.gfP2)
function g2X2y(struct AltBn128.gfP2 x, struct AltBn128.gfP2 y) internal pure returns (bool)
Returns true if G2 point's y^2 equals x.
function isG2PointOnCurve(struct AltBn128.G2Point point) internal pure returns (bool)
Returns true if G2 point is on the curve.
The Initial Protocol Loan (IPL) is a debt issued against the protocol itself in order to fund the stability pool. Even though it's technically debt, it does not in itself make the protocol a fractional reserve because all funds are accounted for.
thUSD is minted by the PCV and deposited into the stability pool. A freeze on withdraw is initiated until debt is fully repaid. At any time, the Threshold DAO can initiate a withdraw, which will destruct the debt and withdraw exceeds. If the PCV for some reason has less than the debt available, withdraw is denied.
Profits from usage of the protocol (loan interest, redemption fee, etc) accrues into the PCV, so in an event where there are less funds than debt the protocol first has to earn back the missing funds.
Benefits of the hybrid PCV + IPL model:
Bootstrap stability pool for free
No need for a Protocol token = all profits accrue directly to the PCV
Immediate surplus on first loan drawn / liquidation
Predictability and (once debt is repaid) high resilience against Black Swans
No idle capital (it doesn't "really" exist)
Disadvantages
Higher risk of under-collateralization during the initial stages
Poor management of PCV could result in not enough funds in the stability pool
Each liquidation that occurs will draw against the stability pool and result in ~10% profit to the stability pool. This also means that the price can drop up to 10% after a liquidation before the stability pool is at risk of losing money on the liquidation. Our integration with B.Protocol ensures that liquidated tBTC is automatically converted back to thUSD and re-deposited into the stability pool. Under normal circumstances and without other factors, it is expected that the balance of thUSD in the pool will grow over time.
But there can be Black Swan events, large drops in the price of BTC in a short period of time or liquidity issues that results in a loss, even at ~10% profit. This is a risk of the protocol becoming undercollateralized, but the same issue would apply even without debt (stability pool not being profitable). The protocol is created to be sustainable, so these types of rare events will be averaged out. In addition, all income streams from Threshold USD also ends up in the PCV which further decrease the risk of undercollaterization.
The undercollaterialization is mostly a concern during the initial bootstrapping phase. As interest accrue and repay the initial debt, the funds in the stability pool will be replaced by fully owned PVC, thus eliminating this disadvantage.
Interface for Keep TokenStaking contract
Seize provided token amount from every member in the misbehaved operators array. The tattletale is rewarded with 5% of the total seized amount scaled by the reward adjustment parameter and the rest 95% is burned.
Gets stake delegation info for the given operator.
Gets the stake owner for the specified operator address.
Gets the beneficiary for the specified operator address.
Gets the authorizer for the specified operator address.
Gets the eligible stake balance of the specified address. An eligible stake is a stake that passed the initialization period and is not currently undelegating. Also, the operator had to approve the specified operator contract.
Operator with a minimum required amount of eligible stake can join the network and participate in new work selection.
Interface for NuCypher StakingEscrow contract
Slash the staker's stake and reward the investigator
Request merge between NuCypher staking contract and T staking contract. Returns amount of staked tokens
Get all tokens belonging to the staker
This file documents a contract which is not yet deployed to Mainnet.
DKGValidator allows performing a full validation of DKG result, including checking the format of fields in the result, declared selected group members, and signatures of operators supporting the result. The operator submitting the result should perform the validation using a free contract call before submitting the result to ensure their result is valid and can not be challenged. All other network operators should perform validation of the submitted result using a free contract call and challenge the result if the validation fails.
Size of a group in the threshold relay.
The minimum number of group members needed to interact according to the protocol to produce a relay entry. The adversary can not learn anything about the key as long as it does not break into groupThreshold+1 of members.
The minimum number of active and properly behaving group members during the DKG needed to accept the result. This number is higher than groupThreshold
to keep a safety margin for members becoming inactive after DKG so that the group can still produce a relay entry.
Size in bytes of a single signature produced by operator supporting DKG result.
Performs a full validation of DKG result, including checking the format of fields in the result, declared selected group members, and signatures of operators supporting the result.
Performs a static validation of DKG result fields: lengths, ranges, and order of arrays.
Performs validation of group members as declared in DKG result against group members selected by the sortition pool.
Performs validation of signatures supplied in DKG result. Note that this function does not check if addresses which supplied signatures supporting the result are the ones selected to the group by sortition pool. This function should be used together with validateGroupMembers
.
Performs validation of hashed group members that actively took part in DKG.
This file documents a contract which is not yet deployed to Mainnet.
Authorized contracts that can interact with the reimbursment pool. Authorization can be granted and removed by the owner.
Static gas includes:
cost of the refund function
base transaction cost
Max gas price used to reimburse a transaction submitter. Protects against malicious operator-miners.
Receive ETH
Refunds ETH to a spender for executing specific transactions.
Ignoring the result of sending ETH to a receiver is made on purpose. For EOA receiving ETH should always work. If a receiver is a smart contract, then we do not want to fail a transaction, because in some cases the refund is done at the very end of multiple calls where all the previous calls were already paid off. It is a receiver's smart contract resposibility to make sure it can receive ETH. Only authorized contracts are allowed calling this function.
Authorize a contract that can interact with this reimbursment pool. Can be authorized by the owner only.
Unauthorize a contract that was previously authorized to interact with this reimbursment pool. Can be unauthorized by the owner only.
Setting a static gas cost for executing a transaction. Can be set by the owner only.
Setting a max gas price for transactions. Can be set by the owner only.
Withdraws all ETH from this pool which are sent to a given address. Can be set by the owner only.
Withdraws ETH amount from this pool which are sent to a given address. Can be set by the owner only.
This file documents a contract which is not yet deployed to Mainnet.
Library for verification of 2-pairing-check BLS signatures, including basic, aggregated, or reconstructed threshold BLS signatures, generated using the AltBn128 curve.
Creates a signature over message using the provided secret key.
Wraps the functionality of BLS.verify, but hashes a message to a point on G1 and marshal to bytes first to allow raw bytes verification.
Verify performs the pairing operation to check if the signature is correct for the provided message and the corresponding public key. Public key must be a valid point on G2 curve in an uncompressed format. Message must be a valid point on G1 curve in an uncompressed format. Signature must be a valid point on G1 curve in an uncompressed format.
function seize(uint256 amountToSeize, uint256 rewardMultiplier, address tattletale, address[] misbehavedOperators) external
amountToSeize
uint256
Token amount to seize from every misbehaved operator.
rewardMultiplier
uint256
Reward adjustment in percentage. Min 1% and 100% max.
tattletale
address
Address to receive the 5% reward.
misbehavedOperators
address[]
Array of addresses to seize the tokens from.
function getDelegationInfo(address operator) external view returns (uint256 amount, uint256 createdAt, uint256 undelegatedAt)
operator
address
Operator address.
amount
uint256
The amount of tokens the given operator delegated.
createdAt
uint256
The time when the stake has been delegated.
undelegatedAt
uint256
The time when undelegation has been requested. If undelegation has not been requested, 0 is returned.
function ownerOf(address operator) external view returns (address)
[0]
address
Stake owner address.
function beneficiaryOf(address operator) external view returns (address payable)
[0]
address payable
Beneficiary address.
function authorizerOf(address operator) external view returns (address)
[0]
address
Authorizer address.
function eligibleStake(address operator, address operatorContract) external view returns (uint256 balance)
operator
address
address of stake operator.
operatorContract
address
address of operator contract.
balance
uint256
an uint256 representing the eligible stake balance.
function slashStaker(address staker, uint256 penalty, address investigator, uint256 reward) external
staker
address
Staker's address
penalty
uint256
Penalty
investigator
address
Investigator
reward
uint256
Reward for the investigator
function requestMerge(address staker, address stakingProvider) external returns (uint256)
function getAllTokens(address staker) external view returns (uint256)
uint256 groupSize
uint256 groupThreshold
uint256 activeThreshold
uint256 signatureByteSize
contract SortitionPool sortitionPool
constructor(contract SortitionPool _sortitionPool) public
function validate(struct BeaconDkg.Result result, uint256 seed, uint256 startBlock) external view returns (bool isValid, string errorMsg)
result
struct BeaconDkg.Result
seed
uint256
seed used to start the DKG and select group members
startBlock
uint256
DKG start block
isValid
bool
true if the result is valid, false otherwise
errorMsg
string
validation error message; empty for a valid result
function validateFields(struct BeaconDkg.Result result) public pure returns (bool isValid, string errorMsg)
isValid
bool
true if the result is valid, false otherwise
errorMsg
string
validation error message; empty for a valid result
function validateGroupMembers(struct BeaconDkg.Result result, uint256 seed) public view returns (bool)
result
struct BeaconDkg.Result
seed
uint256
seed used to start the DKG and select group members
[0]
bool
true if group members matches; false otherwise
function validateSignatures(struct BeaconDkg.Result result, uint256 startBlock) public view returns (bool)
result
struct BeaconDkg.Result
startBlock
uint256
DKG start block
[0]
bool
true if group members matches; false otherwise
function validateMembersHash(struct BeaconDkg.Result result) public pure returns (bool)
result
struct BeaconDkg.Result
DKG result
[0]
bool
true if result's group members hash matches with the one that is challenged.
mapping(address => bool) isAuthorized
uint256 staticGas
uint256 maxGasPrice
event StaticGasUpdated(uint256 newStaticGas)
event MaxGasPriceUpdated(uint256 newMaxGasPrice)
event SendingEtherFailed(uint256 refundAmount, address receiver)
event AuthorizedContract(address thirdPartyContract)
event UnauthorizedContract(address thirdPartyContract)
event FundsWithdrawn(uint256 withdrawnAmount, address receiver)
constructor(uint256 _staticGas, uint256 _maxGasPrice) public
receive() external payable
function refund(uint256 gasSpent, address receiver) external
gasSpent
uint256
Gas spent on a transaction that needs to be reimbursed.
receiver
address
Address where the reimbursment is sent.
function authorize(address _contract) external
_contract
address
Authorized contract.
function unauthorize(address _contract) external
_contract
address
Authorized contract.
function setStaticGas(uint256 _staticGas) external
_staticGas
uint256
Static gas cost.
function setMaxGasPrice(uint256 _maxGasPrice) external
_maxGasPrice
uint256
Max gas price used to reimburse tx submitters.
function withdrawAll(address receiver) external
receiver
address
An address where ETH is sent.
function withdraw(uint256 amount, address receiver) public
amount
uint256
Amount to withdraw from the pool.
receiver
address
An address where ETH is sent.
function sign(bytes message, uint256 secretKey) external view returns (bytes)
function verifyBytes(bytes publicKey, bytes message, bytes signature) external view returns (bool)
function verify(bytes publicKey, bytes message, bytes signature) public view returns (bool)
function _verify(struct AltBn128.G2Point publicKey, struct AltBn128.G1Point message, struct AltBn128.G1Point signature) public view returns (bool)
Abstract contract to support checkpoints for Compound-like voting and delegation. This implementation supports token supply up to 2^96 - 1. This contract keeps a history (checkpoints) of each account's vote power. Vote power can be delegated either by calling the {delegate} function directly, or by providing a signature to be used with {delegateBySig}. Voting power can be publicly queried through {getVotes} and {getPastVotes}. NOTE: Extracted from OpenZeppelin ERCVotes.sol. This contract is upgrade-safe.
struct Checkpoint {
uint32 fromBlock;
uint96 votes;
}
mapping(address => address) _delegates
mapping(address => uint128[]) _checkpoints
uint128[] _totalSupplyCheckpoints
event DelegateChanged(address delegator, address fromDelegate, address toDelegate)
Emitted when an account changes their delegate.
event DelegateVotesChanged(address delegate, uint256 previousBalance, uint256 newBalance)
Emitted when a balance or delegate change results in changes to an account's voting power.
function checkpoints(address account, uint32 pos) public view virtual returns (struct Checkpoints.Checkpoint checkpoint)
function numCheckpoints(address account) public view virtual returns (uint32)
Get number of checkpoints for account
.
function delegates(address account) public view virtual returns (address)
Get the address account
is currently delegating to.
function getVotes(address account) public view returns (uint96)
Gets the current votes balance for account
.
account
address
The address to get votes balance
[0]
uint96
The number of current votes for account
function getPastVotes(address account, uint256 blockNumber) public view returns (uint96)
Determine the prior number of votes for an account as of a block number.
Block number must be a finalized block or else this function will revert to prevent misinformation.
account
address
The address of the account to check
blockNumber
uint256
The block number to get the vote balance at
[0]
uint96
The number of votes the account had as of the given block
function getPastTotalSupply(uint256 blockNumber) public view returns (uint96)
Retrieve the totalSupply
at the end of blockNumber
. Note, this value is the sum of all balances, but it is NOT the sum of all the delegated votes!
blockNumber
must have been already mined
blockNumber
uint256
The block number to get the total supply at
function delegate(address delegator, address delegatee) internal virtual
Change delegation for delegator
to delegatee
.
function moveVotingPower(address src, address dst, uint256 amount) internal
Moves voting power from one delegate to another
src
address
Address of old delegate
dst
address
Address of new delegate
amount
uint256
Voting power amount to transfer between delegates
function writeCheckpoint(uint128[] ckpts, function (uint256,uint256) view returns (uint256) op, uint256 delta) internal returns (uint256 oldWeight, uint256 newWeight)
Writes a new checkpoint based on operating last stored value with a delta
. Usually, said operation is the add
or subtract
functions from this contract, but more complex functions can be passed as parameters.
ckpts
uint128[]
The checkpoints array to use
op
function (uint256,uint256) view returns (uint256)
The function to apply over the last value and the delta
delta
uint256
Variation with respect to last stored value to be used for new checkpoint
function lookupCheckpoint(uint128[] ckpts, uint256 blockNumber) internal view returns (uint96)
Lookup a value in a list of (sorted) checkpoints.
ckpts
uint128[]
The checkpoints array to use
blockNumber
uint256
Block number when we want to get the checkpoint at
function maxSupply() internal view virtual returns (uint96)
Maximum token supply. Defaults to type(uint96).max
(2^96 - 1)
function encodeCheckpoint(uint32 blockNumber, uint96 value) internal pure returns (uint128)
Encodes a blockNumber
and value
into a single uint128
checkpoint.
blockNumber
is stored in the first 32 bits, while value
in the remaining 96 bits.
function decodeBlockNumber(uint128 checkpoint) internal pure returns (uint32)
Decodes a block number from a uint128
checkpoint
.
function decodeValue(uint128 checkpoint) internal pure returns (uint96)
Decodes a voting value from a uint128
checkpoint
.
function decodeCheckpoint(uint128 checkpoint) internal pure returns (uint32 blockNumber, uint96 value)
Decodes a block number and voting value from a uint128
checkpoint
.
function add(uint256 a, uint256 b) internal pure returns (uint256)
function subtract(uint256 a, uint256 b) internal pure returns (uint256)
The following demonstrates how to authorize applications for tBTC v2 and how to register your operator address via the Threshold Dashboard.
One important step to get your node operating on the Threshold Network is proper application authorization as well as operator account registration. Applications need only be authorized once.
It is CRITICALLY important that both the tBTC application as well as the Random Beacon applications are authorized. A node cannot be deployed without both applications being properly authorized.
Please note: by authorizing these applications, an unbonding period of 45 days will go into effect on the T you stake for these applications. This cool-down period begins the day you submit an unstake request.
To get started, visit the Threshold Dashboard and connect your wallet.
Click on "Configure Apps"
Select BOTH tBTC and Random Beacon applications and enter your desired amount of T to stake per application. Note that the minimum is 40,000T.
The operator account is the Ethereum account created on your node. In order for the network to associate your T stake with your node, you must register your Operator Address.
After both applications have been authorized, click on "Start Mapping" to begin the Operator Registration process.
Enter your Operator Address in the field provided and click on "Map Address."
Once the steps above have been successfully completed, you are ready to move on to the next step in the node deployment process.
Don't forget: a tBTC v2 node will not be able to be deployed without successfully authorizing both the tBTC and Random Beacon applications, and registering the node's operator address FIRST.
This file documents a contract which is not yet deployed to Mainnet.
This library is used as a registry of created groups.
This library should be used along with DKG library that ensures linear groups creation (only one group creation happens at a time). A candidate group has to be popped or activated before adding a new candidate group.
Performs preliminary validation of a new group public key. The group public key must be unique and have 128 bytes in length. If the validation fails, the function reverts. This function must be called first for a public key of a group added with addGroup
function.
Adds a new candidate group. The group is stored with group public key and group members, but is not yet activated.
The group members list is stored with all misbehaved members filtered out. The code calling this function should ensure that the number of candidate (not activated) groups is never more than one.
Goes through groups starting from the oldest one that is still active and checks if it hasn't expired. If so, updates the information about expired groups so that all expired groups are marked as such.
Terminates group with the provided index. Reverts if the group is already terminated.
Returns an index of a randomly selected active group. Terminated and expired groups are not considered as active. Before new group is selected, information about expired groups is updated. At least one active group needs to be present for this function to succeed.
Setter for group lifetime.
Checks if group with the given index is terminated.
Gets the cutoff time until which the given group is considered to be active assuming it hasn't been terminated before.
Checks if group with the given index is active and non-terminated.
Gets the number of active groups. Expired and terminated groups are not counted as active.
Evaluates the shift of a selected group index based on the number of expired groups.
Evaluates the shift of a selected group index based on the number of non-expired but terminated groups.
struct Group {
bytes groupPubKey;
uint256 registrationBlockNumber;
bytes32 membersHash;
bool terminated;
}
struct Data {
mapping(bytes32 => struct Groups.Group) groupsData;
bytes32[] groupsRegistry;
uint64[] activeTerminatedGroups;
uint64 expiredGroupOffset;
uint256 groupLifetime;
}
event GroupRegistered(uint64 groupId, bytes groupPubKey)
function validatePublicKey(struct Groups.Data self, bytes groupPubKey) internal view
self
struct Groups.Data
groupPubKey
bytes
Candidate group public key
function addGroup(struct Groups.Data self, bytes groupPubKey, bytes32 membersHash) internal
self
struct Groups.Data
groupPubKey
bytes
Generated candidate group public key
membersHash
bytes32
Keccak256 hash of members that actively took part in DKG.
function expireOldGroups(struct Groups.Data self) internal
function terminateGroup(struct Groups.Data self, uint64 groupId) internal
self
struct Groups.Data
groupId
uint64
Index in the groupRegistry array.
function selectGroup(struct Groups.Data self, uint256 seed) internal returns (uint64)
self
struct Groups.Data
seed
uint256
Random number used as a group selection seed.
function setGroupLifetime(struct Groups.Data self, uint256 lifetime) internal
self
struct Groups.Data
lifetime
uint256
Lifetime of a group in blocks.
function isGroupTerminated(struct Groups.Data self, uint64 groupId) internal view returns (bool)
function groupLifetimeOf(struct Groups.Data self, bytes32 groupPubKeyHash) internal view returns (uint256)
function isGroupActive(struct Groups.Data self, uint64 groupId) internal view returns (bool)
function getGroup(struct Groups.Data self, uint64 groupId) internal view returns (struct Groups.Group)
function getGroup(struct Groups.Data self, bytes groupPubKey) internal view returns (struct Groups.Group)
function numberOfActiveGroups(struct Groups.Data self) internal view returns (uint64)
function shiftByExpiredGroups(struct Groups.Data self, uint64 selectedIndex) internal view returns (uint64)
function shiftByTerminatedGroups(struct Groups.Data self, uint64 selectedIndex) internal view returns (uint64)
The staking contract enables T owners to have their wallets offline and their stake managed by staking providers on their behalf. The staking contract does not define operator role. The operator responsible for running off-chain client software is appointed by the staking provider in the particular application utilizing the staking contract. All off-chain client software should be able to run without exposing operator's or staking provider’s private key and should not require any owner’s keys at all. The stake delegation optimizes the network throughput without compromising the security of the owners’ stake.
enum StakeType {
NU,
KEEP,
T
}
function stake(address stakingProvider, address payable beneficiary, address authorizer, uint96 amount) external
Creates a delegation with msg.sender
owner with the given staking provider, beneficiary, and authorizer. Transfers the given amount of T to the staking contract.
The owner of the delegation needs to have the amount approved to transfer to the staking contract.
function stakeKeep(address stakingProvider) external
Copies delegation from the legacy KEEP staking contract to T staking contract. No tokens are transferred. Caches the active stake amount from KEEP staking contract. Can be called by anyone.
The staking provider in T staking contract is the legacy KEEP staking contract operator.
function stakeNu(address stakingProvider, address payable beneficiary, address authorizer) external
Copies delegation from the legacy NU staking contract to T staking contract, additionally appointing staking provider, beneficiary and authorizer roles. Caches the amount staked in NU staking contract. Can be called only by the original delegation owner.
function setMinimumStakeAmount(uint96 amount) external
Allows the Governance to set the minimum required stake amount. This amount is required to protect against griefing the staking contract and individual applications are allowed to require higher minimum stakes if necessary.
function approveApplication(address application) external
Allows the Governance to approve the particular application before individual stake authorizers are able to authorize it.
function increaseAuthorization(address stakingProvider, address application, uint96 amount) external
Increases the authorization of the given staking provider for the given application by the given amount. Can only be called by the authorizer for that staking provider.
Calls authorizationIncreased(address stakingProvider, uint256 amount)
on the given application to notify the application about authorization change. See IApplication
.
function requestAuthorizationDecrease(address stakingProvider, address application, uint96 amount) external
Requests decrease of the authorization for the given staking provider on the given application by the provided amount. It may not change the authorized amount immediatelly. When it happens depends on the application. Can only be called by the given staking provider’s authorizer. Overwrites pending authorization decrease for the given staking provider and application if the application agrees for that. If the application does not agree for overwriting, the function reverts.
Calls authorizationDecreaseRequested(address stakingProvider, uint256 amount)
on the given application. See IApplication
.
function requestAuthorizationDecrease(address stakingProvider) external
Requests decrease of all authorizations for the given staking provider on all applications by all authorized amount. It may not change the authorized amount immediatelly. When it happens depends on the application. Can only be called by the given staking provider’s authorizer. Overwrites pending authorization decrease for the given staking provider and application.
Calls authorizationDecreaseRequested(address stakingProvider, uint256 amount)
for each authorized application. See IApplication
.
function approveAuthorizationDecrease(address stakingProvider) external returns (uint96)
Called by the application at its discretion to approve the previously requested authorization decrease request. Can only be called by the application that was previously requested to decrease the authorization for that staking provider. Returns resulting authorized amount for the application.
function forceDecreaseAuthorization(address stakingProvider, address application) external
Decreases the authorization for the given stakingProvider
on the given disabled application
, for all authorized amount. Can be called by anyone.
function pauseApplication(address application) external
Pauses the given application’s eligibility to slash stakes. Besides that stakers can't change authorization to the application. Can be called only by the Panic Button of the particular application. The paused application can not slash stakes until it is approved again by the Governance using approveApplication
function. Should be used only in case of an emergency.
function disableApplication(address application) external
Disables the given application. The disabled application can't slash stakers. Also stakers can't increase authorization to that application but can decrease without waiting by calling requestAuthorizationDecrease
at any moment. Can be called only by the governance. The disabled application can't be approved again. Should be used only in case of an emergency.
function setPanicButton(address application, address panicButton) external
Sets the Panic Button role for the given application to the provided address. Can only be called by the Governance. If the Panic Button for the given application should be disabled, the role address should be set to 0x0 address.
function setAuthorizationCeiling(uint256 ceiling) external
Sets the maximum number of applications one staking provider can have authorized. Used to protect against DoSing slashing queue. Can only be called by the Governance.
function topUp(address stakingProvider, uint96 amount) external
Increases the amount of the stake for the given staking provider.
The sender of this transaction needs to have the amount approved to transfer to the staking contract.
function topUpKeep(address stakingProvider) external
Propagates information about stake top-up from the legacy KEEP staking contract to T staking contract. Can be called only by the owner or the staking provider.
function topUpNu(address stakingProvider) external
Propagates information about stake top-up from the legacy NU staking contract to T staking contract. Can be called only by the owner or the staking provider.
function unstakeT(address stakingProvider, uint96 amount) external
Reduces the liquid T stake amount by the provided amount and withdraws T to the owner. Reverts if there is at least one authorization higher than the sum of the legacy stake and remaining liquid T stake or if the unstake amount is higher than the liquid T stake amount. Can be called only by the delegation owner or the staking provider.
function unstakeKeep(address stakingProvider) external
Sets the legacy KEEP staking contract active stake amount cached in T staking contract to 0. Reverts if the amount of liquid T staked in T staking contract is lower than the highest application authorization. This function allows to unstake from KEEP staking contract and still being able to operate in T network and earning rewards based on the liquid T staked. Can be called only by the delegation owner or the staking provider.
function unstakeNu(address stakingProvider, uint96 amount) external
Reduces cached legacy NU stake amount by the provided amount. Reverts if there is at least one authorization higher than the sum of remaining legacy NU stake and liquid T stake for that staking provider or if the untaked amount is higher than the cached legacy stake amount. If succeeded, the legacy NU stake can be partially or fully undelegated on the legacy staking contract. This function allows to unstake from NU staking contract and still being able to operate in T network and earning rewards based on the liquid T staked. Can be called only by the delegation owner or the staking provider.
function unstakeAll(address stakingProvider) external
Sets cached legacy stake amount to 0, sets the liquid T stake amount to 0 and withdraws all liquid T from the stake to the owner. Reverts if there is at least one non-zero authorization. Can be called only by the delegation owner or the staking provider.
function notifyKeepStakeDiscrepancy(address stakingProvider) external
Notifies about the discrepancy between legacy KEEP active stake and the amount cached in T staking contract. Slashes the staking provider in case the amount cached is higher than the actual active stake amount in KEEP staking contract. Needs to update authorizations of all affected applications and execute an involuntary allocation decrease on all affected applications. Can be called by anyone, notifier receives a reward.
function notifyNuStakeDiscrepancy(address stakingProvider) external
Notifies about the discrepancy between legacy NU active stake and the amount cached in T staking contract. Slashes the staking provider in case the amount cached is higher than the actual active stake amount in NU staking contract. Needs to update authorizations of all affected applications and execute an involuntary allocation decrease on all affected applications. Can be called by anyone, notifier receives a reward.
function setStakeDiscrepancyPenalty(uint96 penalty, uint256 rewardMultiplier) external
Sets the penalty amount for stake discrepancy and reward multiplier for reporting it. The penalty is seized from the delegated stake, and 5% of the penalty, scaled by the multiplier, is given to the notifier. The rest of the tokens are burned. Can only be called by the Governance. See seize
function.
function setNotificationReward(uint96 reward) external
Sets reward in T tokens for notification of misbehaviour of one staking provider. Can only be called by the governance.
function pushNotificationReward(uint96 reward) external
Transfer some amount of T tokens as reward for notifications of misbehaviour
function withdrawNotificationReward(address recipient, uint96 amount) external
Withdraw some amount of T tokens from notifiers treasury. Can only be called by the governance.
function slash(uint96 amount, address[] stakingProviders) external
Adds staking providers to the slashing queue along with the amount that should be slashed from each one of them. Can only be called by application authorized for all staking providers in the array.
function seize(uint96 amount, uint256 rewardMultipier, address notifier, address[] stakingProviders) external
Adds staking providers to the slashing queue along with the amount. The notifier will receive reward per each staking provider from notifiers treasury. Can only be called by application authorized for all staking providers in the array.
function processSlashing(uint256 count) external
Takes the given number of queued slashing operations and processes them. Receives 5% of the slashed amount. Executes involuntaryAllocationDecrease
function on each affected application.
function authorizedStake(address stakingProvider, address application) external view returns (uint96)
Returns the authorized stake amount of the staking provider for the application.
function stakes(address stakingProvider) external view returns (uint96 tStake, uint96 keepInTStake, uint96 nuInTStake)
Returns staked amount of T, Keep and Nu for the specified staking provider.
All values are in T denomination
function getStartStakingTimestamp(address stakingProvider) external view returns (uint256)
Returns start staking timestamp.
This value is set at most once.
function stakedNu(address stakingProvider) external view returns (uint256)
Returns staked amount of NU for the specified staking provider.
function rolesOf(address stakingProvider) external view returns (address owner, address payable beneficiary, address authorizer)
Gets the stake owner, the beneficiary and the authorizer for the specified staking provider address.
owner
address
Stake owner address.
beneficiary
address payable
Beneficiary address.
authorizer
address
Authorizer address.
function getApplicationsLength() external view returns (uint256)
Returns length of application array
function getSlashingQueueLength() external view returns (uint256)
Returns length of slashing queue
function getMinStaked(address stakingProvider, enum IStaking.StakeType stakeTypes) external view returns (uint96)
Returns minimum possible stake for T, KEEP or NU in T denomination.
For example, suppose the given staking provider has 10 T, 20 T worth of KEEP, and 30 T worth of NU all staked, and the maximum application authorization is 40 T, then getMinStaked
for that staking provider returns:
0 T if KEEP stake type specified i.e. min = 40 T max - (10 T + 30 T worth of NU) = 0 T
10 T if NU stake type specified i.e. min = 40 T max - (10 T + 20 T worth of KEEP) = 10 T
0 T if T stake type specified i.e. min = 40 T max - (20 T worth of KEEP + 30 T worth of NU) < 0 T In other words, the minimum stake amount for the specified stake type is the minimum amount of stake of the given type needed to satisfy the maximum application authorization given the staked amounts of the other stake types for that staking provider.
function getAvailableToAuthorize(address stakingProvider, address application) external view returns (uint96)
Returns available amount to authorize for the specified application
This file documents a contract which is not yet deployed to Mainnet.
Size of a group in the threshold relay.
Time in blocks after which DKG result is complete and ready to be
Initializes SortitionPool and DKGValidator addresses. Can be performed only once.
Determines the current state of group creation. It doesn't take timeouts into consideration. The timeouts should be tracked and notified separately.
Locks the sortition pool and starts awaiting for the group creation seed.
Allows to submit a DKG result. The submitted result does not go through a validation and before it gets accepted, it needs to wait through the challenge period during which everyone has a chance to challenge the result as invalid one. Submitter of the result needs to be in the sortition pool and if the result gets challenged, the submitter will get slashed.
Checks if DKG timed out. The DKG timeout period includes time required for off-chain protocol execution and time for the result publication. After this time a result cannot be submitted and DKG can be notified about the timeout. DKG period is adjusted by result submission offset that include blocks that were mined while invalid result has been registered until it got challenged.
Notifies about DKG timeout.
Notifies about the seed was not delivered and restores the initial DKG state (IDLE).
Approves DKG result. Can be called when the challenge period for the submitted result is finished. Considers the submitted result as valid. For the first submitterPrecedencePeriodLength
blocks after the end of the challenge period can be called only by the DKG result submitter. After that time, can be called by anyone.
Can be called after a challenge period for the submitted result.
Challenges DKG result. If the submitted result is proved to be invalid it reverts the DKG back to the result submission phase.
Can be called during a challenge period for the submitted result.
Due to EIP150, 1/64 of the gas is not forwarded to the call, and will be kept to execute the remaining operations in the function after the call inside the try-catch.
To ensure there is no way for the caller to manipulate gas limit in such a way that the call inside try-catch fails with out-of-gas and the rest of the function is executed with the remaining 1/64 of gas, we require an extra gas amount to be left at the end of the call to the function challenging DKG result and wrapping the call to BeaconDkgValidator and TokenStaking contracts inside a try-catch.
Updates DKG-related parameters
Completes DKG by cleaning up state.
Should be called after DKG times out or a result is approved.
This file documents a contract which is not yet deployed to Mainnet.
Seed used as the first relay entry value. It's a G1 point G * PI = G * 31415926535897932384626433832795028841971693993751058209749445923078164062862 Where G is the generator of G1 abstract cyclic group.
Initializes the very first previousEntry
with an initial relaySeed
value. Can be performed only once.
Creates a request to generate a new relay entry, which will include a random number (by signing the previous entry's random number).
Creates a new relay entry. Gas-optimized version that can be called only before the soft timeout. This should be the majority of cases.
Creates a new relay entry. Can be called at any time. In case the soft timeout has not been exceeded, it is more gas-efficient to call the second variation of submitEntry
.
Calculates the slashing amount for all group members.
Must be used when a soft timeout was hit.
Updates relay-related parameters
Set relayEntrySubmissionFailureSlashingAmount parameter.
Retries the current relay request in case a relay entry timeout was reported.
Cleans up the current relay request in case a relay entry timeout was reported.
Returns whether a relay entry request is currently in progress.
Returns whether the current relay request has timed out.
Calculates soft timeout block for the pending relay request.
struct Parameters {
uint256 resultChallengePeriodLength;
uint256 resultChallengeExtraGas;
uint256 resultSubmissionTimeout;
uint256 submitterPrecedencePeriodLength;
}
struct Data {
contract SortitionPool sortitionPool;
contract BeaconDkgValidator dkgValidator;
struct BeaconDkg.Parameters parameters;
uint256 startBlock;
uint256 seed;
uint256 resultSubmissionStartBlockOffset;
bytes32 submittedResultHash;
uint256 submittedResultBlock;
}
struct Result {
uint256 submitterMemberIndex;
bytes groupPubKey;
uint8[] misbehavedMembersIndices;
bytes signatures;
uint256[] signingMembersIndices;
uint32[] members;
bytes32 membersHash;
}
enum State {
IDLE,
AWAITING_SEED,
KEY_GENERATION,
AWAITING_RESULT,
CHALLENGE
}
uint256 groupSize
uint256 offchainDkgTime
event DkgStarted(uint256 seed)
event DkgResultSubmitted(bytes32 resultHash, uint256 seed, struct BeaconDkg.Result result)
event DkgTimedOut()
event DkgResultApproved(bytes32 resultHash, address approver)
event DkgResultChallenged(bytes32 resultHash, address challenger, string reason)
event DkgStateLocked()
event DkgSeedTimedOut()
function init(struct BeaconDkg.Data self, contract SortitionPool _sortitionPool, contract BeaconDkgValidator _dkgValidator) internal
self
struct BeaconDkg.Data
_sortitionPool
contract SortitionPool
Sortition Pool reference
_dkgValidator
contract BeaconDkgValidator
DKGValidator reference
function currentState(struct BeaconDkg.Data self) internal view returns (enum BeaconDkg.State state)
function lockState(struct BeaconDkg.Data self) internal
function start(struct BeaconDkg.Data self, uint256 seed) internal
function submitResult(struct BeaconDkg.Data self, struct BeaconDkg.Result result) external
function hasDkgTimedOut(struct BeaconDkg.Data self) internal view returns (bool)
[0]
bool
True if DKG timed out, false otherwise.
function notifyTimeout(struct BeaconDkg.Data self) internal
function notifySeedTimedOut(struct BeaconDkg.Data self) internal
function approveResult(struct BeaconDkg.Data self, struct BeaconDkg.Result result) external returns (uint32[] misbehavedMembers)
self
struct BeaconDkg.Data
result
struct BeaconDkg.Result
Result to approve. Must match the submitted result stored during submitResult
.
misbehavedMembers
uint32[]
Identifiers of members who misbehaved during DKG.
function challengeResult(struct BeaconDkg.Data self, struct BeaconDkg.Result result) external returns (bytes32 maliciousResultHash, uint32 maliciousSubmitter)
self
struct BeaconDkg.Data
result
struct BeaconDkg.Result
Result to challenge. Must match the submitted result stored during submitResult
.
maliciousResultHash
bytes32
Hash of the malicious result.
maliciousSubmitter
uint32
Identifier of the malicious submitter.
function requireChallengeExtraGas(struct BeaconDkg.Data self) internal view
function setParameters(struct BeaconDkg.Data self, uint256 _resultChallengePeriodLength, uint256 _resultChallengeExtraGas, uint256 _resultSubmissionTimeout, uint256 _submitterPrecedencePeriodLength) internal
self
struct BeaconDkg.Data
_resultChallengePeriodLength
uint256
New value of the result challenge period length. It is the number of blocks for which a DKG result can be challenged.
_resultChallengeExtraGas
uint256
New value of the result challenge extra gas. It is the extra gas required to be left at the end of the challenge DKG result transaction.
_resultSubmissionTimeout
uint256
New value of the result submission timeout in seconds. It is a timeout for a group to provide a DKG result.
_submitterPrecedencePeriodLength
uint256
New value of the submitter precedence period length in blocks. It is the time during which only the original DKG result submitter can approve it.
function complete(struct BeaconDkg.Data self) internal
struct Data {
uint64 requestCount;
uint64 currentRequestID;
uint64 currentRequestGroupID;
uint64 currentRequestStartBlock;
struct AltBn128.G1Point previousEntry;
uint32 relayEntrySoftTimeout;
uint32 relayEntryHardTimeout;
uint96 relayEntrySubmissionFailureSlashingAmount;
}
bytes relaySeed
event RelayEntryRequested(uint256 requestId, uint64 groupId, bytes previousEntry)
event RelayEntrySubmitted(uint256 requestId, address submitter, bytes entry)
event RelayEntryTimedOut(uint256 requestId, uint64 terminatedGroupId)
function initSeedEntry(struct Relay.Data self) internal
function requestEntry(struct Relay.Data self, uint64 groupId) internal
self
struct Relay.Data
groupId
uint64
Identifier of the group chosen to handle the request.
function submitEntryBeforeSoftTimeout(struct Relay.Data self, bytes entry, bytes groupPubKey) internal
self
struct Relay.Data
entry
bytes
Group BLS signature over the previous entry.
groupPubKey
bytes
Public key of the group which signed the relay entry.
function submitEntry(struct Relay.Data self, bytes entry, bytes groupPubKey) internal returns (uint96)
self
struct Relay.Data
entry
bytes
Group BLS signature over the previous entry.
groupPubKey
bytes
Public key of the group which signed the relay entry.
[0]
uint96
slashingAmount Amount by which group members should be slashed in case the relay entry was submitted after the soft timeout. The value is zero if entry was submitted on time.
function _submitEntry(struct Relay.Data self, bytes entry, bytes groupPubKey) internal
function calculateSlashingAmount(struct Relay.Data self) internal view returns (uint96)
[0]
uint96
Amount by which group members should be slashed in case the relay entry was submitted after the soft timeout.
function setTimeouts(struct Relay.Data self, uint256 _relayEntrySoftTimeout, uint256 _relayEntryHardTimeout) internal
self
struct Relay.Data
_relayEntrySoftTimeout
uint256
New relay entry soft timeout value. It is the time in blocks during which a result is expected to be submitted so that the group is not slashed.
_relayEntryHardTimeout
uint256
New relay entry hard timeout value. It is the time in blocks for a group to submit the relay entry before slashing for the full slashing amount happens.
function setRelayEntrySubmissionFailureSlashingAmount(struct Relay.Data self, uint96 newRelayEntrySubmissionFailureSlashingAmount) internal
self
struct Relay.Data
newRelayEntrySubmissionFailureSlashingAmount
uint96
New value of the parameter.
function retryOnEntryTimeout(struct Relay.Data self, uint64 newGroupId) internal
self
struct Relay.Data
newGroupId
uint64
ID of the group chosen to retry the current request.
function cleanupOnEntryTimeout(struct Relay.Data self) internal
function isRequestInProgress(struct Relay.Data self) internal view returns (bool)
[0]
bool
True if there is a request in progress. False otherwise.
function hasRequestTimedOut(struct Relay.Data self) internal view returns (bool)
[0]
bool
True if the request timed out. False otherwise.
function softTimeoutBlock(struct Relay.Data self) internal view returns (uint256)
[0]
uint256
The soft timeout block
This page will guide you through Docker setup steps.
Install or update Docker to the latest version. Visit the Official Docker website for detailed instructions. Use the command below to find your installed version if needed:
docker --version
General best practices recommend against running the tBTC v2 client as the root
user as a security precaution. One security-minded approach is to Run the Docker daemon as a non-root user (Rootless mode).
Next, choose ONE of the following options: Docker Compose makes managing the container simple, but requires an additional step to ensure the container starts after a reboot. The tBTC v2 Service option configures the client to run as a service in order to ensure that the client is restarted automatically, should your machine reboot. The Docker Launch Script is faster to setup, but won't start the client if your machine reboots.
Navigate to cd /home/$USER/keep/
Create a file named docker-compose.yaml
Copy the the template below into the file and replace the <Placeholders> with respective details.
version: '3'
services:
keep-client:
image: keepnetwork/keep-client:latest
container_name: keep-client
restart: on-failure
ports:
- "3919:3919"
- "9601:9601"
volumes:
- /home/$USER/keep/config/:/mnt/keep/config
- /home/$USER/keep/storage/:/mnt/keep/storage
environment:
- KEEP_ETHEREUM_PASSWORD=<Operator Account keyfile password>
- LOG_LEVEL=info
logging:
options:
max-size: "100m"
max-file: "3"
command: start --ethereum.url "<Ethereum API WS URL>" --ethereum.keyFile "/mnt/keep/config/<Operator Account keyfile name>" --storage.dir "/mnt/keep/storage" --network.announcedAddresses "/ip4/<PUBLIC_IP_OF_MACHINE>/tcp/3919"
Save and close the file when finished.
To start the tBTC client sudo docker compose up
tBTC will start up with the console output on screen. Verify that the client is running correctly, then press CTRL
+ c
to stop the client.
Restart the client with the 'detach' flag: sudo docker compose up -d
To stop the container at a later time, use sudo docker compose down
To ensure the tBTC container starts after a server reboot, use the template below to create a service in /etc/systemd/system/
called docker-compose-app.service
# /etc/systemd/system/docker-compose-app.service
[Unit]
Description=Docker Compose Application Service
Requires=docker.service
After=docker.service
StartLimitIntervalSec=60
[Service]
WorkingDirectory=/home/$USER/keep/
ExecStart=/usr/local/bin/docker-compose up
ExecStop=/usr/local/bin/docker-compose down
TimeoutStartSec=0
Restart=on-failure
StartLimitBurst=3
[Install]
WantedBy=multi-user.target
Adjust the WorkingDirectory
to the path of your tBTC folder. Save and close the file when finished.
use sudo systemctl enable docker-compose-app
to enable the service.
Create the tbtcv2.service file:
cd /etc/systemd/system/
nano tbtcv2.service
Paste the following in the tbtcv2.service file:
[Unit]
Description=tBTC v2 client
After=network.target
Wants=network.target
[Service]
Environment="ETHEREUM_WS_URL=<Ethereum API WS URL>"
Environment="OPERATOR_KEY_FILE_NAME=<Operator Account keyfile name>"
Environment="OPERATOR_KEY_FILE_PASSWORD=<Operator Account keyfile password>"
Environment="PUBLIC_IP=/ip4/<PUBLIC_IP_OF_MACHINE>/tcp/3919"
Environment="CONFIG_DIR=/home/<user name>/keep/config"
Environment="STORAGE_DIR=/home/<user name>/keep/storage"
# These items only apply if you setup rootless-mode.
# Do not enable unless using rootless mode. Don't forget to adjust user UID.
#Environment="XDG_RUNTIME_DIR=/run/<user name>/<user UID>/"
#Environment="DOCKER_HOST=unix:///run/<user name>/<user UID>/docker.sock"
Type=simple
WorkingDirectory=/home/$USER
ExecStart=/usr/bin/docker run \
--volume ${CONFIG_DIR}:/mnt/keep/config \
--volume ${STORAGE_DIR}:/mnt/keep/storage \
--env KEEP_ETHEREUM_PASSWORD=${OPERATOR_KEY_FILE_PASSWORD} \
--env LOG_LEVEL=info \
--log-opt max-size=100m \
--log-opt max-file=3 \
-p 3919:3919 \
-p 9601:9601 \
keepnetwork/keep-client:latest \
start \
--ethereum.url ${ETHEREUM_WS_URL} \
--ethereum.keyFile /mnt/keep/config/${OPERATOR_KEY_FILE_NAME} \
--storage.dir /mnt/keep/storage \
--network.announcedAddresses $PUBLIC_IP
Restart=always
RestartSec=15s
[Install]
WantedBy=default.target
Replace the placeholders inside the <> brackets, remove the <> brackets but be sure to not edit anything further.
When done, save and close the file.
Next, test to make sure your configuration works:
sudo systemctl start tbtcv2
There will be no console output because it will be running in the background. Use systemctl to get the status of the service:
sudo systemctl status tbtcv2
If the service failed, go back and double check your configuration.
Another option is to see if the Docker container is running:
docker ps
If everything is running as intended, enable the service:
systemctl enable tbtcv2
Now, with the service running, you should make sure that your configuration will tolerate a reboot and start up again automatically.
reboot now
Log back in to your machine and check that the service is running. If it is, you're done. If not, go back and review your work.
To launch the tBTC v2 client, several configuration flags and environmental values need to be set. For simplicity, a bash script can be used rather than typing or pasting all the flags into the console.
Create the launch script:
nano keep.sh
And paste the following:
# Keep tBTC v2 Client
#
# Ethereum endpoint WebSocket URL
# This can be a provider such as Infura, Alchemy, Ankr, etc or your own Geth Nodeq
# ETHEREUM_WS_URL="wss://mainnet.infura.io/ws/v3/redacted_credentials"
# note: only replace characters inside the " ". The Quotation marks must be retained
ETHEREUM_WS_URL="<Ethereum API WS URL>"
# copied to home/keep/config earlier
OPERATOR_KEY_FILE_NAME="<Operator Account keyfile name>"
# password set during Operator Account Address creation
OPERATOR_KEY_FILE_PASSWORD="<Operator Account keyfile password>"
# To configure your node with a Public IP, enter it below.
PUBLIC_IP="<PUBLIC_IP_OF_MACHINE>"
# Alternatively, you can use DNS.
# To configure DNS, modify the last line of the script
# and add your DNS in the following format:
# /dns4/bootstrap-1.test.keep.network/tcp/3919
# Setup configuration and storage directories
# THESE MUST BE PERSISTENT STORAGE
CONFIG_DIR="/home/$USER/keep/config"
STORAGE_DIR="/home/$USER/keep/storage"
docker run \
--detach \
--restart on-failure \
--volume $CONFIG_DIR:/mnt/keep/config \
--volume $STORAGE_DIR:/mnt/keep/storage \
--env KEEP_ETHEREUM_PASSWORD=$OPERATOR_KEY_FILE_PASSWORD \
--env LOG_LEVEL=info \
--log-opt max-size=100m \
--log-opt max-file=3 \
-p 3919:3919 \
-p 9601:9601 \
keepnetwork/keep-client:latest \
start \
--ethereum.url $ETHEREUM_WS_URL \
--ethereum.keyFile /mnt/keep/config/$OPERATOR_KEY_FILE_NAME \
--storage.dir /mnt/keep/storage \
--network.announcedAddresses /ip4/$PUBLIC_IP/tcp/3919
Save and close the file, and make it executable:
sudo chmod +x keep.sh
To launch the tBTC v2 client, execute:
sudo bash keep.sh
The path shown in the example configuration will differ from yours. Make sure it is configured correctly.
Unless the --detach
flag was removed from the startup script, there will be no console output. In order to check your node, retrieve the Docker logs.
First, find your Docker instance identification, it'll be a random combination of words, e.g. stinky_brownie
:
sudo docker ps
Use your specific identification and substitute:
sudo docker logs stinky_brownie >& /path/to/output/file
Scroll down about half a page, and you should see the following:
▓▓▌ ▓▓ ▐▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓ ▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓ ▐▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓
▓▓▓▓▓▓▄▄▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▄▄▄▄ ▓▓▓▓▓▓▄▄▄▄ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▀▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓▀▀▀▀ ▓▓▓▓▓▓▀▀▀▀ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀
▓▓▓▓▓▓ ▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌
▓▓▓▓▓▓▓▓▓▓ █▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓
Trust math, not hardware.
-----------------------------------------------------------------------------------
| Keep Client Node |
| |
| Version: Version: vX.X.X-XX (4d745f6d0) |
| |
| Operator: 0x_your_operator_address |
| |
| Port: 3919 |
| IPs : /ip4/111.222.333.444/tcp/3919/ipfs/redacted |
| |
| Contracts: |
| RandomBeacon : 0x5499f54b4A1CB4816eefCf78962040461be3D80b |
| WalletRegistry : 0x46d52E41C2F300BC82217Ce22b920c34995204eb |
| TokenStaking : 0x01B67b1194C75264d06F808A921228a95C765dd7 |
-----------------------------------------------------------------------------------
Congratulations, your node is up and running.
Background of Threshold USD, Main Parameters and Key Characteristics
launched in 2023 the second generation of , a decentralized and scalable bridge between Bitcoin and Ethereum. It provides Bitcoin holders secure and open access to the broader DeFi ecosystem.
An immediate question that comes up is providing utility for the bridged tBTC for DeFi applications. Threshold USD provides an alternative utility to tBTC by allowing to mint thUSD, which then can be used for multiple purposes in a wider ecosystem.
Threshold USD is a decentralized borrowing protocol that allows you to draw loans using tBTC and ETH as collateral. Loans are paid out in thUSD (a USD pegged stablecoin) and need to maintain a minimum collateral ratio of 110%.
In addition to the collateral, the loans are secured by a Stability Pool containing thUSD and by fellow borrowers collectively acting as guarantors of last resort. Learn more about these mechanisms under .
Threshold USD as a protocol is based on a fork of Liquity (), which has established itself as a successful protocol since its launch on May 2021, going through several severe stress tests successfully.
Liquity is totally governance free and immutable; although that is a tremendous strength for a DeFi Protocol as it makes it completely censorship resistant, it also exposes the protocol to existential oracle risk and limits its ability to scale and respond to market dynamics.
With that in mind some flexibilitiy is embeded into Threshold USD. It has a few parameters that can be changed through governance:
Collateral Type: in addition to tBTC other collateral types may be added (ETH will be an additional collateral to tBTC, as it is the most decentralized and abundant asset in Ethereum) and also removed by revoking mint and burn capabilities.
Price Feed Contract: Oracles are critical for the health of the protocol (defining liquidations) and may need to be updated due to poor performance of existing oracles
PCV Contract: Withdraw from BAMM to PCV, pay the debt and withdraw funds from PCV after repaying all the debt.
BAMM Contract: Dao has veto capability for change submission to the "A" parameter and B.Protocol fees.
This documentation is based on the Liquity documents () with the appropriate changes that are relevant to Threshold USD.
Stable-value assets are an essential building block for Ethereum applications and have grown to represent tens of billions of dollars in value.
However, the vast majority of this value is in the form of fiat-collateralized stablecoins like USDT and USDC. Decentralized stablecoins like DAI and sUSD make up only a small portion of the total stablecoin supply, meaning the vast majority of stablecoins are centralized.
Threshold USD addresses this by creating a more capital efficient and user-friendly way to borrow stablecoins.
Threshold USD’s key benefits include:
Low cost for tBTC loan (0.5% issuance fee and no interest rate at launch).
Minimum collateral ratio of 110% — more efficient usage of deposited tBTC
Governance Minimized— most operations are algorithmic and fully automated, and most protocol parameters are set at time of contract deployment.
Note: See section above for parameters that can be changed through governance
Directly redeemable — thUSD can be redeemed at face value for the underlying collateral at any time
Yes. The Threshold USD token contracts mint function will be able to add additional contracts; this is how we keep the option to upgrade while leaving all the contracts immutable with the exception of the price feed contract.
You can use thUSD via a web interface (aka frontend) or directly via the smart contracts.
Borrow thUSD against tBTC by opening a Vault and use directly in DeFi applications or exchange for other coins
Secure Threshold USD by providing thUSD to the Stability Pool in exchange for Liquidation rewards
Providing liquidity to the thUSD Pool in Curve () to earn swapping fees and maybe incentive fees (these may be time dependent and could be turned on or off at will)
Redeem 1 thUSD for 1 USD worth of tBTC when the thUSD peg falls below $1 (i.e. arbitrage the system)
thUSD is the USD-pegged stablecoin used to pay out loans on the Threshold USD protocol. At any time it can be redeemed against the underlying collateral at face value.
To borrow thUSD, all you need is a wallet (e.g. MetaMask or ) and sufficient tBTC or ETH to open a Vault and pay the gas fees.
To become a Stability Pool depositor you need to have thUSD, which can be borrowed by opening a Vault. You can also use Curve or another (decentralized) exchange to buy the tokens on the open market.
These are the fees for using Threshold USD Protocol:
A one-time fee whenever thUSD is borrowed, as a percentage of the drawn amount (in thUSD)
A fee when thUSD is redeemed (see Section “Redemptions and thUSD Price Stability” for details on the Redemption Mechanism)
For redeemers, there is a redemption fee on the amount paid to users by the system (in tBTC) when exchanging thUSD for tBTC. Note that redemption is separate from repaying your loan as a borrower, which is free of charge
Both fees depend on the redemption volumes, i.e. they increase upon every redemption in function of the redeemed amount, and decay over time as long as no redemptions take place. The intent is to throttle large redemptions with higher fees, and to throttle borrowing directly after large redemption volumes. The fee decay over time ensures that the fee for both borrowers and redeemers will “cool down”, while redemptions volumes are low.
The fees cannot become smaller than 0.5% (except in Recovery Mode), which protects the redemption facility from being misused by arbitrageurs front-running the price feed. The borrowing fee is capped at 5%, keeping the system (somewhat) attractive for borrowers even in phases where the monetary is contracting due to redemptions. Other than that, the two fees are identical.
Interest rate: at launch no ongoing interest rate is charged when using ETH and tBTC as collateral, only the initial borrowing fee.
The decision to incorporate an ongoing interest rate that will accrue on the loan amount in thUSD, which is calculated based on a predetermined interest rate and the amount of time that has elapsed, will be made by the DAO in the near future for these and any other future collaterals.
There are two main ways to generate revenue using Threshold USD:
Providing liquidity to the thUSD Pool in Curve () to earn swapping fees and maybe incentive fees (these may be time dependent and could be turned on or off at will)
Deposit thUSD to the Stability Pool and earn liquidation gains (in tBTC).
Other opportunities may arise in time in the DeFi Ecosystem to use thUSD and earn rewards or yield.
As a non-custodial system, all the tokens sent to the protocol will be held and managed algorithmically without the interference of any person or legal entity. That means your funds will only be subject to the rules set forth in the smart contract code.
You may lose all or part of your funds in a Liquidation scenario:
You are a borrower (Vault owner) and your collateral in tBTC is liquidated. You will still keep your borrowed thUSD, but your Vault will be closed and your collateral will be used to compensate Stability Pool depositors.
If the liquidation happens under Normal Mode (i.e. your Vault is at 110% Collateral Ratio), all your Collateral will be used the Liquidation and fees
If the liquidation happens under Recovery Mode (i.e. Total Protocol Collateral Ratio is less than 150%), you may recover a part of the Collateral if your Vault is between the 110% and 150% Collateral Ratio)
In addition, the following Scenarios may happen that may lead to loss of funds:
Bugs/Hacks: Please note that although the system is diligently audited, a hack or a bug that results in losses for the users can never be fully excluded. This includes potential bugs/hacks on tBTC as it is the collateral for thUSD.
Price Oracle Risk: Liquidations are triggered by the Collateral Ratio, which is a function of the Collateral Price. There is a risk of malfunction of manipulation with the Price Feed. We are minimizing the exposure to those risks, but they cannot be completely ruled out
If the Threshold USD is not accepted and all users want to leave, the last fraction of collateral cannot be recovered as there won’t be enough thUSD available (due to Loan fees accrued). This would only affect a very minor fraction of capital
For redemptions, in case of an acceleration of redemptions, the redemption fee increases and can lead to losses. If the Total Collateral Ratio is less than 110%, redemptions are blocked
This page will show you how to launch a tBTC v2 node on the testnet.
This is a TESTNET guide document. Following this document will not result in a node enabling you to earn mainnet rewards.
Your operating environment will ultimately dictate what machine type to go with. This is particularly relevant if you’re running a containerized solution where multiple applications are sharing VM resources. The below types are sufficient for running one instance of the tBTC v2 Node.
The preferred OS is Ubuntu.
AWS
c5.large
Azure
F2s v2
Google Cloud
n2-highcpu-2
Self-hosted
2 vCPU / 2 GB RAM / 1 GiB Persistent Storage
A Keep Node requires a connection to a WebSocket Ethereum API. You should obtain a WS API URL from a service provider (e.g. Alchemy, Infura, Ankr) or run your own Ethereum node (e.g. Geth).
The client requires an Ethereum Key File of an Operator Account to connect to the Ethereum chain. This account is created in a subsequent step using Geth (GoEthereum).
The Ethereum Key File is expected to be encrypted with a password. The password has to be provided in a prompt after the client starts or configured as a KEEP_ETHEREUM_PASSWORD
environment variable.
The Operator Account has to maintain a positive Ether balance at all times.
Please do NOT reuse an operator account that is being used for PRE or other applications.
To create a new Ethereum account, install Geth (GoEthereum) and create a new account using the command below. This account will subsequently be referred to as the Operator Account.
geth account new --keystore ./operator-key
When prompted, provide a password to protect the operator key file.
Avoid passwords that contain the following characters: ', ", `, $ These characters may be interpreted as part of the configuration which can lead to undesirable outcomes that may be extremely time intensive to correct.
Once the process completes, your public key will be displayed. Take note of your Operator Account public key.
DO NOT LOSE THE PASSWORD TO THE OPERATOR ACCOUNT.
Your Operator Account will need to be funded with sepolia ETH and maintain a positive balance at all times to ensure proper operation and availability of your tBTC v2 node.
The node has to be accessible publicly to establish and maintain connections with bootstrap nodes and discovered peers.
The node exposes metrics and diagnostics services for monitoring. A network port has to be exposed publicly, so the peers can connect to your node. A Diagnostics Port has to be exposed publicly, for the rewards allocation.
Network
network.port
TCP
3919
Status
clientInfo.port
TCP
9601
An Announced Address is a layered addressing information (multiaddress
/multiaddr
) announced to the Threshold Network that is used by peers to connect with your node, e.g.: /dns4/bootstrap-0.test.keep.network/tcp/3919
or /ip4/104.154.61.116/tcp/3919
.
If the machine you’re running your node is not exposing a public IP (e.g. it is behind NAT) you should set the network.AnnouncedAddresses
(flag: --network.announcedAddresses
) configuration property to an addresses (ip4
or dns4
) under which your node is reachable for the public.
To read more about multiaddress
see the libp2p docummentation.
One important step to get your node operating on the Threshold Network is proper application authorization as well as operator account registration. Applications need only be authorized once.
It is CRITICALLY important that both the tBTC application as well as the Random Beacon applications are authorized. A node cannot be deployed without both applications being properly authorized.
Please note: by authorizing these applications, an unbonding period of 45 days will go into effect on the T you stake for these applications. This cool-down period begins the day you submit an unstake request.
To get started, visit the Threshold Dashboard and connect your wallet.
Click on "Configure Apps"
Select BOTH tBTC and Random Beacon applications and enter your desired amount of T to stake per application. Note that the minimum is 40,000T.
The operator account is the Ethereum account created on your node. In order for the network to associate your T stake with your node, you must register your Operator Address.
After both applications have been authorized, click on "Start Mapping" to begin the Operator Registration process.
Enter your Operator Address in the field provided and click on "Map Address."
Once the steps above have been successfully completed, you are ready to move on to the next step in the node deployment process.
Don't forget: a tBTC v2 node will not be able to be deployed without successfully authorizing both the tBTC and Random Beacon applications, and registering the node's operator address FIRST.
The client requires two persistent directories. These directories will store configuration files and data generated and used by the client. It is highly recommended to create frequent backups of these directories. Loss of these data may be catastrophic and may lead to slashing.
cd /home
mkdir keep
cd keep
mkdir storage config
The tBTC v2 client will create two subdirectories within the storage directory: keystore
and work
. You do not need to create these.
The keystore
subdirectory contains sensitive key material data generated by the client. Loosing the keystore
data is a serious protocol offense and leads to slashing and potentially losing funds.
The work
directory contains data generated by the client that should persist the client restarts or relocations. If the work
data are lost the client will be able to recreate them, but it is inconvenient due to the time needed for the operation to complete and may lead to losing rewards.
Assuming Geth was installed for the root user with the command provided in the Operator Account creation step, the operator-key file should be located in the ~/operator-key
directory.
cd ~/operator-key
Contained within the operator-key
directory is the account key file (operator key file), its name will be similar to the following:
UTC--2018-11-01T06-23-57.810787758Z--fa3da235947aab49d439f3bcb46effd1a7237e32
copy (not move!) this account key file to the config
directory created above
ls -la
cp name_of_account_key_file /home/keep/config/name_of_account_key_file
Install or update Docker to the latest version. Visit the Official Docker website for detailed instructions. Use the command below to find your installed version if needed:
docker --version
To launch the tBTC v2 client, several configuration flags and environmental values need to be set. For simplicity, a bash script can be used rather than typing or pasting all the flags into the console.
Create the launch script:
nano keep.sh
And paste the following:
# Keep Testnet tBTC v2 Client
#
# Ethereum endpoint WebSocket URL
# This can be a provider such as Infura, Alchemy, Ankr, etc or your own Geth Nodeq
# ETHEREUM_WS_URL="wss://sepolia.infura.io/ws/v3/redacted_credentials"
# note: only replace characters inside the " ". The Quotation marks must be retained
ETHEREUM_WS_URL="<Ethereum API WS URL>"
# copied to home/keep/config earlier
OPERATOR_KEY_FILE_NAME="<Operator Account keyfile name>"
# password set during Operator Account Address creation
OPERATOR_KEY_FILE_PASSWORD="<Operator Account keyfile password>"
# To configure your node with a Public IP, enter it below.
PUBLIC_IP="<PUBLIC_IP_OF_MACHINE>"
# Alternatively, you can use DNS.
# To configure DNS, modify the last line of the script
# and add your DNS in the following format:
# /dns4/bootstrap-1.test.keep.network/tcp/3919
# Setup configuration and storage directories
# THESE MUST BE PERSISTENT STORAGE
CONFIG_DIR="/home/keep/config"
STORAGE_DIR="/home/keep/storage"
docker run \
--detach \
--restart on-failure \
--volume $CONFIG_DIR:/mnt/keep/config \
--volume $STORAGE_DIR:/mnt/keep/storage \
--env KEEP_ETHEREUM_PASSWORD=$OPERATOR_KEY_FILE_PASSWORD \
--env LOG_LEVEL=info \
--log-opt max-size=100m \
--log-opt max-file=3 \
-p 3919:3919 \
-p 9601:9601 \
us-docker.pkg.dev/keep-test-f3e0/public/keep-client \
start \
--testnet \
--ethereum.url $ETHEREUM_WS_URL \
--ethereum.keyFile /mnt/keep/config/$OPERATOR_KEY_FILE_NAME \
--storage.dir /mnt/keep/storage \
--network.announcedAddresses /ip4/$PUBLIC_IP/tcp/3919
Save and close the file, and make it executable:
sudo chmod +x keep.sh
To launch the tBTC v2 client, execute:
sudo bash keep.sh
The path shown in the example configuration will differ from yours. Make sure it is configured correctly.
Unless the --detach
flag was removed from the startup script, there will be no console output. In order to check your node, retrieve the Docker logs.
First, find your Docker instance identification, it'll be a random combination of words, e.g. stinky_brownie
:
sudo docker ps
Use your specific identification and substitute:
sudo docker logs stinky_brownie >& /path/to/output/file
Scroll down about half a page, and you should see the following:
▓▓▌ ▓▓ ▐▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓ ▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓ ▐▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓
▓▓▓▓▓▓▄▄▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▄▄▄▄ ▓▓▓▓▓▓▄▄▄▄ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▀▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓▀▀▀▀ ▓▓▓▓▓▓▀▀▀▀ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀
▓▓▓▓▓▓ ▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌
▓▓▓▓▓▓▓▓▓▓ █▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓
Trust math, not hardware.
-----------------------------------------------------------------------------------
| Keep Client Node |
| |
| Version: Version: vX.X.XX (4d745f6d0) |
| |
| Operator: 0x_your_operator_address |
| |
| Port: 3919 |
| IPs : /ip4/111.222.333.444/tcp/3919/ipfs/redacted |
| |
| Contracts: |
| RandomBeacon : 0x2bA82903B635a96154A515488d2952E86D6adc3A |
| WalletRegistry : 0x2363cc10b7680000C02E4a7067A68d1788ffc86F |
| TokenStaking : 0x69f962a0fbA5635e84eC94131f9072108E2E4F24 |
-----------------------------------------------------------------------------------
Congratulations, your node is up and running.
TokenStaking is the main staking contract of the Threshold Network. Apart from the basic usage of enabling T stakes, it also acts as a sort of "meta-staking" contract, accepting existing legacy NU/KEEP stakes. Additionally, it serves as application manager for the apps that run on the Threshold Network. Note that legacy NU/KEEP staking contracts see TokenStaking as an application (e.g., slashing is requested by TokenStaking and performed by the legacy contracts).
TokenStaking is upgradeable, using OpenZeppelin's Upgradeability framework. As such, it is required to satisfy OZ's guidelines, like restrictions on constructors, immutable variables, base contracts and libraries. See https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable
enum ApplicationStatus {
NOT_APPROVED,
APPROVED,
PAUSED,
DISABLED
}
struct StakingProviderInfo {
uint96 nuInTStake;
address owner;
uint96 keepInTStake;
address payable beneficiary;
uint96 tStake;
address authorizer;
mapping(address => struct TokenStaking.AppAuthorization) authorizations;
address[] authorizedApplications;
uint256 startStakingTimestamp;
}
struct AppAuthorization {
uint96 authorized;
uint96 deauthorizing;
}
struct ApplicationInfo {
enum TokenStaking.ApplicationStatus status;
address panicButton;
}
struct SlashingEvent {
address stakingProvider;
uint96 amount;
}
uint256 SLASHING_REWARD_PERCENT
uint256 MIN_STAKE_TIME
uint256 GAS_LIMIT_AUTHORIZATION_DECREASE
uint256 CONVERSION_DIVISOR
contract T token
contract IKeepTokenStaking keepStakingContract
contract KeepStake keepStake
contract INuCypherStakingEscrow nucypherStakingContract
uint256 keepRatio
uint256 nucypherRatio
address governance
uint96 minTStakeAmount
uint256 authorizationCeiling
uint96 stakeDiscrepancyPenalty
uint256 stakeDiscrepancyRewardMultiplier
uint256 notifiersTreasury
uint256 notificationReward
mapping(address => struct TokenStaking.StakingProviderInfo) stakingProviders
mapping(address => struct TokenStaking.ApplicationInfo) applicationInfo
address[] applications
struct TokenStaking.SlashingEvent[] slashingQueue
uint256 slashingQueueIndex
event Staked(enum IStaking.StakeType stakeType, address owner, address stakingProvider, address beneficiary, address authorizer, uint96 amount)
event MinimumStakeAmountSet(uint96 amount)
event ApplicationStatusChanged(address application, enum TokenStaking.ApplicationStatus newStatus)
event AuthorizationIncreased(address stakingProvider, address application, uint96 fromAmount, uint96 toAmount)
event AuthorizationDecreaseRequested(address stakingProvider, address application, uint96 fromAmount, uint96 toAmount)
event AuthorizationDecreaseApproved(address stakingProvider, address application, uint96 fromAmount, uint96 toAmount)
event AuthorizationInvoluntaryDecreased(address stakingProvider, address application, uint96 fromAmount, uint96 toAmount, bool successfulCall)
event PanicButtonSet(address application, address panicButton)
event AuthorizationCeilingSet(uint256 ceiling)
event ToppedUp(address stakingProvider, uint96 amount)
event Unstaked(address stakingProvider, uint96 amount)
event TokensSeized(address stakingProvider, uint96 amount, bool discrepancy)
event StakeDiscrepancyPenaltySet(uint96 penalty, uint256 rewardMultiplier)
event NotificationRewardSet(uint96 reward)
event NotificationRewardPushed(uint96 reward)
event NotificationRewardWithdrawn(address recipient, uint96 amount)
event NotifierRewarded(address notifier, uint256 amount)
event SlashingProcessed(address caller, uint256 count, uint256 tAmount)
event OwnerRefreshed(address stakingProvider, address oldOwner, address newOwner)
event GovernanceTransferred(address oldGovernance, address newGovernance)
modifier onlyGovernance()
modifier onlyPanicButtonOf(address application)
modifier onlyAuthorizerOf(address stakingProvider)
modifier onlyOwnerOrStakingProvider(address stakingProvider)
modifier onlyOwnerOf(address stakingProvider)
constructor(contract T _token, contract IKeepTokenStaking _keepStakingContract, contract INuCypherStakingEscrow _nucypherStakingContract, contract VendingMachine _keepVendingMachine, contract VendingMachine _nucypherVendingMachine, contract KeepStake _keepStake) public
_token
contract T
Address of T token contract
_keepStakingContract
contract IKeepTokenStaking
Address of Keep staking contract
_nucypherStakingContract
contract INuCypherStakingEscrow
Address of NuCypher staking contract
_keepVendingMachine
contract VendingMachine
Address of Keep vending machine
_nucypherVendingMachine
contract VendingMachine
Address of NuCypher vending machine
_keepStake
contract KeepStake
Address of Keep contract with grant owners
function initialize() external
function stake(address stakingProvider, address payable beneficiary, address authorizer, uint96 amount) external
Creates a delegation with msg.sender
owner with the given staking provider, beneficiary, and authorizer. Transfers the given amount of T to the staking contract.
The owner of the delegation needs to have the amount approved to transfer to the staking contract.
function stakeKeep(address stakingProvider) external
Copies delegation from the legacy KEEP staking contract to T staking contract. No tokens are transferred. Caches the active stake amount from KEEP staking contract. Can be called by anyone.
The staking provider in T staking contract is the legacy KEEP staking contract operator.
function stakeNu(address stakingProvider, address payable beneficiary, address authorizer) external
Copies delegation from the legacy NU staking contract to T staking contract, additionally appointing beneficiary and authorizer roles. Caches the amount staked in NU staking contract. Can be called only by the original delegation owner.
function setMinimumStakeAmount(uint96 amount) external
Allows the Governance to set the minimum required stake amount. This amount is required to protect against griefing the staking contract and individual applications are allowed to require higher minimum stakes if necessary.
Staking providers are not required to maintain a minimum T stake all the time. 24 hours after the delegation, T stake can be reduced below the minimum stake. The minimum stake in the staking contract is just to protect against griefing stake operation. Please note that each application may have its own minimum authorization though and the authorization can not be higher than the stake.
function approveApplication(address application) external
Allows the Governance to approve the particular application before individual stake authorizers are able to authorize it.
function increaseAuthorization(address stakingProvider, address application, uint96 amount) external
Increases the authorization of the given staking provider for the given application by the given amount. Can only be called by the given staking provider’s authorizer.
Calls authorizationIncreased
callback on the given application to notify the application about authorization change. See IApplication
.
function requestAuthorizationDecrease(address stakingProvider) external
Requests decrease of all authorizations for the given staking provider on all applications by all authorized amount. It may not change the authorized amount immediatelly. When it happens depends on the application. Can only be called by the given staking provider’s authorizer. Overwrites pending authorization decrease for the given staking provider and application.
Calls authorizationDecreaseRequested
callback for each authorized application. See IApplication
.
function approveAuthorizationDecrease(address stakingProvider) external returns (uint96)
Called by the application at its discretion to approve the previously requested authorization decrease request. Can only be called by the application that was previously requested to decrease the authorization for that staking provider. Returns resulting authorized amount for the application.
function forceDecreaseAuthorization(address stakingProvider, address application) external
Decreases the authorization for the given stakingProvider
on the given disabled application
, for all authorized amount. Can be called by anyone.
function pauseApplication(address application) external
Pauses the given application’s eligibility to slash stakes. Besides that stakers can't change authorization to the application. Can be called only by the Panic Button of the particular application. The paused application can not slash stakes until it is approved again by the Governance using approveApplication
function. Should be used only in case of an emergency.
function disableApplication(address application) external
Disables the given application. The disabled application can't slash stakers. Also stakers can't increase authorization to that application but can decrease without waiting by calling forceDecreaseAuthorization
at any moment. Can be called only by the governance. The disabled application can't be approved again. Should be used only in case of an emergency.
function setPanicButton(address application, address panicButton) external
Sets the Panic Button role for the given application to the provided address. Can only be called by the Governance. If the Panic Button for the given application should be disabled, the role address should be set to 0x0 address.
function setAuthorizationCeiling(uint256 ceiling) external
Sets the maximum number of applications one staking provider can have authorized. Used to protect against DoSing slashing queue. Can only be called by the Governance.
function topUp(address stakingProvider, uint96 amount) external
Increases the amount of the stake for the given staking provider.
The sender of this transaction needs to have the amount approved to transfer to the staking contract.
function topUpKeep(address stakingProvider) external
Propagates information about stake top-up from the legacy KEEP staking contract to T staking contract. Can be called only by the owner or the staking provider.
function topUpNu(address stakingProvider) external
Propagates information about stake top-up from the legacy NU staking contract to T staking contract. Can be called only by the owner or the staking provider.
function unstakeT(address stakingProvider, uint96 amount) external
Reduces the liquid T stake amount by the provided amount and withdraws T to the owner. Reverts if there is at least one authorization higher than the sum of the legacy stake and remaining liquid T stake or if the unstake amount is higher than the liquid T stake amount. Can be called only by the owner or the staking provider. Can only be called when 24h passed since the stake has been delegated.
function unstakeKeep(address stakingProvider) external
Sets the legacy KEEP staking contract active stake amount cached in T staking contract to 0. Reverts if the amount of liquid T staked in T staking contract is lower than the highest application authorization. This function allows to unstake from KEEP staking contract and still being able to operate in T network and earning rewards based on the liquid T staked. Can be called only by the delegation owner or the staking provider. Can only be called when 24h passed since the stake has been delegated.
This function (or unstakeAll
) must be called before undelegate
/undelegateAt
in Keep staking contract. Otherwise provider can be slashed by notifyKeepStakeDiscrepancy
method.
function unstakeNu(address stakingProvider, uint96 amount) external
Reduces cached legacy NU stake amount by the provided amount. Reverts if there is at least one authorization higher than the sum of remaining legacy NU stake and liquid T stake for that staking provider or if the untaked amount is higher than the cached legacy stake amount. If succeeded, the legacy NU stake can be partially or fully undelegated on the legacy staking contract. This function allows to unstake from NU staking contract and still being able to operate in T network and earning rewards based on the liquid T staked. Can be called only by the delegation owner or the staking provider. Can only be called when 24h passed since the stake has been delegated.
This function (or unstakeAll
) must be called before withdraw
in NuCypher staking contract. Otherwise NU tokens can't be unlocked.
stakingProvider
address
Staking provider address
amount
uint96
Amount of NU to unstake in T denomination
function unstakeAll(address stakingProvider) external
Sets cached legacy stake amount to 0, sets the liquid T stake amount to 0 and withdraws all liquid T from the stake to the owner. Reverts if there is at least one non-zero authorization. Can be called only by the delegation owner or the staking provider. Can only be called when 24h passed since the stake has been delegated.
function notifyKeepStakeDiscrepancy(address stakingProvider) external
Notifies about the discrepancy between legacy KEEP active stake and the amount cached in T staking contract. Slashes the staking provider in case the amount cached is higher than the actual active stake amount in KEEP staking contract. Needs to update authorizations of all affected applications and execute an involuntary authorization decrease on all affected applications. Can be called by anyone, notifier receives a reward.
function notifyNuStakeDiscrepancy(address stakingProvider) external
Notifies about the discrepancy between legacy NU active stake and the amount cached in T staking contract. Slashes the staking provider in case the amount cached is higher than the actual active stake amount in NU staking contract. Needs to update authorizations of all affected applications and execute an involuntary authorization decrease on all affected applications. Can be called by anyone, notifier receives a reward.
Real discrepancy between T and Nu is impossible. This method is a safeguard in case of bugs in NuCypher staking contract
function setStakeDiscrepancyPenalty(uint96 penalty, uint256 rewardMultiplier) external
Sets the penalty amount for stake discrepancy and reward multiplier for reporting it. The penalty is seized from the delegated stake, and 5% of the penalty, scaled by the multiplier, is given to the notifier. The rest of the tokens are burned. Can only be called by the Governance. See seize
function.
function setNotificationReward(uint96 reward) external
Sets reward in T tokens for notification of misbehaviour of one staking provider. Can only be called by the governance.
function pushNotificationReward(uint96 reward) external
Transfer some amount of T tokens as reward for notifications of misbehaviour
function withdrawNotificationReward(address recipient, uint96 amount) external
Withdraw some amount of T tokens from notifiers treasury. Can only be called by the governance.
function slash(uint96 amount, address[] _stakingProviders) external
Adds staking providers to the slashing queue along with the amount that should be slashed from each one of them. Can only be called by application authorized for all staking providers in the array.
This method doesn't emit events for providers that are added to the queue. If necessary events can be added to the application level.
function seize(uint96 amount, uint256 rewardMultiplier, address notifier, address[] _stakingProviders) external
Adds staking providers to the slashing queue along with the amount. The notifier will receive reward per each provider from notifiers treasury. Can only be called by application authorized for all staking providers in the array.
This method doesn't emit events for staking providers that are added to the queue. If necessary events can be added to the application level.
function processSlashing(uint256 count) external virtual
Takes the given number of queued slashing operations and processes them. Receives 5% of the slashed amount. Executes involuntaryAuthorizationDecrease
function on each affected application.
function delegateVoting(address stakingProvider, address delegatee) external
Delegate voting power from the stake associated to the stakingProvider
to a delegatee
address. Caller must be the owner of this stake.
function transferGovernance(address newGuvnor) external virtual
Transfers ownership of the contract to newGuvnor
.
function authorizedStake(address stakingProvider, address application) external view returns (uint96)
Returns the authorized stake amount of the staking provider for the application.
function stakes(address stakingProvider) external view returns (uint96 tStake, uint96 keepInTStake, uint96 nuInTStake)
Returns staked amount of T, Keep and Nu for the specified staking provider.
All values are in T denomination
function getStartStakingTimestamp(address stakingProvider) external view returns (uint256)
Returns start staking timestamp.
This value is set at most once.
function stakedNu(address stakingProvider) external view returns (uint256 nuAmount)
Returns staked amount of NU for the specified staking provider.
function rolesOf(address stakingProvider) external view returns (address owner, address payable beneficiary, address authorizer)
Gets the stake owner, the beneficiary and the authorizer for the specified staking provider address.
owner
address
Stake owner address.
beneficiary
address payable
Beneficiary address.
authorizer
address
Authorizer address.
function getApplicationsLength() external view returns (uint256)
Returns length of application array
function getSlashingQueueLength() external view returns (uint256)
Returns length of slashing queue
function requestAuthorizationDecrease(address stakingProvider, address application, uint96 amount) public
Requests decrease of the authorization for the given staking provider on the given application by the provided amount. It may not change the authorized amount immediatelly. When it happens depends on the application. Can only be called by the given staking provider’s authorizer. Overwrites pending authorization decrease for the given staking provider and application if the application agrees for that. If the application does not agree for overwriting, the function reverts.
Calls authorizationDecreaseRequested
callback on the given application. See IApplication
.
function getMinStaked(address stakingProvider, enum IStaking.StakeType stakeTypes) public view returns (uint96)
Returns minimum possible stake for T, KEEP or NU in T denomination
For example, suppose the given staking provider has 10 T, 20 T worth of KEEP, and 30 T worth of NU all staked, and the maximum application authorization is 40 T, then getMinStaked
for that staking provider returns:
0 T if KEEP stake type specified i.e. min = 40 T max - (10 T + 30 T worth of NU) = 0 T
10 T if NU stake type specified i.e. min = 40 T max - (10 T + 20 T worth of KEEP) = 10 T
0 T if T stake type specified i.e. min = 40 T max - (20 T worth of KEEP + 30 T worth of NU) < 0 T In other words, the minimum stake amount for the specified stake type is the minimum amount of stake of the given type needed to satisfy the maximum application authorization given the staked amounts of the other stake types for that staking provider.
function getAvailableToAuthorize(address stakingProvider, address application) public view returns (uint96 availableTValue)
Returns available amount to authorize for the specified application.
function delegate(address stakingProvider, address delegatee) internal virtual
Delegate voting power from the stake associated to the stakingProvider
to a delegatee
address. Caller must be the owner of this stake.
Original abstract function defined in Checkpoints contract had two parameters, delegator
and delegatee
. Here we override it and comply with the same signature but the semantics of the first parameter changes to the stakingProvider
address.
function notify(uint96 amount, uint256 rewardMultiplier, address notifier, address[] _stakingProviders) internal
Adds staking providers to the slashing queue along with the amount. The notifier will receive reward per each staking provider from notifiers treasury. Can only be called by application authorized for all staking providers in the array.
function processSlashing(struct TokenStaking.SlashingEvent slashing) internal returns (uint96 tAmountToBurn)
Processes one specified slashing event. Executes involuntaryAuthorizationDecrease
function on each affected application.
function authorizationDecrease(address stakingProvider, struct TokenStaking.StakingProviderInfo stakingProviderStruct, uint96 slashedAmount) internal
Synchronize authorizations (if needed) after slashing stake
function seizeKeep(struct TokenStaking.StakingProviderInfo stakingProviderStruct, address stakingProvider, uint96 tAmountToSlash, uint256 rewardMultiplier) internal returns (uint96)
Convert amount from T to Keep and call seize
in Keep staking contract. Returns remainder of slashing amount in T
Note this internal function doesn't update stake checkpoints
function seizeNu(struct TokenStaking.StakingProviderInfo stakingProviderStruct, uint96 tAmountToSlash, uint256 rewardMultiplier) internal returns (uint96)
Convert amount from T to NU and call slashStaker
in NuCypher staking contract. Returns remainder of slashing amount in T
Note this internal function doesn't update the stake checkpoints
function cleanAuthorizedApplications(struct TokenStaking.StakingProviderInfo stakingProviderStruct, uint256 numberToDelete) internal
Removes application with zero authorization from authorized applications array
function newStakeCheckpoint(address _delegator, uint96 _amount, bool increase) internal
Creates new checkpoints due to a change of stake amount
_delegator
address
Address of the staking provider acting as delegator
_amount
uint96
Amount of T to increment
increase
bool
True if the change is an increase, false if a decrease
function increaseStakeCheckpoint(address _delegator, uint96 _amount) internal
Creates new checkpoints due to an increment of a stakers' stake
_delegator
address
Address of the staking provider acting as delegator
_amount
uint96
Amount of T to increment
function decreaseStakeCheckpoint(address _delegator, uint96 _amount) internal
Creates new checkpoints due to a decrease of a stakers' stake
_delegator
address
Address of the stake owner acting as delegator
_amount
uint96
Amount of T to decrease
function getNuAmountInT(address owner, address stakingProvider) internal returns (uint96)
Returns amount of Nu stake in the NuCypher staking contract for the specified staking provider. Resulting value in T denomination
function _transferGovernance(address newGuvnor) internal virtual
function getKeepAmountInT(address stakingProvider) internal view returns (uint96)
Returns amount of Keep stake in the Keep staking contract for the specified staking provider. Resulting value in T denomination
function convertToT(uint256 amount, uint256 ratio) internal pure returns (uint96 tAmount, uint256 remainder)
Returns the T token amount that's obtained from amount
legacy tokens for the given ratio
, and the remainder that can't be converted.
function convertFromT(uint96 tAmount, uint256 ratio) internal pure returns (uint256 amount, uint96 tRemainder)
Returns the amount of legacy tokens that's obtained from tAmount
T tokens for the given ratio
, and the T remainder that can't be converted.
This file documents a contract which is not yet deployed to Mainnet.
Keep Random Beacon contract. It lets to request a new relay entry and validates the new relay entry provided by the network. This contract is in charge of all other Random Beacon activities such as group lifecycle or slashing.
Should be owned by the governance contract controlling Random Beacon parameters.
uint256 genesisSeed
Seed value used for the genesis group selection. https://www.wolframalpha.com/input/?i=pi+to+78+digits
uint256 _callbackGasLimit
Relay entry callback gas limit. This is the gas limit with which callback function provided in the relay request transaction is executed. The callback is executed with a new relay entry value in the same transaction the relay entry is submitted.
uint256 _groupCreationFrequency
The frequency of new group creation. Groups are created with a fixed frequency of relay requests.
uint96 _maliciousDkgResultSlashingAmount
Slashing amount for submitting a malicious DKG result. Every DKG result submitted can be challenged for the time of dkg.ResultChallengePeriodLength
. If the DKG result submitted is challenged and proven to be malicious, the operator who submitted the malicious result is slashed for _maliciousDkgResultSlashingAmount
.
uint96 _unauthorizedSigningSlashingAmount
Slashing amount when an unauthorized signing has been proved, which means the private key leaked and all the group members should be punished.
uint256 _sortitionPoolRewardsBanDuration
Duration of the sortition pool rewards ban imposed on operators who misbehaved during DKG by being inactive or disqualified and for operators that were identified by the rest of group members as inactive via notifyOperatorInactivity
.
uint256 _relayEntryTimeoutNotificationRewardMultiplier
Percentage of the staking contract malicious behavior notification reward which will be transferred to the notifier reporting about relay entry timeout. Notifiers are rewarded from a notifiers treasury pool. For example, if notification reward is 1000 and the value of the multiplier is 5, the notifier will receive: 5% of 1000 = 50 per each operator affected.
uint256 _unauthorizedSigningNotificationRewardMultiplier
Percentage of the staking contract malicious behavior notification reward which will be transferred to the notifier reporting about unauthorized signing. Notifiers are rewarded from a notifiers treasury pool. For example, if a notification reward is 1000 and the value of the multiplier is 5, the notifier will receive: 5% of 1000 = 50 per each operator affected.
uint256 _dkgMaliciousResultNotificationRewardMultiplier
Percentage of the staking contract malicious behavior notification reward which will be transferred to the notifier reporting about a malicious DKG result. Notifiers are rewarded from a notifiers treasury pool. For example, if notification reward is 1000 and the value of the multiplier is 5, the notifier will receive: 5% of 1000 = 50 per each operator affected.
uint256 _dkgResultSubmissionGas
Calculated gas cost for submitting a DKG result. This will be refunded as part of the DKG approval process. It is in the submitter's interest to not skip his priority turn on the approval, otherwise the refund of the DKG submission will be refunded to another group member that will call the DKG approve function.
uint256 _dkgResultApprovalGasOffset
Gas that is meant to balance the DKG result approval's overall cost. Can be updated by the governance based on the current market conditions.
uint256 _notifyOperatorInactivityGasOffset
Gas that is meant to balance the operator inactivity notification cost. Can be updated by the governance based on the current market conditions.
uint256 _relayEntrySubmissionGasOffset
Gas that is meant to balance the relay entry submission cost. Can be updated by the governance based on the current market conditions.
mapping(uint64 => uint256) inactivityClaimNonce
Stores current operator inactivity claim nonce for given group. Each claim is made with an unique nonce which protects against claim replay.
mapping(address => bool) authorizedRequesters
Authorized addresses that can request a relay entry.
contract SortitionPool sortitionPool
contract IERC20 tToken
contract IStaking staking
struct BeaconAuthorization.Data authorization
struct BeaconDkg.Data dkg
struct Groups.Data groups
struct Relay.Data relay
struct Callback.Data callback
event AuthorizationParametersUpdated(uint96 minimumAuthorization, uint64 authorizationDecreaseDelay, uint64 authorizationDecreaseChangePeriod)
event RelayEntryParametersUpdated(uint256 relayEntrySoftTimeout, uint256 relayEntryHardTimeout, uint256 callbackGasLimit)
event GroupCreationParametersUpdated(uint256 groupCreationFrequency, uint256 groupLifetime, uint256 dkgResultChallengePeriodLength, uint256 dkgResultChallengeExtraGas, uint256 dkgResultSubmissionTimeout, uint256 dkgResultSubmitterPrecedencePeriodLength)
event RewardParametersUpdated(uint256 sortitionPoolRewardsBanDuration, uint256 relayEntryTimeoutNotificationRewardMultiplier, uint256 unauthorizedSigningNotificationRewardMultiplier, uint256 dkgMaliciousResultNotificationRewardMultiplier)
event SlashingParametersUpdated(uint256 relayEntrySubmissionFailureSlashingAmount, uint256 maliciousDkgResultSlashingAmount, uint256 unauthorizedSigningSlashingAmount)
event GasParametersUpdated(uint256 dkgResultSubmissionGas, uint256 dkgResultApprovalGasOffset, uint256 notifyOperatorInactivityGasOffset, uint256 relayEntrySubmissionGasOffset)
event RequesterAuthorizationUpdated(address requester, bool isAuthorized)
event DkgStarted(uint256 seed)
event DkgResultSubmitted(bytes32 resultHash, uint256 seed, struct BeaconDkg.Result result)
event DkgTimedOut()
event DkgResultApproved(bytes32 resultHash, address approver)
event DkgResultChallenged(bytes32 resultHash, address challenger, string reason)
event DkgMaliciousResultSlashed(bytes32 resultHash, uint256 slashingAmount, address maliciousSubmitter)
event DkgMaliciousResultSlashingFailed(bytes32 resultHash, uint256 slashingAmount, address maliciousSubmitter)
event DkgStateLocked()
event DkgSeedTimedOut()
event GroupRegistered(uint64 groupId, bytes groupPubKey)
event RelayEntryRequested(uint256 requestId, uint64 groupId, bytes previousEntry)
event RelayEntrySubmitted(uint256 requestId, address submitter, bytes entry)
event RelayEntryTimedOut(uint256 requestId, uint64 terminatedGroupId)
event RelayEntryDelaySlashed(uint256 requestId, uint256 slashingAmount, address[] groupMembers)
event RelayEntryDelaySlashingFailed(uint256 requestId, uint256 slashingAmount, address[] groupMembers)
event RelayEntryTimeoutSlashed(uint256 requestId, uint256 slashingAmount, address[] groupMembers)
event RelayEntryTimeoutSlashingFailed(uint256 requestId, uint256 slashingAmount, address[] groupMembers)
event UnauthorizedSigningSlashed(uint64 groupId, uint256 unauthorizedSigningSlashingAmount, address[] groupMembers)
event UnauthorizedSigningSlashingFailed(uint64 groupId, uint256 unauthorizedSigningSlashingAmount, address[] groupMembers)
event CallbackFailed(uint256 entry, uint256 entrySubmittedBlock)
event InactivityClaimed(uint64 groupId, uint256 nonce, address notifier)
event OperatorRegistered(address stakingProvider, address operator)
event AuthorizationIncreased(address stakingProvider, address operator, uint96 fromAmount, uint96 toAmount)
event AuthorizationDecreaseRequested(address stakingProvider, address operator, uint96 fromAmount, uint96 toAmount, uint64 decreasingAt)
event AuthorizationDecreaseApproved(address stakingProvider)
event InvoluntaryAuthorizationDecreaseFailed(address stakingProvider, address operator, uint96 fromAmount, uint96 toAmount)
event OperatorJoinedSortitionPool(address stakingProvider, address operator)
event OperatorStatusUpdated(address stakingProvider, address operator)
constructor(contract SortitionPool _sortitionPool, contract IERC20 _tToken, contract IStaking _staking, contract BeaconDkgValidator _dkgValidator, contract ReimbursementPool _reimbursementPool) public
Assigns initial values to parameters to make the beacon work safely. These parameters are just proposed defaults and they might be updated with update*
functions after the contract deployment and before transferring the ownership to the governance contract.
modifier onlyStakingContract()
modifier onlyReimbursableAdmin()
function updateAuthorizationParameters(uint96 _minimumAuthorization, uint64 _authorizationDecreaseDelay, uint64 _authorizationDecreaseChangePeriod) external
Updates the values of authorization parameters.
Can be called only by the contract guvnor, which should be the random beacon governance contract. The caller is responsible for validating parameters.
_minimumAuthorization
uint96
New minimum authorization amount
_authorizationDecreaseDelay
uint64
New authorization decrease delay in seconds
_authorizationDecreaseChangePeriod
uint64
New authorization decrease change period in seconds
function updateRelayEntryParameters(uint256 relayEntrySoftTimeout, uint256 relayEntryHardTimeout, uint256 callbackGasLimit) external
Updates the values of relay entry parameters.
Can be called only by the contract guvnor, which should be the random beacon governance contract. The caller is responsible for validating parameters.
relayEntrySoftTimeout
uint256
New relay entry submission soft timeout
relayEntryHardTimeout
uint256
New relay entry hard timeout
callbackGasLimit
uint256
New callback gas limit
function updateGroupCreationParameters(uint256 groupCreationFrequency, uint256 groupLifetime, uint256 dkgResultChallengePeriodLength, uint256 dkgResultChallengeExtraGas, uint256 dkgResultSubmissionTimeout, uint256 dkgSubmitterPrecedencePeriodLength) external
Updates the values of group creation parameters.
Can be called only by the contract guvnor, which should be the random beacon governance contract. The caller is responsible for validating parameters.
groupCreationFrequency
uint256
New group creation frequency
groupLifetime
uint256
New group lifetime in blocks
dkgResultChallengePeriodLength
uint256
New DKG result challenge period length
dkgResultChallengeExtraGas
uint256
New DKG result challenge extra gas
dkgResultSubmissionTimeout
uint256
New DKG result submission timeout
dkgSubmitterPrecedencePeriodLength
uint256
New DKG result submitter precedence period length
function updateRewardParameters(uint256 sortitionPoolRewardsBanDuration, uint256 relayEntryTimeoutNotificationRewardMultiplier, uint256 unauthorizedSigningNotificationRewardMultiplier, uint256 dkgMaliciousResultNotificationRewardMultiplier) external
Updates the values of reward parameters.
Can be called only by the contract guvnor, which should be the random beacon governance contract. The caller is responsible for validating parameters.
sortitionPoolRewardsBanDuration
uint256
New sortition pool rewards ban duration in seconds.
relayEntryTimeoutNotificationRewardMultiplier
uint256
New value of the relay entry timeout notification reward multiplier
unauthorizedSigningNotificationRewardMultiplier
uint256
New value of the unauthorized signing notification reward multiplier
dkgMaliciousResultNotificationRewardMultiplier
uint256
New value of the DKG malicious result notification reward multiplier
function updateSlashingParameters(uint96 relayEntrySubmissionFailureSlashingAmount, uint96 maliciousDkgResultSlashingAmount, uint96 unauthorizedSigningSlashingAmount) external
Updates the values of slashing parameters.
Can be called only by the contract guvnor, which should be the random beacon governance contract. The caller is responsible for validating parameters.
relayEntrySubmissionFailureSlashingAmount
uint96
New relay entry submission failure amount
maliciousDkgResultSlashingAmount
uint96
New malicious DKG result slashing amount
unauthorizedSigningSlashingAmount
uint96
New unauthorized signing slashing amount
function updateGasParameters(uint256 dkgResultSubmissionGas, uint256 dkgResultApprovalGasOffset, uint256 notifyOperatorInactivityGasOffset, uint256 relayEntrySubmissionGasOffset) external
Updates the values of gas parameters.
Can be called only by the contract guvnor, which should be the random beacon governance contract. The caller is responsible for validating parameters.
dkgResultSubmissionGas
uint256
New DKG result submission gas
dkgResultApprovalGasOffset
uint256
New DKG result approval gas offset
notifyOperatorInactivityGasOffset
uint256
New operator inactivity notification gas offset
relayEntrySubmissionGasOffset
uint256
New relay entry submission gas offset
function setRequesterAuthorization(address requester, bool isAuthorized) external
Set authorization for requesters that can request a relay entry.
Can be called only by the contract guvnor, which should be the random beacon governance contract.
requester
address
Requester, can be a contract or EOA
isAuthorized
bool
True or false
function withdrawRewards(address stakingProvider) external
Withdraws application rewards for the given staking provider. Rewards are withdrawn to the staking provider's beneficiary address set in the staking contract. Reverts if staking provider has not registered the operator address.
Emits RewardsWithdrawn
event.
function withdrawIneligibleRewards(address recipient) external
Withdraws rewards belonging to operators marked as ineligible for sortition pool rewards.
Can be called only by the contract guvnor, which should be the random beacon governance contract.
recipient
address
Recipient of withdrawn rewards.
function registerOperator(address operator) external
Used by staking provider to set operator address that will operate a node. The given staking provider can set operator address only one time. The operator address can not be changed and must be unique. Reverts if the operator is already set for the staking provider or if the operator address is already in use. Reverts if there is a pending authorization decrease for the staking provider.
function joinSortitionPool() external
Lets the operator join the sortition pool. The operator address must be known - before calling this function, it has to be appointed by the staking provider by calling registerOperator
. Also, the operator must have the minimum authorization required by the beacon. Function reverts if there is no minimum stake authorized or if the operator is not known. If there was an authorization decrease requested, it is activated by starting the authorization decrease delay.
function updateOperatorStatus(address operator) external
Updates status of the operator in the sortition pool. If there was an authorization decrease requested, it is activated by starting the authorization decrease delay. Function reverts if the operator is not known.
function authorizationIncreased(address stakingProvider, uint96 fromAmount, uint96 toAmount) external
Used by T staking contract to inform the beacon that the authorized stake amount for the given staking provider increased.
Reverts if the authorization amount is below the minimum.
The function is not updating the sortition pool. Sortition pool state needs to be updated by the operator with a call to joinSortitionPool
or updateOperatorStatus
.
Can only be called by T staking contract.
function authorizationDecreaseRequested(address stakingProvider, uint96 fromAmount, uint96 toAmount) external
Used by T staking contract to inform the beacon that the authorization decrease for the given staking provider has been requested.
Reverts if the amount after deauthorization would be non-zero and lower than the minimum authorization.
Reverts if another authorization decrease request is pending for the staking provider and not enough time passed since the original request (see authorizationDecreaseChangePeriod
).
If the operator is not known (registerOperator
was not called) it lets to approveAuthorizationDecrease
immediately. If the operator is known (registerOperator
was called), the operator needs to update state of the sortition pool with a call to joinSortitionPool
or updateOperatorStatus
. After the sortition pool state is in sync, authorization decrease delay starts.
After authorization decrease delay passes, authorization decrease request needs to be approved with a call to approveAuthorizationDecrease
function.
If there is a pending authorization decrease request, it is overwritten, but only if enough time passed since the original request. Otherwise, the function reverts.
Can only be called by T staking contract.
function approveAuthorizationDecrease(address stakingProvider) external
Approves the previously registered authorization decrease request. Reverts if authorization decrease delay has not passed yet or if the authorization decrease was not requested for the given staking provider.
function involuntaryAuthorizationDecrease(address stakingProvider, uint96 fromAmount, uint96 toAmount) external
Used by T staking contract to inform the beacon the authorization has been decreased for the given staking provider involuntarily, as a result of slashing.
If the operator is not known (registerOperator
was not called) the function does nothing. The operator was never in a sortition pool so there is nothing to update.
If the operator is known, sortition pool is unlocked, and the operator is in the sortition pool, the sortition pool state is updated. If the sortition pool is locked, update needs to be postponed. Every other staker is incentivized to call updateOperatorStatus
for the problematic operator to increase their own rewards in the pool.
function genesis() external
Triggers group selection if there are no active groups.
function submitDkgResult(struct BeaconDkg.Result dkgResult) external
\x19Ethereum signed message:
before signing, so the message to sign is: \x19Ethereum signed message:\n${keccak256(chainID,groupPubKey,misbehaved,startBlock)}
dkgResult
struct BeaconDkg.Result
DKG result.
function notifyDkgTimeout() external
Notifies about DKG timeout.
function approveDkgResult(struct BeaconDkg.Result dkgResult) external
Approves DKG result. Can be called when the challenge period for the submitted result is finished. Considers the submitted result as valid, bans misbehaved group members from the sortition pool rewards, and completes the group creation by activating the candidate group. For the first submitterPrecedencePeriodLength
blocks after the end of the challenge period can be called only by the DKG result submitter. After that time, can be called by anyone.
dkgResult
struct BeaconDkg.Result
Result to approve. Must match the submitted result stored during submitDkgResult
.
function challengeDkgResult(struct BeaconDkg.Result dkgResult) external
Challenges DKG result. If the submitted result is proved to be invalid it reverts the DKG back to the result submission phase. It removes a candidate group that was previously registered with the DKG result submission.
Due to EIP-150 1/64 of the gas is not forwarded to the call, and will be kept to execute the remaining operations in the function after the call inside the try-catch. To eliminate a class of attacks related to the gas limit manipulation, this function requires an extra amount of gas to be left at the end of the execution.
dkgResult
struct BeaconDkg.Result
Result to challenge. Must match the submitted result stored during submitDkgResult
.
function getGroupCreationState() external view returns (enum BeaconDkg.State)
Check current group creation state.
function hasDkgTimedOut() external view returns (bool)
Checks if DKG timed out. The DKG timeout period includes time required for off-chain protocol execution and time for the result publication for all group members. After this time result cannot be submitted and DKG can be notified about the timeout.
[0]
bool
True if DKG timed out, false otherwise.
function getGroupsRegistry() external view returns (bytes32[])
function getGroup(uint64 groupId) external view returns (struct Groups.Group)
function getGroup(bytes groupPubKey) external view returns (struct Groups.Group)
function requestRelayEntry(contract IRandomBeaconConsumer callbackContract) external
Creates a request to generate a new relay entry, which will include a random number (by signing the previous entry's random number). Requester must be previously authorized by the governance.
callbackContract
contract IRandomBeaconConsumer
Beacon consumer callback contract.
function submitRelayEntry(bytes entry) external
Creates a new relay entry. Gas-optimized version that can be called only before the soft timeout. This should be the majority of cases.
entry
bytes
Group BLS signature over the previous entry.
function submitRelayEntry(bytes entry, uint32[] groupMembers) external
Creates a new relay entry.
entry
bytes
Group BLS signature over the previous entry.
groupMembers
uint32[]
Identifiers of group members.
function reportRelayEntryTimeout(uint32[] groupMembers) external
Reports a relay entry timeout.
groupMembers
uint32[]
Identifiers of group members.
function reportUnauthorizedSigning(bytes signedMsgSender, uint64 groupId, uint32[] groupMembers) external
Reports unauthorized groups signing. Must provide a valid signature of the sender's address as a message. Successful signature verification means the private key has been leaked and all group members should be punished by slashing their tokens. Group has to be active or expired. Unauthorized signing cannot be reported for a terminated group. In case of reporting unauthorized signing for a terminated group, or when the signature is invalid, function reverts.
signedMsgSender
bytes
Signature of the sender's address as a message.
groupId
uint64
Group that is being reported for leaking a private key.
groupMembers
uint32[]
Identifiers of group members.
function notifyOperatorInactivity(struct BeaconInactivity.Claim claim, uint256 nonce, uint32[] groupMembers) external
Notifies about operators who are inactive. Using this function, a majority of the group can decide about punishing specific group members who constantly fail doing their job. If the provided claim is proved to be valid and signed by sufficient number of group members, operators of members deemed as inactive are banned for sortition pool rewards for duration specified by _sortitionPoolRewardsBanDuration
parameter. The sender of the claim must be one of the claim signers. This function can be called only for active and non-terminated groups.
claim
struct BeaconInactivity.Claim
Operator inactivity claim.
nonce
uint256
Current inactivity claim nonce for the given group. Must be the same as the stored one.
groupMembers
uint32[]
Identifiers of group members.
function minimumAuthorization() external view returns (uint96)
The minimum authorization amount required so that operator can participate in the random beacon. This amount is required to execute slashing for providing a malicious DKG result or when a relay entry times out.
function isRelayRequestInProgress() external view returns (bool)
[0]
bool
Flag indicating whether a relay entry request is currently in progress.
function eligibleStake(address stakingProvider) external view returns (uint96)
Returns the current value of the staking provider's eligible stake. Eligible stake is defined as the currently authorized stake minus the pending authorization decrease. Eligible stake is what is used for operator's weight in the sortition pool. If the authorized stake minus the pending authorization decrease is below the minimum authorization, eligible stake is 0.
function availableRewards(address stakingProvider) external view returns (uint96)
Returns the amount of rewards available for withdrawal for the given staking provider. Reverts if staking provider has not registered the operator address.
function pendingAuthorizationDecrease(address stakingProvider) external view returns (uint96)
Returns the amount of stake that is pending authorization decrease for the given staking provider. If no authorization decrease has been requested, returns zero.
function remainingAuthorizationDecreaseDelay(address stakingProvider) external view returns (uint64)
Returns the remaining time in seconds that needs to pass before the requested authorization decrease can be approved. If the sortition pool state was not updated yet by the operator after requesting the authorization decrease, returns type(uint64).max
.
function stakingProviderToOperator(address stakingProvider) public view returns (address)
Returns operator registered for the given staking provider.
function operatorToStakingProvider(address operator) public view returns (address)
Returns staking provider of the given operator.
function isOperatorUpToDate(address operator) external view returns (bool)
Checks if the operator's authorized stake is in sync with operator's weight in the sortition pool. If the operator is not in the sortition pool and their authorized stake is non-zero, function returns false.
function isOperatorInPool(address operator) external view returns (bool)
Returns true if the given operator is in the sortition pool. Otherwise, returns false.
function selectGroup() external view returns (uint32[])
Selects a new group of operators. Can only be called when DKG is in progress and the pool is locked. At least one operator has to be registered in the pool, otherwise the function fails reverting the transaction.
[0]
uint32[]
IDs of selected group members.
function authorizationParameters() external view returns (uint96 minimumAuthorization, uint64 authorizationDecreaseDelay, uint64 authorizationDecreaseChangePeriod)
Returns authorization-related parameters of the beacon.
The minimum authorization is also returned by minimumAuthorization()
function, as a requirement of IApplication
interface.
minimumAuthorization
uint96
The minimum authorization amount required so that operator can participate in the random beacon. This amount is required to execute slashing for providing a malicious DKG result or when a relay entry times out.
authorizationDecreaseDelay
uint64
Delay in seconds that needs to pass between the time authorization decrease is requested and the time that request gets approved. Protects against free-riders earning rewards and not being active in the network.
authorizationDecreaseChangePeriod
uint64
Authorization decrease change period in seconds. It is the time, before authorization decrease delay end, during which the pending authorization decrease request can be overwritten. If set to 0, pending authorization decrease request can not be overwritten until the entire authorizationDecreaseDelay
ends. If set to value equal authorizationDecreaseDelay
, request can always be overwritten.
function relayEntryParameters() external view returns (uint256 relayEntrySoftTimeout, uint256 relayEntryHardTimeout, uint256 callbackGasLimit)
Returns relay-entry-related parameters of the beacon.
relayEntrySoftTimeout
uint256
Soft timeout in blocks for a group to submit the relay entry. If the soft timeout is reached for submitting the relay entry, the slashing starts.
relayEntryHardTimeout
uint256
Hard timeout in blocks for a group to submit the relay entry. After the soft timeout passes without relay entry submitted, all group members start getting slashed. The slashing amount increases linearly until the group submits the relay entry or until relayEntryHardTimeout
is reached. When the hard timeout is reached, each group member will get slashed for _relayEntrySubmissionFailureSlashingAmount
.
callbackGasLimit
uint256
Relay entry callback gas limit. This is the gas limit with which callback function provided in the relay request transaction is executed. The callback is executed with a new relay entry value in the same transaction the relay entry is submitted.
function groupCreationParameters() external view returns (uint256 groupCreationFrequency, uint256 groupLifetime, uint256 dkgResultChallengePeriodLength, uint256 dkgResultChallengeExtraGas, uint256 dkgResultSubmissionTimeout, uint256 dkgSubmitterPrecedencePeriodLength)
Returns group-creation-related parameters of the beacon.
groupCreationFrequency
uint256
The frequency of a new group creation. Groups are created with a fixed frequency of relay requests.
groupLifetime
uint256
Group lifetime in blocks. When a group reached its lifetime, it is no longer selected for new relay requests but may still be responsible for submitting relay entry if relay request assigned to that group is still pending.
dkgResultChallengePeriodLength
uint256
The number of blocks for which a DKG result can be challenged. Anyone can challenge DKG result for a certain number of blocks before the result is fully accepted and the group registered in the pool of active groups. If the challenge gets accepted, all operators who signed the malicious result get slashed for and the notifier gets rewarded.
dkgResultChallengeExtraGas
uint256
The extra gas required to be left at the end of the challenge DKG result transaction.
dkgResultSubmissionTimeout
uint256
Timeout in blocks for a group to submit the DKG result. All members are eligible to submit the DKG result. If dkgResultSubmissionTimeout
passes without the DKG result submitted, DKG is considered as timed out and no DKG result for this group creation can be submitted anymore.
dkgSubmitterPrecedencePeriodLength
uint256
Time during the DKG result approval stage when the submitter of the DKG result takes the precedence to approve the DKG result. After this time passes anyone can approve the DKG result.
function rewardParameters() external view returns (uint256 sortitionPoolRewardsBanDuration, uint256 relayEntryTimeoutNotificationRewardMultiplier, uint256 unauthorizedSigningNotificationRewardMultiplier, uint256 dkgMaliciousResultNotificationRewardMultiplier)
Returns reward-related parameters of the beacon.
sortitionPoolRewardsBanDuration
uint256
Duration of the sortition pool rewards ban imposed on operators who misbehaved during DKG by being inactive or disqualified and for operators that were identified by the rest of group members as inactive via notifyOperatorInactivity
.
relayEntryTimeoutNotificationRewardMultiplier
uint256
Percentage of the staking contract malicious behavior notification reward which will be transferred to the notifier reporting about relay entry timeout. Notifiers are rewarded from a notifiers treasury pool. For example, if notification reward is 1000 and the value of the multiplier is 5, the notifier will receive: 5% of 1000 = 50 per each operator affected.
unauthorizedSigningNotificationRewardMultiplier
uint256
Percentage of the staking contract malicious behavior notification reward which will be transferred to the notifier reporting about unauthorized signing. Notifiers are rewarded from a notifiers treasury pool. For example, if a notification reward is 1000 and the value of the multiplier is 5, the notifier will receive: 5% of 1000 = 50 per each operator affected.
dkgMaliciousResultNotificationRewardMultiplier
uint256
Percentage of the staking contract malicious behavior notification reward which will be transferred to the notifier reporting about a malicious DKG result. Notifiers are rewarded from a notifiers treasury pool. For example, if notification reward is 1000 and the value of the multiplier is 5, the notifier will receive: 5% of 1000 = 50 per each operator affected.
function slashingParameters() external view returns (uint96 relayEntrySubmissionFailureSlashingAmount, uint96 maliciousDkgResultSlashingAmount, uint96 unauthorizedSigningSlashingAmount)
Returns slashing-related parameters of the beacon.
relayEntrySubmissionFailureSlashingAmount
uint96
Slashing amount for not submitting relay entry. When relay entry hard timeout is reached without the relay entry submitted, each group member gets slashed for relayEntrySubmissionFailureSlashingAmount
. If the relay entry gets submitted after the soft timeout, but before the hard timeout, each group member gets slashed proportionally to relayEntrySubmissionFailureSlashingAmount
and the time passed since the soft deadline.
maliciousDkgResultSlashingAmount
uint96
Slashing amount for submitting a malicious DKG result. Every DKG result submitted can be challenged for the time of dkg.ResultChallengePeriodLength
. If the DKG result submitted is challenged and proven to be malicious, the operator who submitted the malicious result is slashed for maliciousDkgResultSlashingAmount
.
unauthorizedSigningSlashingAmount
uint96
Slashing amount when an unauthorized signing has been proved, which means the private key leaked and all the group members should be punished.
function gasParameters() external view returns (uint256 dkgResultSubmissionGas, uint256 dkgResultApprovalGasOffset, uint256 notifyOperatorInactivityGasOffset, uint256 relayEntrySubmissionGasOffset)
Returns gas-related parameters of the beacon.
dkgResultSubmissionGas
uint256
Calculated gas cost for submitting a DKG result. This will be refunded as part of the DKG approval process.
dkgResultApprovalGasOffset
uint256
Gas that is meant to balance the DKG result approval's overall cost.
notifyOperatorInactivityGasOffset
uint256
Gas that is meant to balance the operator inactivity notification cost.
relayEntrySubmissionGasOffset
uint256
Gas that is meant to balance the relay entry submission cost.
This file documents a contract which is not yet deployed to Mainnet.
Owns the RandomBeacon
contract and is responsible for updating its governable parameters in respect to governance delay individual for each parameter.
uint256 newGovernanceDelay
uint256 governanceDelayChangeInitiated
address newRandomBeaconGovernance
uint256 randomBeaconGovernanceTransferInitiated
uint256 newRelayEntrySoftTimeout
uint256 relayEntrySoftTimeoutChangeInitiated
uint256 newRelayEntryHardTimeout
uint256 relayEntryHardTimeoutChangeInitiated
uint256 newCallbackGasLimit
uint256 callbackGasLimitChangeInitiated
uint256 newGroupCreationFrequency
uint256 groupCreationFrequencyChangeInitiated
uint256 newGroupLifetime
uint256 groupLifetimeChangeInitiated
uint256 newDkgResultChallengePeriodLength
uint256 dkgResultChallengePeriodLengthChangeInitiated
uint256 newDkgResultChallengeExtraGas
uint256 dkgResultChallengeExtraGasChangeInitiated
uint256 newDkgResultSubmissionTimeout
uint256 dkgResultSubmissionTimeoutChangeInitiated
uint256 newDkgSubmitterPrecedencePeriodLength
uint256 dkgSubmitterPrecedencePeriodLengthChangeInitiated
uint96 newRelayEntrySubmissionFailureSlashingAmount
uint256 relayEntrySubmissionFailureSlashingAmountChangeInitiated
uint96 newMaliciousDkgResultSlashingAmount
uint256 maliciousDkgResultSlashingAmountChangeInitiated
uint96 newUnauthorizedSigningSlashingAmount
uint256 unauthorizedSigningSlashingAmountChangeInitiated
uint256 newSortitionPoolRewardsBanDuration
uint256 sortitionPoolRewardsBanDurationChangeInitiated
uint256 newRelayEntryTimeoutNotificationRewardMultiplier
uint256 relayEntryTimeoutNotificationRewardMultiplierChangeInitiated
uint256 newUnauthorizedSigningNotificationRewardMultiplier
uint256 unauthorizedSigningNotificationRewardMultiplierChangeInitiated
uint96 newMinimumAuthorization
uint256 minimumAuthorizationChangeInitiated
uint64 newAuthorizationDecreaseDelay
uint256 authorizationDecreaseDelayChangeInitiated
uint64 newAuthorizationDecreaseChangePeriod
uint256 authorizationDecreaseChangePeriodChangeInitiated
uint256 newDkgMaliciousResultNotificationRewardMultiplier
uint256 dkgMaliciousResultNotificationRewardMultiplierChangeInitiated
uint256 newDkgResultSubmissionGas
uint256 dkgResultSubmissionGasChangeInitiated
uint256 newDkgResultApprovalGasOffset
uint256 dkgResultApprovalGasOffsetChangeInitiated
uint256 newNotifyOperatorInactivityGasOffset
uint256 notifyOperatorInactivityGasOffsetChangeInitiated
uint256 newRelayEntrySubmissionGasOffset
uint256 relayEntrySubmissionGasOffsetChangeInitiated
contract RandomBeacon randomBeacon
uint256 governanceDelay
event GovernanceDelayUpdateStarted(uint256 governanceDelay, uint256 timestamp)
event GovernanceDelayUpdated(uint256 governanceDelay)
event RandomBeaconGovernanceTransferStarted(address newRandomBeaconGovernance, uint256 timestamp)
event RandomBeaconGovernanceTransferred(address newRandomBeaconGovernance)
event RelayEntrySoftTimeoutUpdateStarted(uint256 relayEntrySoftTimeout, uint256 timestamp)
event RelayEntrySoftTimeoutUpdated(uint256 relayEntrySoftTimeout)
event RelayEntryHardTimeoutUpdateStarted(uint256 relayEntryHardTimeout, uint256 timestamp)
event RelayEntryHardTimeoutUpdated(uint256 relayEntryHardTimeout)
event CallbackGasLimitUpdateStarted(uint256 callbackGasLimit, uint256 timestamp)
event CallbackGasLimitUpdated(uint256 callbackGasLimit)
event GroupCreationFrequencyUpdateStarted(uint256 groupCreationFrequency, uint256 timestamp)
event GroupCreationFrequencyUpdated(uint256 groupCreationFrequency)
event GroupLifetimeUpdateStarted(uint256 groupLifetime, uint256 timestamp)
event GroupLifetimeUpdated(uint256 groupLifetime)
event DkgResultChallengePeriodLengthUpdateStarted(uint256 dkgResultChallengePeriodLength, uint256 timestamp)
event DkgResultChallengePeriodLengthUpdated(uint256 dkgResultChallengePeriodLength)
event DkgResultChallengeExtraGasUpdateStarted(uint256 dkgResultChallengeExtraGas, uint256 timestamp)
event DkgResultChallengeExtraGasUpdated(uint256 dkgResultChallengeExtraGas)
event DkgResultSubmissionTimeoutUpdateStarted(uint256 dkgResultSubmissionTimeout, uint256 timestamp)
event DkgResultSubmissionTimeoutUpdated(uint256 dkgResultSubmissionTimeout)
event DkgSubmitterPrecedencePeriodLengthUpdateStarted(uint256 submitterPrecedencePeriodLength, uint256 timestamp)
event DkgSubmitterPrecedencePeriodLengthUpdated(uint256 submitterPrecedencePeriodLength)
event RelayEntrySubmissionFailureSlashingAmountUpdateStarted(uint96 relayEntrySubmissionFailureSlashingAmount, uint256 timestamp)
event RelayEntrySubmissionFailureSlashingAmountUpdated(uint96 relayEntrySubmissionFailureSlashingAmount)
event MaliciousDkgResultSlashingAmountUpdateStarted(uint96 maliciousDkgResultSlashingAmount, uint256 timestamp)
event MaliciousDkgResultSlashingAmountUpdated(uint96 maliciousDkgResultSlashingAmount)
event UnauthorizedSigningSlashingAmountUpdateStarted(uint96 unauthorizedSigningSlashingAmount, uint256 timestamp)
event UnauthorizedSigningSlashingAmountUpdated(uint96 unauthorizedSigningSlashingAmount)
event SortitionPoolRewardsBanDurationUpdateStarted(uint256 sortitionPoolRewardsBanDuration, uint256 timestamp)
event SortitionPoolRewardsBanDurationUpdated(uint256 sortitionPoolRewardsBanDuration)
event RelayEntryTimeoutNotificationRewardMultiplierUpdateStarted(uint256 relayEntryTimeoutNotificationRewardMultiplier, uint256 timestamp)
event RelayEntryTimeoutNotificationRewardMultiplierUpdated(uint256 relayEntryTimeoutNotificationRewardMultiplier)
event UnauthorizedSigningNotificationRewardMultiplierUpdateStarted(uint256 unauthorizedSigningTimeoutNotificationRewardMultiplier, uint256 timestamp)
event UnauthorizedSigningNotificationRewardMultiplierUpdated(uint256 unauthorizedSigningTimeoutNotificationRewardMultiplier)
event MinimumAuthorizationUpdateStarted(uint96 minimumAuthorization, uint256 timestamp)
event MinimumAuthorizationUpdated(uint96 minimumAuthorization)
event AuthorizationDecreaseDelayUpdateStarted(uint64 authorizationDecreaseDelay, uint256 timestamp)
event AuthorizationDecreaseDelayUpdated(uint64 authorizationDecreaseDelay)
event AuthorizationDecreaseChangePeriodUpdateStarted(uint64 authorizationDecreaseChangePeriod, uint256 timestamp)
event AuthorizationDecreaseChangePeriodUpdated(uint64 authorizationDecreaseChangePeriod)
event DkgMaliciousResultNotificationRewardMultiplierUpdateStarted(uint256 dkgMaliciousResultNotificationRewardMultiplier, uint256 timestamp)
event DkgMaliciousResultNotificationRewardMultiplierUpdated(uint256 dkgMaliciousResultNotificationRewardMultiplier)
event DkgResultSubmissionGasUpdateStarted(uint256 dkgResultSubmissionGas, uint256 timestamp)
event DkgResultSubmissionGasUpdated(uint256 dkgResultSubmissionGas)
event DkgResultApprovalGasOffsetUpdateStarted(uint256 dkgResultApprovalGasOffset, uint256 timestamp)
event DkgResultApprovalGasOffsetUpdated(uint256 dkgResultApprovalGasOffset)
event NotifyOperatorInactivityGasOffsetUpdateStarted(uint256 notifyOperatorInactivityGasOffset, uint256 timestamp)
event NotifyOperatorInactivityGasOffsetUpdated(uint256 notifyOperatorInactivityGasOffset)
event RelayEntrySubmissionGasOffsetUpdateStarted(uint256 relayEntrySubmissionGasOffset, uint256 timestamp)
event RelayEntrySubmissionGasOffsetUpdated(uint256 relayEntrySubmissionGasOffset)
modifier onlyAfterGovernanceDelay(uint256 changeInitiatedTimestamp)
Reverts if called before the governance delay elapses.
changeInitiatedTimestamp
uint256
Timestamp indicating the beginning of the change.
constructor(contract RandomBeacon _randomBeacon, uint256 _governanceDelay) public
function beginGovernanceDelayUpdate(uint256 _newGovernanceDelay) external
Begins the governance delay update process.
Can be called only by the contract owner.
_newGovernanceDelay
uint256
New governance delay
function finalizeGovernanceDelayUpdate() external
Finalizes the governance delay update process.
Can be called only by the contract owner, after the governance delay elapses.
function beginRandomBeaconGovernanceTransfer(address _newRandomBeaconGovernance) external
Begins the random beacon governance transfer process.
Can be called only by the current contract governance.
function finalizeRandomBeaconGovernanceTransfer() external
Finalizes the random beacon governance transfer process.
Can be called only by the current contract governance, after the governance delay elapses.
function beginRelayEntrySoftTimeoutUpdate(uint256 _newRelayEntrySoftTimeout) external
Begins the relay entry soft timeout update process.
Can be called only by the contract owner.
_newRelayEntrySoftTimeout
uint256
New relay entry submission timeout in blocks
function finalizeRelayEntrySoftTimeoutUpdate() external
Finalizes the relay entry soft timeout update process.
Can be called only by the contract owner, after the governance delay elapses.
function beginRelayEntryHardTimeoutUpdate(uint256 _newRelayEntryHardTimeout) external
Begins the relay entry hard timeout update process.
Can be called only by the contract owner.
_newRelayEntryHardTimeout
uint256
New relay entry hard timeout in blocks
function finalizeRelayEntryHardTimeoutUpdate() external
Finalizes the relay entry hard timeout update process.
Can be called only by the contract owner, after the governance delay elapses.
function beginCallbackGasLimitUpdate(uint256 _newCallbackGasLimit) external
Begins the callback gas limit update process.
Can be called only by the contract owner.
_newCallbackGasLimit
uint256
New callback gas limit
function finalizeCallbackGasLimitUpdate() external
Finalizes the callback gas limit update process.
Can be called only by the contract owner, after the governance delay elapses.
function beginGroupCreationFrequencyUpdate(uint256 _newGroupCreationFrequency) external
Begins the group creation frequency update process.
Can be called only by the contract owner.
_newGroupCreationFrequency
uint256
New group creation frequency
function finalizeGroupCreationFrequencyUpdate() external
Finalizes the group creation frequency update process.
Can be called only by the contract owner, after the governance delay elapses.
function beginGroupLifetimeUpdate(uint256 _newGroupLifetime) external
Begins the group lifetime update process. Group lifetime needs to be shorter than the authorization decrease delay to ensure every active group is backed by enough stake. A new group lifetime value is in blocks and has to be calculated based on the average block time and authorization decrease delay which value is in seconds.
Can be called only by the contract owner.
_newGroupLifetime
uint256
New group lifetime in blocks
function finalizeGroupLifetimeUpdate() external
Finalizes the group creation frequency update process.
Can be called only by the contract owner, after the governance delay elapses.
function beginDkgResultChallengePeriodLengthUpdate(uint256 _newDkgResultChallengePeriodLength) external
Begins the DKG result challenge period length update process.
Can be called only by the contract owner.
_newDkgResultChallengePeriodLength
uint256
New DKG result challenge period length in blocks
function finalizeDkgResultChallengePeriodLengthUpdate() external
Finalizes the DKG result challenge period length update process.
Can be called only by the contract owner, after the governance delay elapses.
function beginDkgResultChallengeExtraGasUpdate(uint256 _newDkgResultChallengeExtraGas) external
Begins the DKG result challenge extra gas update process.
Can be called only by the contract owner.
_newDkgResultChallengeExtraGas
uint256
New DKG result challenge extra gas
function finalizeDkgResultChallengeExtraGasUpdate() external
Finalizes the DKG result challenge extra gas update process.
Can be called only by the contract owner, after the governance delay elapses.
function beginDkgResultSubmissionTimeoutUpdate(uint256 _newDkgResultSubmissionTimeout) external
Begins the DKG result submission timeout update process.
Can be called only by the contract owner.
_newDkgResultSubmissionTimeout
uint256
New DKG result submission timeout in blocks
function finalizeDkgResultSubmissionTimeoutUpdate() external
Finalizes the DKG result submission timeout update process.
Can be called only by the contract owner, after the governance delay elapses.
function beginDkgSubmitterPrecedencePeriodLengthUpdate(uint256 _newDkgSubmitterPrecedencePeriodLength) external
Begins the DKG submitter precedence period length.
Can be called only by the contract owner.
_newDkgSubmitterPrecedencePeriodLength
uint256
New DKG submitter precedence period length in blocks
function finalizeDkgSubmitterPrecedencePeriodLengthUpdate() external
Finalizes the DKG submitter precedence period length.
Can be called only by the contract owner, after the governance delay elapses.
function beginSortitionPoolRewardsBanDurationUpdate(uint256 _newSortitionPoolRewardsBanDuration) external
Begins the sortition pool rewards ban duration update process.
Can be called only by the contract owner.
_newSortitionPoolRewardsBanDuration
uint256
New sortition pool rewards ban duration.
function finalizeSortitionPoolRewardsBanDurationUpdate() external
Finalizes the sortition pool rewards ban duration update process.
Can be called only by the contract owner, after the governance delay elapses.
function beginRelayEntryTimeoutNotificationRewardMultiplierUpdate(uint256 _newRelayEntryTimeoutNotificationRewardMultiplier) external
Begins the relay entry timeout notification reward multiplier update process.
Can be called only by the contract owner.
_newRelayEntryTimeoutNotificationRewardMultiplier
uint256
New relay entry timeout notification reward multiplier.
function beginUnauthorizedSigningNotificationRewardMultiplierUpdate(uint256 _newUnauthorizedSigningNotificationRewardMultiplier) external
Begins the unauthorized signing notification reward multiplier update process.
Can be called only by the contract owner.
_newUnauthorizedSigningNotificationRewardMultiplier
uint256
New unauthorized signing notification reward multiplier.
function finalizeUnauthorizedSigningNotificationRewardMultiplierUpdate() external
Finalizes the unauthorized signing notification reward multiplier update process.
Can be called only by the contract owner, after the governance delay elapses.
function finalizeRelayEntryTimeoutNotificationRewardMultiplierUpdate() external
Finalizes the relay entry timeout notification reward multiplier update process.
Can be called only by the contract owner, after the governance delay elapses.
function beginDkgMaliciousResultNotificationRewardMultiplierUpdate(uint256 _newDkgMaliciousResultNotificationRewardMultiplier) external
Begins the DKG malicious result notification reward multiplier update process.
Can be called only by the contract owner.
_newDkgMaliciousResultNotificationRewardMultiplier
uint256
New DKG malicious result notification reward multiplier.
function finalizeDkgMaliciousResultNotificationRewardMultiplierUpdate() external
Finalizes the DKG malicious result notification reward multiplier update process.
Can be called only by the contract owner, after the governance delay elapses.
function beginRelayEntrySubmissionFailureSlashingAmountUpdate(uint96 _newRelayEntrySubmissionFailureSlashingAmount) external
Begins the relay entry submission failure slashing amount update process.
Can be called only by the contract owner.
_newRelayEntrySubmissionFailureSlashingAmount
uint96
New relay entry submission failure slashing amount
function finalizeRelayEntrySubmissionFailureSlashingAmountUpdate() external
Finalizes the relay entry submission failure slashing amount update process.
Can be called only by the contract owner, after the governance delay elapses.
function beginDkgResultSubmissionGasUpdate(uint256 _newDkgResultSubmissionGas) external
Begins the DKG result submission gas update process.
Can be called only by the contract owner.
_newDkgResultSubmissionGas
uint256
New relay entry submission gas offset
function finalizeDkgResultSubmissionGasUpdate() external
Finalizes DKG result submission gas update process.
Can be called only by the contract owner, after the governance delay elapses.
function beginDkgResultApprovalGasOffsetUpdate(uint256 _newDkgResultApprovalGasOffset) external
Begins the DKG result approval gas offset update process.
Can be called only by the contract owner.
_newDkgResultApprovalGasOffset
uint256
New DKG approval gas offset
function finalizeDkgResultApprovalGasOffsetUpdate() external
Finalizes the DKG result approval gas offset update process.
Can be called only by the contract owner, after the governance delay elapses.
function beginNotifyOperatorInactivityGasOffsetUpdate(uint256 _newNotifyOperatorInactivityGasOffset) external
Begins the notify operator inactivity gas offset update process.
Can be called only by the contract owner.
_newNotifyOperatorInactivityGasOffset
uint256
New operator inactivity notification gas offset
function finalizeNotifyOperatorInactivityGasOffsetUpdate() external
Finalizes the notify operator inactivity gas offset update process.
Can be called only by the contract owner, after the governance delay elapses.
function beginRelayEntrySubmissionGasOffsetUpdate(uint256 _newRelayEntrySubmissionGasOffset) external
Begins the relay entry submission gas offset update process.
Can be called only by the contract owner.
_newRelayEntrySubmissionGasOffset
uint256
New relay entry submission gas offset
function finalizeRelayEntrySubmissionGasOffsetUpdate() external
Finalizes relay entry submission gas offset update process.
Can be called only by the contract owner, after the governance delay elapses.
function beginMaliciousDkgResultSlashingAmountUpdate(uint96 _newMaliciousDkgResultSlashingAmount) external
Begins the malicious DKG result slashing amount update process.
Can be called only by the contract owner.
_newMaliciousDkgResultSlashingAmount
uint96
New malicious DKG result slashing amount
function finalizeMaliciousDkgResultSlashingAmountUpdate() external
Finalizes the malicious DKG result slashing amount update process.
Can be called only by the contract owner, after the governance delay elapses.
function beginUnauthorizedSigningSlashingAmountUpdate(uint96 _newUnauthorizedSigningSlashingAmount) external
Begins the unauthorized signing slashing amount update process.
Can be called only by the contract owner.
_newUnauthorizedSigningSlashingAmount
uint96
New unauthorized signing slashing amount
function finalizeUnauthorizedSigningSlashingAmountUpdate() external
Finalizes the unauthorized signing slashing amount update process.
Can be called only by the contract owner, after the governance delay elapses.
function beginMinimumAuthorizationUpdate(uint96 _newMinimumAuthorization) external
Begins the minimum authorization amount update process.
Can be called only by the contract owner.
_newMinimumAuthorization
uint96
New minimum authorization amount.
function finalizeMinimumAuthorizationUpdate() external
Finalizes the minimum authorization amount update process.
Can be called only by the contract owner, after the governance delay elapses.
function beginAuthorizationDecreaseDelayUpdate(uint64 _newAuthorizationDecreaseDelay) external
Begins the authorization decrease delay update process.
Can be called only by the contract owner.
_newAuthorizationDecreaseDelay
uint64
New authorization decrease delay
function finalizeAuthorizationDecreaseDelayUpdate() external
Finalizes the authorization decrease delay update process.
Can be called only by the contract owner, after the governance delay elapses.
function beginAuthorizationDecreaseChangePeriodUpdate(uint64 _newAuthorizationDecreaseChangePeriod) external
Begins the authorization decrease change period update process.
Can be called only by the contract owner.
_newAuthorizationDecreaseChangePeriod
uint64
New authorization decrease change period
function finalizeAuthorizationDecreaseChangePeriodUpdate() external
Finalizes the authorization decrease change period update process.
Can be called only by the contract owner, after the governance delay elapses.
function setRequesterAuthorization(address requester, bool isAuthorized) external
Set authorization for requesters that can request a relay entry. It can be done by the governance only.
requester
address
Requester, can be a contract or EOA
isAuthorized
bool
True or false
function withdrawIneligibleRewards(address recipient) external
Withdraws rewards belonging to operators marked as ineligible for sortition pool rewards.
Can be called only by the contract owner.
recipient
address
Recipient of withdrawn rewards.
function getRemainingGovernanceDelayUpdateTime() external view returns (uint256)
Get the time remaining until the governance delay can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingRandomBeaconGovernanceTransferDelayTime() external view returns (uint256)
Get the time remaining until the random beacon governance can be transferred.
[0]
uint256
Remaining time in seconds.
function getRemainingRelayEntrySoftTimeoutUpdateTime() external view returns (uint256)
Get the time remaining until the relay entry submission soft timeout can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingRelayEntryHardTimeoutUpdateTime() external view returns (uint256)
Get the time remaining until the relay entry hard timeout can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingCallbackGasLimitUpdateTime() external view returns (uint256)
Get the time remaining until the callback gas limit can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingGroupCreationFrequencyUpdateTime() external view returns (uint256)
Get the time remaining until the group creation frequency can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingGroupLifetimeUpdateTime() external view returns (uint256)
Get the time remaining until the group lifetime can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingDkgResultChallengePeriodLengthUpdateTime() external view returns (uint256)
Get the time remaining until the DKG result challenge period length can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingDkgResultChallengeExtraGasUpdateTime() external view returns (uint256)
Get the time remaining until the DKG result challenge extra gas can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingDkgResultSubmissionTimeoutUpdateTime() external view returns (uint256)
Get the time remaining until the DKG result submission timeout can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingDkgSubmitterPrecedencePeriodLengthUpdateTime() external view returns (uint256)
Get the time remaining until the wallet owner can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingRelayEntrySubmissionFailureSlashingAmountUpdateTime() external view returns (uint256)
Get the time remaining until the relay entry submission failure slashing amount can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingMaliciousDkgResultSlashingAmountUpdateTime() external view returns (uint256)
Get the time remaining until the malicious DKG result slashing amount can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingUnauthorizedSigningSlashingAmountUpdateTime() external view returns (uint256)
Get the time remaining until the unauthorized signing slashing amount can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingMimimumAuthorizationUpdateTime() external view returns (uint256)
Get the time remaining until the minimum authorization amount can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingAuthorizationDecreaseDelayUpdateTime() external view returns (uint256)
Get the time remaining until the authorization decrease delay can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingAuthorizationDecreaseChangePeriodUpdateTime() external view returns (uint256)
Get the time remaining until the authorization decrease change period can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingSortitionPoolRewardsBanDurationUpdateTime() external view returns (uint256)
Get the time remaining until the sortition pool rewards ban duration can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingRelayEntryTimeoutNotificationRewardMultiplierUpdateTime() external view returns (uint256)
Get the time remaining until the relay entry timeout notification reward multiplier duration can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingUnauthorizedSigningNotificationRewardMultiplierUpdateTime() external view returns (uint256)
Get the time remaining until the unauthorized signing notification reward multiplier duration can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingDkgMaliciousResultNotificationRewardMultiplierUpdateTime() external view returns (uint256)
Get the time remaining until the DKG malicious result notification reward multiplier duration can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingDkgResultSubmissionGasUpdateTime() external view returns (uint256)
Get the time remaining until the DKG result submission gas duration can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingDkgResultApprovalGasOffsetUpdateTime() external view returns (uint256)
Get the time remaining until the DKG approval gas offset duration can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingNotifyOperatorInactivityGasOffsetUpdateTime() external view returns (uint256)
Get the time remaining until the operator inactivity notification gas offset duration can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingRelayEntrySubmissionGasOffsetUpdateTime() external view returns (uint256)
Get the time remaining until the relay entry submission gas offset duration can be updated.
[0]
uint256
Remaining time in seconds.
function getRemainingChangeTime(uint256 changeTimestamp) internal view returns (uint256)
Gets the time remaining until the governable parameter update can be committed.
changeTimestamp
uint256
Timestamp indicating the beginning of the change.
[0]
uint256
Remaining time in seconds.