Payment channels

Keepers transact with the Coordinator off-chain using payment channels.
In order to maximize throughput and minimize gas costs, Keepers transact with the Coordinator using off-chain payment channels. They will open this payment channel by staking ROOK into a contract and then pass signed commitments back and forth with the Coordinator. The payment channel state can be brought on-chain to settle in the event of a withdrawal, collection, or dispute. Keepers can submit bids for many auctions simultaneously. This gives Keepers the ability to perform all bids through a single KSA with high throughput rather than have to set up multiple KSAs to increase bidding throughput. Keepers may; however, choose to use mutliple KSAs to separate accounts or strategies. Having this payment channel off-chain, dramatically reduces gas costs for Keepers participating in the Rook Protocol.

Opening a Payment Channel

Payment channels are opened and settled on-chain using the staking contract. To open a payment channel, Keepers should stake Rook into the staking contract using the stake function. They should do this from their KSA.
function stake(uint256 _amount) public;
A Keeper that already has a payment channel open can call this function again to add more spendable Rook to their payment channel. The current amount of Rook staked by a KSA is stored in the public stakedAmount map on the contract.
mapping (address => uint256) public stakedAmount;

Transacting Using the Payment Channel

Once the payment channel is open, the Keeper and Coordinator transact with this staked Rook by passing back and forth signed off-chain commitments. A commitment is defined in the staking contract as follows:
struct StakeCommitment {
address stakeAddress;
uint256 stakeSpent;
uint256 stakeNonce;
uint256 channelNonce;
bytes data;
bytes stakeAddressSignature;
bytes coordinatorSignature;
  • stakeAddress is the KSA that the Keeper used to stake the Rook and that they plan to have sign commitments.
  • stakeSpent is how much of the Rook that the Keeper has staked has been spent. This will increase when the Keeper makes bids and decrease when the Keeper is issued refunds.
  • stakeNonce is an increasing number used to prevent replays of previously used commitments.
  • channelNonce is an increasing number that is incremented every time a Keeper closes their payment channel.
  • data will contain arbitrary information depending on the type of stake commitment.
  • stakeAddressSignature is an Ethereum signature from the KSA using the hash from the function stakeCommitmentHash as the message.
  • coordinatorSignature is an Ethereum signature from the Coordinator using the hash from the function stakeCommitmentHash as the message.
function stakeCommitmentHash(
address _stakeAddress,
uint256 _stakeSpent,
uint256 _stakeNonce,
uint256 _channelNonce,
bytes memory _data
) public pure returns (bytes32);
Bid and Refund Sequence Diagram

Submitting Bid Commitments

Keepers will spend Rook when they submit bids to the Coordinator. Here is an example of the bid JSON:
  • The commitment information is under the payment field. The relevant fields are stakeAddress, stakeSpent, stakeNonce, channelNonce, previousCommitmentHash, and stakeAddressSignature.
  • Set the bid amount using stakeSpent. It is initialized to 0 and should be incremented relative to the latest value by an amount equal to the desired bid. So if the current value of stakeSpent is 100, to create a payment that bids 50, stakeSpent should be set to 150. stakeSpent can only be set as high the amount of Rook the keeper has staked. The Coordinator will reject any bids that attempt to bid higher than the staked amount.
  • The information you need to prepare this commitment is obtained from the keeperStakeChannel API. You can also track it yourself locally, but we recommend regularly syncing up with the Coordinator to ensure you don't get out of sync.
  • stakeNonce must be incremented by 1 in each new commitment.
  • channelNonce should always match the current on-chain value
  • previousCommitmentHash is used by the Coordinator as a validation mechanism. This is the data field in the commitment definition in the staking contract. When creating a new bid commitment, previousCommitmentHash must be set to the output of stakeCommitmentHash on the preceding commitment. Important: If it is the first bid commitment and there is no preceding commitment, previousCommitmentHash should be set to "0x0000000000000000000000000000000000000000" as in the example above.
  • stakeAddressSignature should be an Ethereum signature from the KSA over the output of stakeCommitmentHash on the new commitment.
Upon receiving a bid commitment from a Keeper, the Coordinator will validate the bid commitment according to the rules above. If it is valid, the Coordinator will record the bid commitment and sign it. The latest commitment the Coordinator has validated and recorded can be checked using the /keeperStakeChannel endpoint.


Keepers will accumulate refunds for any bids they did not win according to the Bid Outcome logic TODO Create this page. The refundable ROOK will accumulate for every auction they lose. ROOK bids will also be refunded to Keepers who win an auction but fail to fill the orders bid upon, although their reputation will suffer. The amount of refundable ROOK that has been accumulated can be checked using the /keeperStakeChannel endpoint.
Request a refund by sending a refund request websocket message. In the payload, the KSA must include the latest stakeAddress, channelNonce, and stakeNonce for their payment channel, and a stakeAddressSignature. The stakeAddressSignature is an Ethereum signature on the message hash returned by the /refundRequestMessageHash endpoint. The coordinator will create a new commitment using the same rules the Keeper uses to create a bid commitment except that the value for stakeSpent is determined by subtracting the total refundable ROOK from the current latest commitment's stakeSpent.
In the event a refund is requested and issued, the stakeSpent will be reduced. This means the Keeper must generate new bid commitments using the refund commitment as the latest commitment state. For example: say a keeper's stakeSpent is 150 and they have accumulated a refund of 50. They request and are issued a refund. Their stakeSpent is now reduced to 100. Now let's say another profit opportunity appears and they place another bid. The keeper must bid based on the most recent commitment, which is the refund commitment. So if the keeper wants to bid 25, they will bid with a stakeSpent of 125.

Closing a Payment Channel

To close a payment channel and withdraw their unspent ROOK, Keepers will submit the latest commitment to the staking contract. The staking contract offers a timelocked and an instant withdrawal.

Timelocked Withdrawal

A Keeper will initiate the timelocked withdrawal process using the initiateTimelockedWithdrawal function.
function initiateTimelockedWithdrawal(
StakeCommitment memory _commitment
) external;
The keeper should use the latest commitment in the payment channel and if they have any outstanding refunds, they should request a refund from the Coordinator first. The Keeper can retrieve the latest commitment using the /keeperStakeChannel endpoint. The commitment may or may not have the KSA signature on it already depending on if it's a bid or a refund commitment. Since the Coordinator generates the refund commitment, it will not have the KSA signature on it. The Keeper should use previousCommitmentHash for the data field in StakeCommitment. This will begin a 7 day timelock where the Coordinator can challenge the withdrawal by submitting a commitment with a higher stake nonce using the same function. Once the 7 day timelock has concluded, the Keeper can complete their withdrawal using executeTimelockedWithdrawal.
function executeTimelockedWithdrawal(
address _stakeAddress
) public;

Instant Withdrawal

A Keeper can initiate an instant withdrawal by retrieving a message hash using the /instantWithdrawalRequestMessageHash endpoint, generating an Ethereum signature on it using their KSA, and then submitting it to the /initiateInstantWithdrawal endpoint. Once the Coordinator sees this instant withdrawal request, it will no longer allow the KSA to bid on auctions and then it will wait some amount of time to allow all active auctions containing a bid from the KSA to conclude. After the auctions have concluded, it will issue any outstanding refunds and generate an instant withdrawal signature. The latest commitment and the instant withdrawal signature on it can be retrieved using the /keeperStakeChannel endpoint. The Keeper should then generate their own instant withdrawal signature. They should generate an Ethereum signature on the output of stakePaymentHash using the latest commitment and the stake contract constant INSTANT_WITHDRAWAL_COMMITMENT_DATA as the value for the data parameter. They can then complete the withdrawal using the executeInstantWithdrawal function on the stake contract.
function executeInstantWithdrawal(
StakeCommitment memory _commitment
) external;
  • The signatures passed in should be the instant withdrawal signatures.
  • The value for data should be the stake contract constant INSTANT_WITHDRAWAL_COMMITMENT_DATA.

Retrieving the Latest State

Note that the on-chain payment channel state will more often than not be stale since transacting using the payment channel occurs off-chain. There will only be on-chain payment channel interactions in certain circumstances: when a keeper stakes Rook to the payment channel, when a coordinator performs a partial settlement of spent Rook to accumulate claimable Rook, and when a keeper performs a withdrawal. The Coordinator is the source of truth for the most up to date payment channel state and this state should be retrieved using the /keeperStakeChannel endpoint.