This guide is a technical guide with examples of how to implement Liquid Collective’s redemption workflow into a Platform's workflow. For more information on Liquid Collective’s ETH staking withdrawal architecture, check out
Pre-read
Review the for the Alluvial API.
Implementation
The guide will using the Holesky environment for smart contract calls.
For this example, the implementation will use both smart contract calls and offchain calls via the Redemption API.
The Redemption API is an offchain API that exposes:
The list of redeem requests for an owner (wallet) including their redemption IDs
The status information about redeem requests (including id, redeemed amount, satisfaction status, claim status, etc.)
The heights of the withdrawal stack (ETH supplied) and the redeem queue (LsETH queued to redeem). Learn more on different queues & stacks.
Time estimates for redemptions (projected and fulfilled)
For Contract Addresses: view this . Make sure you use the Contract Address when sending txs.
For ABIs: This guide uses Goerli testnet and interacts with two contracts: and . RedeemManger contract is only needed if listening to RedeemManager contract events.
Ethereum Network
Proxy
Implementation
LsETH
0x1d8b30cC38Dba8aBce1ac29Ea27d9cFd05379A09
0x6edbde63319df1c54ee94075191c3d2ac5a1bf81
RedeemManager
0x0693875efbF04dDAd955c04332bA3324472DF980
0x3b377e3ac2cc844d8d27b8930f6a3035d3a3fc5b
Create Redemption Request
As an Platform, you allow users to submit (or to request that you submit on their behalf) the amount of LsETH they wish to redeem.
Note: In some cases, the recipient may be different from the message sender. For example: if using an omnibus model, you may have LsETH in a wallet separate from the user’s ETH wallet. In this case, the LsETH wallet will make the requestRedeem, however, the recipient of ETH will be the ETH wallet.
After successfully calling requestRedeem function an ID will be generated. Keep this redeemRequestId as you will use it later on.
Below is a code snippet that will call the requestRedeem function.
Code uses Chainstack as its provider to interact with smart contracts and uses Ethers.js
Contract.json is the ABI associated with the LsETH contract
When creating a requestRedeem, the LsETH contract will emit a RequestedRedeem event. The event contains the redeem request IDs. Additionally, you can use the Redemption API to retrieve redeem request IDs associated with an owner, which is demonstrated later on in this guide.
Below is a code snippet for listening to the RequestedRedeem event via websocket connection.
Request:
Code uses Chainstack websocket as its provider to interact with smart contracts.
websocket.js
const ethers = require('ethers');
const Contract = require('./RedeemManager.json');
async function main() {
const provider = new ethers.providers.WebSocketProvider(
'wss://ethereum-holesky.core.chainstack.com/ws/<ID>',
17000
);
const redeemManagerContract = new ethers.Contract(
Contract.address,
Contract.abi,
provider
);
redeemManagerContract.on(
'RequestedRedeem',
(owner, height, amount, maxRedeemableEth, id) => {
console.log('RequestedRedeem event');
console.log(`Owner of redeem ${owner}`);
console.log(`Height ${height.toString()}`);
console.log(`Amount of LsETH to redeem ${amount.toString()}`);
console.log(
`Maximum amount of ETH to redeem ${maxRedeemableEth.toString()}`
);
console.log(`Request Redeem ID ${id}`);
}
);
}
main();
run in terminal
node websocket.js
Response:
As you can see in the response below, the redeem request ID is 18. This is the request ID associated with the request earlier in the “Create Redeem Request” section of this guide.
websocket.js
RequestedRedeem event
Owner of redeem <WALLET ADDRESS>
Height 4980971589444881813
Amount of LsETH to redeem 10000000
Maximum amount of ETH to redeem 10212074
Request Redeem ID 18
eth/v0/redeems
You can use the Redemption API to list the redeemRequests for a given recipient wallet.
You can get a list of all redemptions associated with a wallet using the curl request below.
You can see information related to the status of the redemption. Later on, you will see the status update as the redeemRequest becomes satisfied and can be claimed.
Timing projections
Now that you have a redeem ID, use the projections endpoint to get an estimate of how long it might take for the redeemRequest to be satisfied and become claimable.
The response below displays when the redeemRequest is likely to be redeemable. Once redeemable, you can use resolveRedeemRequests to get the matching withdrawal event ID.
A withdrawal event is triggered at the time of the protocol’s Oracle report. Oracles report every 24 hours.
Withdrawal event IDs are not generated instantly, even if there is enough supply. Withdrawal event IDs will be generated at the next Oracle report after redeem request is submitted. This is by design to ensure a fair redemption process for all Liquid Collective participants.
Below is an example event.
Request:
websocket.js
redeemManagerContract.on(
'ReportedWithdrawal',
(height, amount, ethAmount, id) => {
console.log('ReportedWithdrawal event');
console.log(`Height ${height.toString()}`);
console.log(`Amount of LsETH to redeem ${amount.toString()}`);
console.log(`ETH amount being withdrawn ${ethAmount.toString()}`);
console.log(`Withdrawal event ID ${id}`);
}
);
This event will emit the newly created withdrawal ID, as you can see below:
Response:
websocket.js
ReportedWithdrawal event
Height 4980971589444881813
Amount of LsETH to redeem 10000000
ETH amount being withdrawn 10212074
Withdrawal event ID 10
You receive a redeem ID of 10. 10 is the specific withdrawal event ID that is produced when the Liquid Collective Protocol has enough ETH to satisfy (either partial or full) the redemption. This means that the redeemRequest has been satisfied and the corresponding withdrawal event ID was returned.
This withdrawal event may fully satisfy or partially satisfy the redeem request ID. To ensure redemption requests are fully satisfied prior to claiming, use the /eth/v0/redeems/{idx} endpoint.
A response of…
-1 means that the request is not satisfied yet
-2 means that the request is out of bounds
-3 means that the request has already been claimed
An alternative method to getting the Withdrawal event ID is using the /eth/v0/redeems/{id} endpoint.
The response below indicates that this redemption can be fully satisfied, meaning there is enough supply in the withdrawal stack to fulfill this redemption. This is noted via the "status_satisfaction": "FULLY_SATISFIED" and also the claimable_amount_lseth is equal to the total_amount_lseth.
After making a claim there will be two events triggered, which will be inspected next in this guide.
ClaimedRedeemRequest event
On successfully claiming a redeemRequest, a ClaimedRedeemRequest event is emitted. This event indicates the amount of LsETH claimed, the amount of ETH sent to the recipient, and the remaining LsETH amount that has been satisfied but not yet claimed.
A remaining LsETH amount of 0 means the request has been fully claimed. If the amount is greater than 0, the request has been partially claimed and another claim will be required to fully claim the request.
Request:
websocket.js
redeemManagerContract.on(
'ClaimedRedeemRequest',
(
redeemRequestId,
recipient,
ethAmount,
lsEthAmount,
remainingLsEthAmount
) => {
console.log('ClaimedRedeemRequest event');
console.log(`Redeem Request ID ${redeemRequestId}`);
console.log(`Recipient of redeem request ${recipient}`);
console.log(`Amount of ETH ${ethAmount.toString()}`);
console.log(`Amount of LsETH ${lsEthAmount.toString()}`);
console.log(`Amount of remaining LsETH ${remainingLsEthAmount.toString()}`);
}
);
Response:
websocket.js
ClaimedRedeemRequest event
Redeem Request ID 18
Recipient of redeem request <WALLET ADDRESS>
Amount of ETH 10209790
Amount of LsETH 10000000
Amount of remaining LsETH 0
SatisfiedRedeemRequest event
On successfully claiming a redemption request one or more SatisfiedRedeemRequest events are emitted.
Those events provide details about the withdrawal events that have satisfied a redemption request.
Request:
websocket.js
redeemManagerContract.on(
'SatisfiedRedeemRequest',
(
redeemRequestId,
withdrawalEventId,
lsEthAmountSatisfied,
ethAmountSatisfied,
lsEthAmountRemaining,
ethAmountExceeding
) => {
console.log('SatisfiedRedeemRequest event');
console.log(`Redeem Request ID ${redeemRequestId}`);
console.log(`Withdrawal ID ${withdrawalEventId}`);
console.log(`Amount of LsETH satisfied ${lsEthAmountSatisfied.toString()}`);
console.log(`Amount of ETH satisfied ${ethAmountSatisfied.toString()}`);
console.log(
`Amount of LsETH left to satisfy ${lsEthAmountRemaining.toString()}`
);
console.log(
`Amount of ETH added to buffer ${ethAmountExceeding.toString()}`
);
}
);
Response:
websocket.js
SatisfiedRedeemRequest event
Redeem Request ID 18
Withdrawal ID 10
Amount of LsETH satisfied 10000000
Amount of ETH satisfied 10209790
Amount of LsETH left to satisfy 0
Amount of ETH added to buffer 163
eth/v0/redeems
An alternative approach is to use the Alluvial API to call the /redeems endpoint, as it will return the status of both satisfaction and claim status.
Below you can see that the redeem queue is greater than the withdrawal stack. This indicates that more supply is needed to fulfill the redeem requests. Additional supply can enter via more deposits and/or exiting a validator.
If the timestamp is in the past, it means that there is enough supply to fulfill demand and Alluvial returns the timestamp associated with the latest redeem request.
If the timestamp is in the future, it means there isn't enough supply to fulfill demand and time is needed to add supply into the Withdrawal stack.
This is an estimated time and actual times may vary depending on frequency of deposits and redemptions on the Liquid Collective protocol.
The owner has claimed part of redeem request before the redeem request was fully satisfied. More supply will need to be added, hence the withdrawal_event_id is == -1, whereas before there would have been a positive integer associated with the withdrawal_event_id (ex. 0, 1, 2, etc...).
Note that claimed_amount_lseth is equal to previously claimed amount.
The owner has claimed part of redeem request before the redeem request was fully satisfied. More supply was found, however not enough to fully satisfy this redeem request.
In the example below you can see that total_amount_lseth > claimed_amount_lseth + claimable_amount_lseth. There is 40000 LsETH still left to fully satisfy this request.
RedeemManger.json is the ABI for the RedeemManager contract. ABIs can be found in .
The full lifecycle method visual can be seen .
For all status combinations check out this
In order to see if a redemption request can be satisfied, use the function call.
For other potential states, check out the at the bottom of the doc.
With both a request redeem ID and withdrawal event ID you can submit a claim for the redemption calling the function.
The Alluvial API exposes information about both the.
The redemption process is very dynamic. You can see a matrix of all the redeem statuses . Below will show the difference between response payloads when requesting from either:
This happens when the redemption request can be partially satisfied and the owner has not made a against the redeem request.