# Who is Alluvial?

Alluvial is a software development company supporting the development of the Liquid Collective protocol. We're focused on the overall growth and maturity of the ecosystem by fostering participation in proof of stake blockchains.

To learn more about the Liquid Collective protocol and how it works, check out Liquid Collective's [documentation](https://docs.liquidcollective.io/v1/).

## New to Alluvial's documentation?

The Alluvial docs have three key sections:

1. [Guides](https://docs.alluvial.finance/guides): This section contains several step by step guides for how technical teams can implement key Liquid Staking flows.
2. [APIs](https://docs.alluvial.finance/api-reference): This section shows all the raw API requests that Liquid Collective Platforms can call.
3. [Third party integration guides](https://docs.alluvial.finance/third-party-guides): This section includes guides for how to interact with the Liquid Collective protocol via other software platforms.

### Helpful tips

If you are an engineer or technical person, looking to understand the level of effort or work involved in implementing the key flows (ETH staking and LsETH redemptions) then we recommend reviewing the [staking](https://docs.alluvial.finance/guides/staking) and [redemption](https://docs.alluvial.finance/guides/redemptions) guides. The [architecture guide](https://docs.alluvial.finance/guides/architecture) is a helpful compliment to these key guides.


# Guides

Using Alluvial APIs makes it easy to onboard and interact with the Liquid Collective protocol.

There are two types of Liquid Collective Platforms. Platforms either:

* **Enable mint/redeem**: These Platforms enable direct Liquid Collective protocol interactions, including depositing ETH to Ethereum's deposit contract and redeeming LsETH for ETH (minting and redeeming). These Platforms offer KYC/AML and Sanctions Screening procedures for their users, then submit the user's wallet address to Liquid Collective's Allowlist smart contract upon successful completion.
* **Enable secondary interactions**: These Platforms do not enable minting, redeeming, or direct Liquid Collective protocol interactions, but do enable secondary protocol interactions such as trading, lending, or other services not required to Allowlist KYC/AML and Sanctions Screened wallet addresses. Users of these Platforms can seamlessly interact with LsETH, and may accrue Ethereum's consensus and execution layer network rewards simply by holding LsETH.

There are two lists that Platforms can interact with:

* Allowlist: Any address added to this list can mint and/or burn directly with the Liquid Collective protocol. Only wallets that have on-ramped via a mint/redeemed enabled Platform can be added to this list.
* On-Platform: Platforms that **only** enable secondary interactions can only add wallets to the On-Platform list, and cannot interact with the Allowlist smart contract. When a wallet address is added to the Allowlist it is also automatically added to the On-Platform list. This list is used for calculating Platform rebates.

Below are two guides for Platforms to add their users to the appropriate lists.

* [Platforms enabling Mint and Redeem guide](https://docs.alluvial.finance/guides/platform-mint_burn)
* [Platforms enabling secondary interactions](https://docs.alluvial.finance/guides/platform-listing)


# Mint and Redeem support

## Pre-read

Review the [Authentication Guide](https://docs.alluvial.finance/guides/supplemental_guides/authentication) for the Alluvial API.

## Onboarding wallets

### Create an Account object

First create an account object for each of your users.

**Request:**

{% tabs %}
{% tab title="cURL" %}

```shell
curl -X 'POST' \
  'https://api.staging.alluvial.finance/v0/platform/accounts' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer eyJhbGci…CVm5g' \
  -H 'Content-Type: application/json' \
  -d '{
  "key": "FO123"
}'
```

{% endtab %}
{% endtabs %}

**Response:**

```json
{
  "id": "da36a6fa-070d-4cd1-b99a-f2da4f4ccb20",
  "key": "FO123",
  "org_id": "org_WaYHN06ay6WoTjcz",
  "status": "ACTIVE",
  "created_at": "2023-03-17T17:24:18.031434748Z",
  "wallets": []
}
```

### Create Wallet objects

#### Add wallet to Allowlist

Attach a wallet object to each account. You can add the wallet to the Allowlist (on-chain) or the On-Platform list (off-chain).

The first example below shows how to add a wallet address to the Allowlist (which be default is added to the On-Platform list).

{% hint style="info" %}
Use the Alluvial account in UUID in the uri Path
{% endhint %}

**Request:**

{% tabs %}
{% tab title="cURL" %}

```shell
curl -X 'POST' \
  'https://api.staging.alluvial.finance/v0/platform/accounts/da36a6fa-070d-4cd1-b99a-f2da4f4ccb20/wallets' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer eyJhbGci…CVm5g' \
  -H 'Content-Type: application/json' \
  -d '{
  "address": "0x5210d328bC5651F92F4557EfDE08dd97A36A935c",
  "type": "ETH"
}'
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
The status of the Wallet object upon creation will be `NOT_READY`. This is because the wallet information will be sent to the Allowlist smart contract to update the Registry. Once the Registry contract is updated, the status will change to `ALLOWLISTED`.

Once the wallet address is added to the Allowlist it will also automatically be added to the On-Platform list.
{% endhint %}

**Response:**

```json
{
  "id": "a41c520e-fadd-4c0c-a1ce-d574ee731cee",
  "type": "ETH",
  "address": "0x5210d328bC5651F92F4557EfDE08dd97A36A935c",
  "account_id": "993327a3-1d48-4eff-a9ee-7ec769ec1f64",
  "status": "ALLOWLISTED",
  "allowlisted": true,
  "on_platform": true,
  "created_at": "2024-03-25T18:38:31.427654433Z"
}
```

#### Add wallet to On-Platform list

Platforms that enable mint/redeem may also need to add a wallet address to the On-Platform list separately. The following request is for a Platform that enables mint/redeem to add a wallet to the On-Platform list.

**Request:**

{% tabs %}
{% tab title="cURL" %}

```shell
curl -X 'POST' \
  'https://api.staging.alluvial.finance/v0/platform/accounts/da36a6fa-070d-4cd1-b99a-f2da4f4ccb20/wallets' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer eyJhbGci…CVm5g' \
  -H 'Content-Type: application/json' \
  -d '{
  "address": "0x5210d328bC5651F92F4557EfDE08dd97A36A935c",
  "type": "ETH",
  "allowlisted": "false"
}'
```

{% endtab %}
{% endtabs %}

**Request:**

```json
{
  "address": "0x5210d328bC5651F92F4557EfDE08dd97A36A935c",
  "created_at": "string",
  "id": "string",
  "status": "NOT_ALLOWLISTED",
  "type": "ETH",
  "on_platform": "true"
}
```

Now that you have an Account object created and Wallet objects associated, the user can stake ETH and mint LsETH. Before depositing, ensure that the Wallet object(s) have a status = `ALLOWLISTED`.

## Stake ETH

To complete the staking process, review [this guide](https://docs.alluvial.finance/guides/supplemental_guides/staking).


# Secondary Interaction support

## Pre-read

Review the [Authentication Guide](https://docs.alluvial.finance/guides/supplemental_guides/authentication) for the Alluvial API.

### Create an Account object

Platforms that enable secondary protocol interactions, such as trading, lending, or other services, can create an Account object. The Account object is generally associated with a Platform's customer.

**Request:**

{% tabs %}
{% tab title="cURL" %}

```shell
curl -X 'POST' \
  'https://api.staging.alluvial.finance/v0/platform/accounts' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer 'eyJhbGci…CVm5g'
  -d '{
 "key": "foobar-123"
}'
```

{% endtab %}
{% endtabs %}

**Response:**

```json
{
  "id": "4b1d05ed-b498-4847-99a0-c415c6b19f6b",
  "key": "foobar-123",
  "org_id": "org_WaYHN06ay6WoT...",
  "status": "ACTIVE",
  "created_at": "2024-02-09T22:40:28.954304245Z",
  "wallets": []
}
```

### Create a Wallet object

After an Account object is created, associate a Wallet object to an account. You can associate one or many Wallet objects to a given Account object.

**Request:**

{% tabs %}
{% tab title="cURL" %}

```shell
curl -X 'POST' \
  'https://api.staging.alluvial.finance/v0/platform/accounts/4b1d05ed-b498-4847-99a0-c415c6b19f6b/wallets/' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer 'eyJhbGci…CVm5g'
  -D '{
 "address": "0x469b998812B9675b7B2Fb37519f574fEA5ee92E8",
 "allowlisted": "false",
 "type": "ETH"
}'
```

{% endtab %}
{% endtabs %}

**Response:**

```json
{
  "id": "421a39a1-e9c3-4a33-81bc-98eff7bfd502",
  "type": "ETH",
  "address": "0x469b998812b9675b7b2fb37519f574fea5ee92e8",
  "status": "NOT_ALLOWLISTED",
  "on_platform": true,
  "created_at": "2024-02-09T22:43:02.249045841Z"
}
```

Your Platform is all set! Wallets added to the On-Platform list will be used to calculate the relevant Protocol Service Fee Rebates.


# Reporting

Alluvial's reporting API enables Platforms to request the ETH network rewards that their users have received. Below are two guides:

1. Platforms with omnibus account structure.
2. Platforms with segregated account structure.

{% hint style="warning" %}
**Commercials V3 cutover — May 1, 2026.** Fees and rates are calculated under Commercials V3 from 2026-05-01 00:00 UTC. The examples below use the v1 endpoints. The v1 endpoints do not accept a `unit` parameter, and V3 replaces the v0 fee fields: `dao_fees_*` becomes the LC fee fields (`period_lc_fees_lseth` / `period_lc_fees_rate`), `provider_fees_*` becomes the tech provider fee fields (`period_tech_provider_fees_lseth` / `period_tech_provider_fees_rate`), and `slashing_fees_*` is removed.
{% endhint %}

## Omnibus account structure

Platforms that support an omnibus structure should use the `/eth/v1/rewards` endpoint. This will return data in a lots structure. Lots represent LsETH balance changes for a given period. Rewards can then be calculated for each period. Total rewards for a user is the sum of rewards for each lot.

Below is an example request where a Platform wants to see the rewards for a wallet. Assume that the user has a total balance of 3 but acquired at different dates.

* The user had 1 LsETH from 2024-04-01 to 2024-04-10. As such this will be the first lot added to the request body.
* The user then acquired 2 more LsETH, for a total of 3, as of 2024-04-11.

In our request below assume that the current date for which we want data is til 2024-04-15.

### Request

```bash
curl --request POST 'https://api.staging.alluvial.finance/eth/v1/rewards' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer e...g' \
--data '[
	{
		"balance": "1",
		"from": "2024-04-01",
		"to": "2024-04-10"
	},
	{
		"balance": "3",
		"from": "2024-04-11",
		"to": "2024-04-15"
	}
]'
```

### Response

```json
{
  "balance": "4",
  "accrued_rewards": "0.0041516089956458",
  "lots": [
    {
      "from": "2024-04-01",
      "to": "2024-04-10",
      "start_conversion_rate": "1.0014257204933798",
      "end_conversion_rate": "1.0023467329617235",
      "balance": "1",
      "accrued_rewards": "0.0009210124683437"
    },
    {
      "from": "2024-04-11",
      "to": "2024-04-15",
      "start_conversion_rate": "1.0026157619809285",
      "end_conversion_rate": "1.0036926274900292",
      "balance": "3",
      "accrued_rewards": "0.0032305965273021"
    }
  ]
}
```

## Segregated account structure

Platforms that support a segregated structure can use two different endpoints to get reward data at the account or wallet level.

### List rewards by account(s)

Use the endpoint `/eth/v1/rewards/accounts` to display ETH network rewards at the account level.

{% hint style="info" %}
For a range of days, use the endpoint `/eth/v1/rewards/accounts/:idOrKey/summary`
{% endhint %}

#### Request

```bash
curl --request POST 'https://api.staging.alluvial.finance/eth/v1/rewards/accounts' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Impersonate-Org: org_uLD0aCAXyWYg4WQH' \
--header 'Authorization: Bearer ey...Qg' \
--data '{
	"accounts": [
		"8f9d6eff-d630-4389-810f-89c4b07b8fc5"
	],
	"date": "2024-05-15"
}'
```

#### Response

Below is the response show the aggregated rewards for the account in addition for each address.

```json
[
  {
    "account_id": "8f9d6eff-d630-4389-810f-89c4b07b8fc5",
    "totals": {
      "balance_lseth": "0.0000198315414745",
      "rewards_eth": "0.000000000092766089"
    },
    "date": "2024-05-15",
    "wallets": [
      {
        "oracle_report": "1437a7330f02400a5662ed70f5c23b251d76fdb1ede48240d9f1e7bcd70540470000014b",
        "address": "0x7fc2B172EcC8E4609088f341Aa0Fb3841De8A77A",
        "account_id": "8f9d6eff-d630-4389-810f-89c4b07b8fc5",
        "org_id": "org_WaYHN06ay6WoTjcz",
        "date": "2024-05-15",
        "balance_lseth": "0.00000991577073725",
        "rewards_eth": "0.000000000092766089",
        "total_rewards_eth": "0.000000000092766089",
        "conversion_rate": "1.0085131998588114",
        "previous_conversion_rate": "1.0085038444500037",
        "mints_lseth": "0",
        "total_mints_lseth": "0.00000991577073725",
        "burns_lseth": "0",
        "total_burns_lseth": "0",
        "fees_eth": "0.000000000010307343",
        "total_fees_eth": "0.000000000010307343",
        "period_lc_fees_lseth": "0.000000000000979198",
        "total_lc_fees_lseth": "0.000000000000979198",
        "period_tech_provider_fees_lseth": "0.000000000000360757",
        "total_tech_provider_fees_lseth": "0.000000000000360757",
        "platform_fees_eth": "0.000000000007112067",
        "total_platform_fees_eth": "0.000000000007112067",
        "operator_fees_eth": "0.000000000001546101",
        "total_operator_fees_eth": "0.000000000001546101"
      }
    ]
  }
]
```

### List rewards by wallet(s)

Use the endpoint `/eth/v1/rewards/wallets` to display ETH network rewards at the wallet level.

{% hint style="info" %}
For a range of days, use the endpoint `/eth/v1/rewards/wallets/:idOrKey/summary`
{% endhint %}

#### Request

```bash
curl --request POST 'https://api.staging.alluvial.finance/eth/v1/rewards/wallets' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Impersonate-Org: org_uLD0aCAXyWYg4WQH' \
--header 'Authorization: Bearer ey...Qg' \
--data '{
	"addresses": [
		"0x7fc2B172EcC8E4609088f341Aa0Fb3841De8A77A"
	],
	"date": "2024-05-15"
}'
```

#### Response

Below is the response showing the aggregated rewards for the account in addition for each address.

```json
[
  {
    "oracle_report": "1437a7330f02400a5662ed70f5c23b251d76fdb1ede48240d9f1e7bcd70540470000014b",
    "address": "0x7fc2B172EcC8E4609088f341Aa0Fb3841De8A77A",
    "account_id": "8f9d6eff-d630-4389-810f-89c4b07b8fc5",
    "org_id": "org_WaYHN06ay6WoTjcz",
    "date": "2024-05-15",
    "balance_lseth": "0.00000991577073725",
    "rewards_eth": "0.000000000092766089",
    "total_rewards_eth": "0.000000000092766089",
    "conversion_rate": "1.0085131998588114",
    "previous_conversion_rate": "1.0085038444500037",
    "mints_lseth": "0",
    "total_mints_lseth": "0.00000991577073725",
    "burns_lseth": "0",
    "total_burns_lseth": "0",
    "fees_eth": "0.000000000010307343",
    "total_fees_eth": "0.000000000010307343",
    "period_lc_fees_lseth": "0.000000000000979198",
    "total_lc_fees_lseth": "0.000000000000979198",
    "period_tech_provider_fees_lseth": "0.000000000000360757",
    "total_tech_provider_fees_lseth": "0.000000000000360757",
    "platform_fees_eth": "0.000000000007112067",
    "total_platform_fees_eth": "0.000000000007112067",
    "operator_fees_eth": "0.000000000001546101",
    "total_operator_fees_eth": "0.000000000001546101"
  }
]
```

## Staking Reward Rate

The Alluvial API provides a staking rewards rate (SRR), which is calculated as a 7 day trailing average.

To get SRR information, call the [/protocol](https://docs.alluvial.finance/api-reference/reporting#eth-v0-protocol-date) endpoint.

### Request

```bash
curl 'https://api.alluvial.finance/eth/v0/protocol/2024-05-01' \
--header 'Accept: application/json' \
--header 'Accept: application/json'
```

### Response

Based on providing a specific date of 2024-05-01, the returned SRR is 2.85%.

```json
[
  {
    "oracle_report": "2e39c7e7e0b78c043ac98e190175fa7f057a75197647d4d844864ac06c98aa3400000225",
    "date": "2024-05-01T12:14:47Z",
    "total_protocol_mints_lseth": "89180.704128270784037675",
    "total_protocol_burns_lseth": "6676.332903830493099868",
    "total_protocol_active_keys_count": 2703,
    "rewards_eth": "6.66289490787142415",
    "gross_fee_rate": "0.1",
    "gross_fee_lseth": "0.636483355669561459",
    "conversion_rate": "1.0468294022963504",
    "total_lseth_supply": "82674.954629966742503924",
    "a_srr_7d": "2.85"
  }
]
```

## Discounting API

Platforms that want to provide a custom discount to their users can use the Discounting APIs to offload the calculations of a discounted net Protocol Service Fee to Alluvial's enterprise accounting service. Platforms can then use the API to call ETH network rewards, Protocol Service Fees, and end user discounts for one or more wallets or user accounts.

First, you will set a new target fee rate. This represents the new gross Protocol Service Fee rate that will be calculated for an account and/or wallet.

For example, the current gross [Protocol Service Fee](https://docs.liquidcollective.io/eth/tokenomics/rewards-fees-socialization#protocol-service-fee) rate is 10%. To offer a user an effective net rate of 8%, you'd set the target fee rate at 0.08, which effectively provides a 2% discount to the associated wallet(s) and/or account(s).

### Request

```sh
curl --location --request POST 'https://api.staging.alluvial.finance/v0/platform/accounts/8f9d6eff-d630-4389-810f-89c4b07b8fc5/discount_rate' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer eyJQ' \
--data '{
  "target_fee_rate": 0.08
}'
```

The target fee rate is now set at 8%.

### Response

```json
{
  "account_id": "8f9d6eff-d630-4389-810f-89c4b07b8fc5",
  "target_fee_rate": "0.08",
  "created_at": "2024-06-24T21:27:07.387160077Z"
}
```

On each Oracle report, you can use the Reporting APIs to see discounted values for the associated account, including the discounted net Protocol Service Fee, ETH network rewards received adjusted for that Fee, and effective end user discounts.

### Request

```sh
curl --location --request POST 'https://api.staging.alluvial.finance/eth/v0/rewards/accounts' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer eyJ...mQ' \
--data '{
  "accounts": [
    "8f9d6eff-d630-4389-810f-89c4b07b8fc5"
  ],
  "date": "2024-06-22",
  "unit": "lseth"
}'
```

In the response below, you can see that the `rebate_eth` or `rebate_lseth` will reflect the amount the wallet is attributed to be reimbursed in order to achieve the the 8% target fee rate (or conversely, a 2% discount on the gross Protocol Service Fee).

### Response

```json
[
  {
    "account_id": "8f9d6eff-d630-4389-810f-89c4b07b8fc5",
    "totals": {
      "balance_lseth": "0.947997263286356641",
      "rewards_eth": "0.001671038079957103"
    },
    "date": "2024-06-22",
    "wallets": [
      {
        "oracle_report": "43306f4695a1d7ed4118c68fecd150e1f071b338815007960e61900d8d9c123e00000046",
        "address": "0x4D1c3ec0B381F012A90f096cD4E8bc7746e4f2eB",
        "account_id": "8f9d6eff-d630-4389-810f-89c4b07b8fc5",
        "org_id": "org_WaYHN06ay6WoTjcz",
        "date": "2024-06-22",
        "balance_lseth": "0.00001",
        "rewards_eth": "0.000000000087955639",
        "total_rewards_eth": "0.000000020987954327",
        "conversion_rate": "1.0106749980064223",
        "previous_conversion_rate": "1.0106662024425232",
        "mints_lseth": "0",
        "total_mints_lseth": "0",
        "burns_lseth": "0",
        "total_burns_lseth": "0",
        "fees_eth": "0.000000000009772849",
        "total_fees_eth": "0.000000002331994927",
        "dao_fees_eth": "0.000000000000928421",
        "total_dao_fees_eth": "0.000000000221539524",
        "provider_fees_eth": "0.00000000000034205",
        "total_provider_fees_eth": "0.000000000081619819",
        "slashing_fees_eth": "0.000000000000293185",
        "total_slashing_fees_eth": "0.00000000006995985",
        "platform_fees_eth": "0.000000000006743266",
        "total_platform_fees_eth": "0.000000001609076496",
        "platform_fees_at_discount_rate_eth": "0",
        "total_platform_fees_at_discount_rate_eth": "0",
        "operator_fees_eth": "0.000000000001465927",
        "total_operator_fees_eth": "0.00000000034979924",
        "gross_protocol_fee_rate": "0",
        "discount_rate": "0",
        "gross_fees_eth": "0.000000000009772849",
        "total_gross_fees_eth": "0.000000002331994927",
        "gross_rewards_eth": "0.000000000097728488",
        "total_gross_rewards_eth": "0.000000000189971905",
        "rebate_eth": "0",
        "total_rebate_eth": "0",
        "rebate_lseth": "0",
        "total_rebate_lseth": "0"
      },
      {
        "oracle_report": "43306f4695a1d7ed4118c68fecd150e1f071b338815007960e61900d8d9c123e00000046",
        "address": "0x7fc2B172EcC8E4609088f341Aa0Fb3841De8A77A",
        "account_id": "8f9d6eff-d630-4389-810f-89c4b07b8fc5",
        "org_id": "org_WaYHN06ay6WoTjcz",
        "date": "2024-06-22",
        "balance_lseth": "0.947987263286356641",
        "rewards_eth": "0.000008338082549768",
        "total_rewards_eth": "0.001671017092002776",
        "conversion_rate": "1.0106749980064223",
        "previous_conversion_rate": "1.0106662024425232",
        "mints_lseth": "0",
        "total_mints_lseth": "5.947997863289656641",
        "burns_lseth": "0",
        "total_burns_lseth": "0.0000006000033",
        "fees_eth": "0.000000926453616641",
        "total_fees_eth": "0.000185668565778085",
        "dao_fees_eth": "0.000000088013093581",
        "total_dao_fees_eth": "0.000017638513748923",
        "provider_fees_eth": "0.000000032425876582",
        "total_provider_fees_eth": "0.000006498399802232",
        "slashing_fees_eth": "0.000000027793608499",
        "total_slashing_fees_eth": "0.000005570056973343",
        "platform_fees_eth": "0.000000639252995482",
        "total_platform_fees_eth": "0.000128111310386881",
        "platform_fees_at_discount_rate_eth": "0",
        "total_platform_fees_at_discount_rate_eth": "0",
        "operator_fees_eth": "0.000000138968042496",
        "total_operator_fees_eth": "0.000027850284866709",
        "gross_protocol_fee_rate": "0",
        "discount_rate": "0",
        "gross_fees_eth": "0.000000926453616641",
        "total_gross_fees_eth": "0.000185668565778085",
        "gross_rewards_eth": "0.000009264536166409",
        "total_gross_rewards_eth": "0.000018009094566287",
        "rebate_eth": "0",
        "total_rebate_eth": "0",
        "rebate_lseth": "0",
        "total_rebate_lseth": "0"
      }
    ]
  }
]
```

The discount data is exposed via the following API endpoints:

* `/rewards/wallets`
* `/eth/v0/rewards/accounts/:idOrKey/summary`
* `/eth/v0/rewards/wallets/:idOrKey/summary`


# Supplemental Guides


# Architecture

This guide is intended for Platforms to learn about how they can integrate the Liquid Collective protocol with the support of Alluvial's APIs.

To help illustrate how a Platform utilizes the Alluvial APIs to support its Liquid Collective protocol integration, this guide introduces a few fictional characters:

* Acme Custodian Corp: Financial institution that is a Platform for the Liquid Collective protocol and will offer its customers the ability to participate in the Liquid Collective protocol by staking ETH.
* Alice: user of Acme Custodian Corp
* Bob: user of Acme Custodian Corp

### Custodial vs. Non-Custodial wallets

A custodial wallet's private key(s) are held by either a 3rd party or by the financial institution that is servicing the wallet account on behalf of the customer. In this example, Alice and Bob have a custodial account with Acme Corp and Acme Corp owns / has access to the private key associated with Alice's and Bob's wallet addresses.

Below is an example of the flow of funds, showing how the staking action by Alice and Bob will flow through Acme's architecture and the Alluvial APIs.

{% hint style="info" %}
Architectural components in the Acme Infrastructure box are for illustrative purposes.
{% endhint %}

![Custodial flow of funds](/files/2Rqkpvv1Qa3x70qmMhwe)

Non-custodial (or self-managed) wallets are those in which the customer holds the private keys. In this example, Alice is the only one with access to the private key for her wallet.

The largest differences between the two flow of funds diagrams are the wallet ownership and actions (i.e. signing transactions).

{% hint style="info" %}
Architectural components in the Acme Infrastructure box are for illustrative purposes.
{% endhint %}

![Non custodial flow of funds](/files/nJ3wMWHW23HDF2CcP3cw)

### Segregated vs. Omnibus Accounts

#### Segregated account(s)

A segregated account structure is one where each customer at the financial institution (ex. Acme Corp) has their own unique, separate account. In this example, both Alice and Bob have their own unique Ethereum address associated with their individual accounts at Acme Corp.

![Segregated accounts](/files/oYZajCI6qkeLOhFurbqf)

Based on the example segregated account structure, you can see how Acme Corp will map its internal accounts to the technical resources (Organization, Depositor, Wallet) created when interacting with the Alluvial API.

![Segregated accounts mapping](/files/ZhSJVamkYWJ7PruKa68m)

#### Omnibus account(s)

An omnibus account is an account structure in which all users' accounts are grouped into a single wallet address.

![Omnibus accounts](/files/PdCIxvNWIRfgkKMu0F3O)

Below is an image of how to map Acme's architecture to resources (Organization, Depositor, and Wallet) created via Alluvial's API.

Alluvial isn't aware of, and at no time has control over, the underlying account structure Acme implements. Acme will create a Depositor object that represents all of the internal customers.

{% hint style="warning" %}
Review the Depositor states [(deny, pause, reactivate, and remove)](https://docs.alluvial.finance/api-reference/onboarding) as applying these states will impact all underlying customers.
{% endhint %}

![Omnibus accounts mapping](/files/wEpJPJL2QoLLPrTRsg6k)


# Authentication

This guide walks users through how to use authenticate requests using the Alluvial APIs.

## Client Credentials Flow

Below is a ladder diagram showing the flow to create an access token.

This flow involves 3 parties:

* Platform Server: the client looking to access the Alluvial API and which has previously been given a Client ID and Client Secret credentials.
* Alluvial Authorization Server: responsible for validating credentials and generating JWT Access Token.
* Alluvial API: the target resource to be accessed.

<figure><img src="/files/BNJ5Iyw98ER4XJTvenYc" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
To obtain a Client ID and Client Secret reach out to your Alluvial representative.
{% endhint %}

## Getting Access Token

To obtain an access token, use the a request below using your client id and secret.

**Request:**

{% hint style="info" %}
Make sure you are using the correct audience URL. Staging: <https://api.staging.alluvial.finance> Production: <https://api.alluvial.finance>
{% endhint %}

{% code title="" overflow="wrap" lineNumbers="true" %}

```bash
curl  'https://auth.alluvial.finance/oauth/token' \
--header 'content-type: application/json' \
--data '{
 "audience": "https://api.staging.alluvial.finance",
 "grant_type": "client_credentials",
 "client_id": "<YOUR_CLIENT_ID>",
 "client_secret": "<YOUR_CLIENT_SECRET>"
}'
```

{% endcode %}

**Response:**

{% code title="" overflow="wrap" lineNumbers="true" %}

```json
{
  "access_token": "eyJhbGci…CVm5g",
  "scope": "read:eth-oracle read:eth-contracts read:eth-operators",
  "expires_in": 86400,
  "token_type": "Bearer"
}
```

{% endcode %}

### Refreshing Access Token

Access Token should be reused for every request until it expires, in which case they should go through the Client Credential Flow again to obtain a fresh Access Token.

#### Sample request

To use the access token, pass it via the HTTP header `Authorization: Bearer`

{% code title="" overflow="wrap" lineNumbers="true" %}

```bash
curl 'https://api.staging.alluvial.finance/v0/wallets/0x2B7ff5d4C14A9Da8d5C9354c7A52aB40DdC1C01e' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer eyJh...b'
```

{% endcode %}

If you receive a 2xx response, you are now able to make fully authenticated requests.

If you receive a 4xx response, check if your access token is expired.

You are now ready to make requests! Please check out our guides on [staking](https://docs.alluvial.finance/guides/staking) and [redemptions](https://docs.alluvial.finance/guides/redemptions).


# Redemptions

## Goals

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 [Liquid Collective’s LsETH redemption documentation](https://docs.liquidcollective.io/eth/tokenomics/redemptions#redemption-architecture)

### Pre-read

Review the [Authentication Guide](https://docs.alluvial.finance/guides/authentication) for the Alluvial API.

## Implementation

{% hint style="info" %}
The guide will using the Hoodi network for smart contract calls.
{% endhint %}

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 [here](https://docs.liquidcollective.io/eth/tokenomics/redemptions#queues-stacks-and-buffers) on different queues & stacks.
* Time estimates for redemptions (projected and fulfilled)

{% hint style="warning" %}
For Contract Addresses: view this [page](https://docs.liquidcollective.io/eth/deployments). Make sure you use the Contract Address when sending txs.

For ABIs: This guide uses Hoodi testnet and interacts with two contracts: [LsETH](https://hoodi.etherscan.io/address/0x0CA0c58b1986a55876552E0D9532C963625D5646#code) and [RedeemManager](https://hoodi.etherscan.io/address/0x5d51E82b75A4F16ef677d5bE20d707b6441A00b7#code). RedeemManger contract is only needed if listening to RedeemManager contract events.
{% endhint %}

### Create Redemption Request

The LsETH contract exposes a function called [requestRedeem](https://hoodi.etherscan.io/address/0x0CA0c58b1986a55876552E0D9532C963625D5646#writeProxyContract#F13).

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.

{% hint style="info" %}
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.
{% endhint %}

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.

{% hint style="info" %}
Code uses public RPC as its provider to interact with smart contracts and uses Ethers.js

Contract.json is the ABI associated with the LsETH contract
{% endhint %}

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```javascript
const { ethers } = require('ethers');
const Contract = require('./Contract.json');

(async () => {
  const nodeUrl = 'https://ethereum-hoodi-rpc.publicnode.com';
  const provider = new ethers.providers.JsonRpcProvider(nodeUrl);

  const signer = new ethers.Wallet('<INSERT WALLET SECRET>', provider);

  const LsETHContract = new ethers.Contract(
    Contract.address,
    Contract.abi,
    signer
  );

  const walletAddress = '<INSERT WALLET ADDRESS>';

  const value = ethers.utils.parseEther('0.000001');

  //Get estimate for gas limit
  const redeem_estimation = await LsETHContract.estimateGas.requestRedeem(
    value,
    walletAddress,
    { gasLimit: 1 }
  );

  // Create Redeem Request
  let tx = await LsETHContract.requestRedeem(value, walletAddress, {
    gasLimit: redeem_estimation,
  });
  let receipt = await tx.wait();
  console.log(tx);
})();
```

{% endcode %}

### RequestedRedeem event

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:**

{% hint style="info" %}
Code uses public RPC as its provider to interact with smart contracts and uses Ethers.js

Contract.json is the ABI associated with the LsETH contract
{% endhint %}

{% code title="websocket.js" overflow="wrap" lineNumbers="true" %}

```javascript
const ethers = require('ethers');
const Contract = require('./RedeemManager.json');

async function main() {
  const provider = new ethers.providers.WebSocketProvider(
    'wss://ethereum-hoodi-rpc.publicnode.com',
    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();
```

{% endcode %}

run in terminal

```shell
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.

{% hint style="info" %}
The full lifecycle method visual can be seen [here](https://docs.liquidcollective.io/eth/tokenomics/redemptions#three-stage-redemption-process).
{% endhint %}

{% code title="websocket.js" overflow="wrap" lineNumbers="true" %}

```javascript
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
```

{% endcode %}

### 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.

#### Request

{% code title="index.sh" overflow="wrap" lineNumbers="true" %}

```bash
curl -X 'GET' \
  'https://api.staging.alluvial.finance/eth/v0/redeems?owner=<WALLET ADDRESS>' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer e...jk'
```

{% endcode %}

**Response:**

{% code title="" overflow="wrap" lineNumbers="true" %}

```json
[
  {
    "id": 18,
    "withdrawal_event_id": -1,
    "total_amount_lseth": "10000000",
    "claimed_amount_lseth": "0",
    "claimable_amount_lseth": "0",
    "max_redeemable_amount_eth": "10209790",
    "owner": "<WALLET ADDRESS>",
    "height": "4980971589354881813",
    "status_claim": "NOT_CLAIMED",
    "status_satisfaction": "PENDING_SATISFACTION",
    "requested_at": 9017542
  }
]
```

{% endcode %}

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.

{% hint style="info" %}
For all status combinations check out this [Redeem Status Matrix](https://docs.liquidcollective.io/eth/tokenomics/redemptions#redeem-status-matrix)
{% endhint %}

### 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.

**Request:**

{% code title="index.sh" overflow="wrap" lineNumbers="true" %}

```bash
curl 'https://api.staging.alluvial.finance/eth/v0/redeems/18/projection' \
  -H 'Authorization: Bearer e...jk'
```

{% endcode %}

**Response:**

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.

{% code title="" overflow="wrap" lineNumbers="true" %}

```json
{
  "projected_redeemable_at": "2023-05-19T19:16:24Z"
}
```

{% endcode %}

### Reported withdrawal event

A withdrawal event is triggered at the time of the protocol’s Oracle report. Oracles report every 24 hours.

{% hint style="info" %}
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.
{% endhint %}

Below is an example event.

**Request:**

{% code title="websocket.js" overflow="wrap" lineNumbers="true" %}

```javascript
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}`);
  }
);
```

{% endcode %}

This event will emit the newly created withdrawal ID, as you can see below:

**Response:**

{% code title="websocket.js" overflow="wrap" lineNumbers="true" %}

```javascript
ReportedWithdrawal event
Height 4980971589444881813
Amount of LsETH to redeem 10000000
ETH amount being withdrawn 10212074
Withdrawal event ID 10
```

{% endcode %}

### Resolve redemption request

In order to see if a redemption request can be satisfied, use the [resolveRedeemRequests](https://hoodi.etherscan.io/address/0x0CA0c58b1986a55876552E0D9532C963625D5646#readProxyContract#F40) function call.

**Request:**

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```javascript
const { ethers } = require('ethers');
const Contract = require('./Contract.json');

(async () => {
  const LsETHContract = new ethers.Contract(
    Contract.address,
    Contract.abi,
    provider
  );

  let arrRequestId = [18];

  let resolveRedeem = await LsETHContract.resolveRedeemRequests(arrRequestId);
  console.log(resolveRedeem.toString());
})();
```

{% endcode %}

**Response:**

```json
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.

{% hint style="info" %}
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.
{% endhint %}

{% hint style="warning" %}
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
  {% endhint %}

An alternative method to getting the Withdrawal event ID is using the `/eth/v0/redeems/{id}` endpoint.

**Request:**

{% code title="index.sh" overflow="wrap" lineNumbers="true" %}

```bash
curl 'https://api.staging.alluvial.finance/eth/v0/redeems/18' \
  -H 'Authorization: Bearer e...jk'
```

{% endcode %}

**Response:**

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`.

For other potential states, check out the [appendix](https://docs.alluvial.finance/guides/redemptions#appendix) at the bottom of the doc.

{% code title="" overflow="wrap" lineNumbers="true" %}

```json
[
  {
    "id": 18,
    "withdrawal_event_id": 10,
    "total_amount_lseth": "10000000",
    "claimed_amount_lseth": "0",
    "claimable_amount_lseth": "10000000",
    "max_redeemable_amount_eth": "10209790",
    "owner": "<WALLET ADDRESS>",
    "height": "4980971589354881813",
    "status_claim": "NOT_CLAIMED",
    "status_satisfaction": "FULLY_SATISFIED",
    "requested_at": 9017542
  }
]
```

{% endcode %}

### Claim redemption requests

With both a request redeem ID and withdrawal event ID you can submit a claim for the redemption calling the [claimRedeemRequests](https://hoodi.etherscan.io/address/0x0CA0c58b1986a55876552E0D9532C963625D5646#writeProxyContract#F3) function.

{% hint style="warning" %}
Before claiming, ensure you have correct Withdrawal event ID. Withdrawal events IDs will change based on redemptions happening on the protocol.
{% endhint %}

**Request:**

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```javascript
const arrRequestId = [18];
const arrWithdrawalId = [10];

//Get estimate for gas limit
const claim_estimation = await LsETHContract.estimateGas.claimRedeemRequests(
  arrRequestId,
  arrWithdrawalId,
  { gasLimit: 1 }
);

const claimRedeemRequests = await LsETHContract.claimRedeemRequests(
  arrRequestId,
  arrWithdrawalId,
  { gasLimit: claim_estimation }
);
const receipt = await claimRedeemRequests.wait();
console.log(claimRedeemRequests);
```

{% endcode %}

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:**

{% code title="websocket.js" overflow="wrap" lineNumbers="true" %}

```javascript
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()}`);
  }
);
```

{% endcode %}

**Response:**

{% code title="websocket.js" overflow="wrap" lineNumbers="true" %}

```javascript
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
```

{% endcode %}

### 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:**

{% code title="websocket.js" overflow="wrap" lineNumbers="true" %}

```javascript
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()}`
    );
  }
);
```

{% endcode %}

**Response:**

{% code title="websocket.js" overflow="wrap" lineNumbers="true" %}

```javascript
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
```

{% endcode %}

### 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.

**Request:**

{% code title="index.sh" overflow="wrap" lineNumbers="true" %}

```bash
curl -X 'GET' \
  'https://api.staging.alluvial.finance/eth/v0/redeems?owner=<WALLET ADDRESS>' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer e...jk'
```

{% endcode %}

{% hint style="warning" %}
Non-finalized blocks are returned in the Alluvial API.
{% endhint %}

**Response:**

Below the endpoint returns that this request redeem has been fully satisfied and claimed.

```json
{
  "id": 18,
  "withdrawal_event_id": -3,
  "total_amount_lseth": "10000000",
  "claimed_amount_lseth": "10000000",
  "claimable_amount_lseth": "0",
  "max_redeemable_amount_eth": "10209790",
  "owner": "<WALLET ADDRESS>",
  "height": "4980971589354881813",
  "status_claim": "FULLY_CLAIMED",
  "status_satisfaction": "FULLY_SATISFIED",
  "requested_at": 9017542
}
```

You are now able to fully implement the redemption process for the Liquid Collective protocol!

## Redemption Information

The Alluvial API exposes information about both the[Withdrawal stack and Redeem queue](https://docs.liquidcollective.io/eth/tokenomics/redemptions#visualization-of-withdrawal-stack-redemption-queue-and-deposit-and-redemption-buffers).

### Redemption height

Platforms who want visibility into the current supply (amount of withdrawals) and demand (amount of redeems) can use the `/redeems_info` endpoint.

**Request:**

{% code title="index.sh" overflow="wrap" lineNumbers="true" %}

```bash
curl -X 'GET' \
  'https://api.staging.alluvial.finance/eth/v0/redeems_info' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer e...jk'
```

{% endcode %}

**Response:**

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.

{% hint style="info" %}
amounts returned are denominated in LsETH
{% endhint %}

{% code title="index.sh" overflow="wrap" lineNumbers="true" %}

```json
{
  "total_amount_withdrawal_stack_lseth": "204462016832064499274",
  "total_amount_redeem_queue_lseth": "237456822758541259805"
}
```

{% endcode %}

In addition to exposing the Withdrawal stack and Redeem queue, Alluvial exposes an estimated time when redeems will be fulfilled.

**Request:**

{% code title="index.sh" overflow="wrap" lineNumbers="true" %}

```bash
curl -X 'GET' \
  'https://api.staging.alluvial.finance/eth/v0/redeems_info/projection'\
  -H 'accept: application/json' \
  -H 'Authorization: Bearer e...jk'
```

{% endcode %}

**Response:**

The current projections timestamp logic is:

* 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.

{% hint style="info" %}
This is an estimated time and actual times may vary depending on frequency of deposits and redemptions on the Liquid Collective protocol.
{% endhint %}

{% code title="index.sh" overflow="wrap" lineNumbers="true" %}

```json
{
  "projected_fulfilled_at": "2023-06-15T11:02:24Z"
}
```

{% endcode %}

## Appendix

### Full Code

Full code for making deposit transaction

{% hint style="danger" %}
Code is meant for testing purposes and should not be used directly for production workloads.
{% endhint %}

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```javascript
const { ethers } = require('ethers');
const Contract = require('../Contract.json');
const walletAddress = '0xbe79ff177a8F6a0D9656cF47D8687f43666a4d1e';

(async () => {
  const nodeUrl = 'https://ethereum-hoodi-rpc.publicnode.com';
  const provider = new ethers.providers.JsonRpcProvider(nodeUrl);

  const gasPrice = await provider.getGasPrice();

  const signer = new ethers.Wallet('<INSERT WALLET PRIVATE KEY>', provider);

  const riverContract = new ethers.Contract(
    Contract.address,
    Contract.abi,
    signer
  );

  const value = ethers.utils.parseEther('0.000000000001');

  //Get estimate for gas limit
  const redeem_estimation = await riverContract.estimateGas.requestRedeem(
    value,
    walletAddress,
    { gasLimit: 1 }
  );

  // Create Redeem Request
  let tx = await riverContract.requestRedeem(value, walletAddress, {
    gasLimit: redeem_estimation,
  });

  // ***** Start resolve redeem function block *****
  // Uncomment when running claimRedeemRequests()
  //let arrRequestId = [18];

  //let resolveRedeem = await LsETHContract.resolveRedeemRequests(arrRequestId);
  // console.log(resolveRedeem.toString())
  // ***** Stop resolve redeem function block *****

  // ***** Start claiming function block *****
  // Get estimate for gas limit
  // const claim_estimation = await LsETHContract.estimateGas.claimRedeemRequests(arrRequestId, arrWithdrawalId, { gasLimit: 1});

  //let arrRequestId = [18];
  //const arrWithdrawalId = [10]

  //const claimRedeemRequests = await LsETHContract.claimRedeemRequests(arrRequestId, arrWithdrawalId, { gasLimit: claim_estimation});
  //const receipt = await claimRedeemRequests.wait();
  //console.log(claimRedeemRequests);
  // ***** end claiming function block *****
})();
```

{% endcode %}

### /redeems response payloads

The redemption process is very dynamic. You can see a matrix of all the redeem statuses [here](https://docs.liquidcollective.io/eth/tokenomics/redemptions#redeem-status-matrix). Below will show the difference between response payloads when requesting from either:

* eth/v0/redeems/18
* eth/v0/redeems?owner=

#### NOT\_CLAIMED & PENDING\_SATISFACTION

**Why would this state happen?**

* This is the initial state for all redeem requests.

{% code title="index.sh" overflow="wrap" lineNumbers="true" %}

```json
{
  "id": 1,
  "withdrawal_event_id": -1,
  "total_amount_lseth": "1000000",
  "claimed_amount_lseth": "0",
  "claimable_amount_lseth": "0",
  "max_redeemable_amount_eth": "1023507",
  "owner": "<WALLET ADDRESS>",
  "height": "201956822758540259700",
  "status_claim": "NOT_CLAIMED",
  "status_satisfaction": "PENDING_SATISFACTION",
  "requested_at": 9134158
}
```

{% endcode %}

#### NOT\_CLAIMED & PARTIALLY\_SATISFIED

**Why would this state happen?**

* This happens when the redemption request can be partially satisfied and the owner has not made a [claimRedeemRequest](https://hoodi.etherscan.io/address/0x0CA0c58b1986a55876552E0D9532C963625D5646#writeProxyContract#F3) against the redeem request.
* Notice that the `claimable_amount_lseth` is greater than 0 but less than the `total_amount_lseth`.

{% code title="index.sh" overflow="wrap" lineNumbers="true" %}

```json
{
  "id": 1,
  "withdrawal_event_id": 1,
  "total_amount_lseth": "1000000",
  "claimed_amount_lseth": "0",
  "claimable_amount_lseth": "900000",
  "max_redeemable_amount_eth": "1023507",
  "owner": "<WALLET ADDRESS>",
  "height": "201956822758540259700",
  "status_claim": "NOT_CLAIMED",
  "status_satisfaction": "PARTIALLY_SATISFIED",
  "requested_at": 9134158
}
```

{% endcode %}

#### NOT\_CLAIMED & FULLY\_SATISFIED

**Why would this state happen?**

* The amount of supply can fully satisfy the amount of LsETH being redeemed in this request.
* Notice that `total_amount_lseth` == `claimable_amount_lseth`

{% code title="index.sh" overflow="wrap" lineNumbers="true" %}

```json
{
  "id": 1,
  "withdrawal_event_id": 1,
  "total_amount_lseth": "1000000",
  "claimed_amount_lseth": "0",
  "claimable_amount_lseth": "1000000",
  "max_redeemable_amount_eth": "1023507",
  "owner": "<WALLET ADDRESS>",
  "height": "201956822758540259700",
  "status_claim": "NOT_CLAIMED",
  "status_satisfaction": "FULLY_SATISFIED",
  "requested_at": 9134158
}
```

{% endcode %}

#### PARTIALLY\_CLAIMED & PENDING\_SATISFACTION

**Why would this state happen?**

* 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.

{% code title="index.sh" overflow="wrap" lineNumbers="true" %}

```json
{
  "id": 1,
  "withdrawal_event_id": -1,
  "total_amount_lseth": "1000000",
  "claimed_amount_lseth": "900000",
  "claimable_amount_lseth": "0",
  "max_redeemable_amount_eth": "1023507",
  "owner": "<WALLET ADDRESS>",
  "height": "201956822758540259700",
  "status_claim": "PARTIALLY_CLAIMED",
  "status_satisfaction": "PENDING_SATISFACTION",
  "requested_at": 9134158
}
```

{% endcode %}

#### PARTIALLY\_CLAIMED & PARTIALLY\_SATISFIED

**Why would this state happen?**

* 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.

{% code title="index.sh" overflow="wrap" lineNumbers="true" %}

```json
{
  "id": 1,
  "withdrawal_event_id": 2,
  "total_amount_lseth": "1000000",
  "claimed_amount_lseth": "900000",
  "claimable_amount_lseth": "60000",
  "max_redeemable_amount_eth": "1023507",
  "owner": "<WALLET ADDRESS>",
  "height": "201956822758540259700",
  "status_claim": "PARTIALLY_CLAIMED",
  "status_satisfaction": "PARTIALLY_SATISFIED",
  "requested_at": 9134158
}
```

{% endcode %}

#### PARTIALLY\_CLAIMED & FULLY\_SATISFIED

**Why would this state happen?**

* The owner has claimed part of redeem request before the redeem request was fully satisfied.
* In the example below you can see that `total_amount_lseth` = `claimed_amount_lseth` + `claimable_amount_lseth`.

{% code title="index.sh" overflow="wrap" lineNumbers="true" %}

```json
{
  "id": 1,
  "withdrawal_event_id": 2,
  "total_amount_lseth": "1000000",
  "claimed_amount_lseth": "900000",
  "claimable_amount_lseth": "100000",
  "max_redeemable_amount_eth": "1023507",
  "owner": "<WALLET ADDRESS>",
  "height": "201956822758540259700",
  "status_claim": "PARTIALLY_CLAIMED",
  "status_satisfaction": "FULLY_SATISFIED",
  "requested_at": 9134158
}
```

{% endcode %}

#### FULLY\_CLAIMED & FULLY\_SATISFIED

**Why would this state happen?**

* This is final state for all redeem requests. This indicates that the redeem request has been fully satisfied.
* `total_amount_lseth` == `claimed_amount_lseth` and `claimable_amount_lseth` == 0
* `withdrawal_event_id` reflects this state with the `-3` ID.

{% code title="index.sh" overflow="wrap" lineNumbers="true" %}

```json
{
  "id": 1,
  "withdrawal_event_id": -3,
  "total_amount_lseth": "1000000",
  "claimed_amount_lseth": "1000000",
  "claimable_amount_lseth": "0",
  "max_redeemable_amount_eth": "1023507",
  "owner": "<WALLET ADDRESS>",
  "height": "201956822758540259700",
  "status_claim": "FULLY_CLAIMED",
  "status_satisfaction": "FULLY_SATISFIED",
  "requested_at": 9134158
}
```

{% endcode %}


# Staking

## Goals

This guide shows you how to Stake ETH using the Liquid Collective Protocol and receive LsETH in return.

By the end of this guide you will:

* Have an understanding of the Liquid Collective protocol
* Stake ETH

### Pre-read

Review the [Authentication Guide](https://docs.alluvial.finance/guides/authentication) for the Alluvial API.

### Dependencies

* [Ethers.js](https://docs.ethers.org/v5/): You will use Ether.js, a popular library for interacting with Smart Contracts to query blockchain information and send transactions.
* Application Binary Interface (ABI): The ABI for the River smart contract will be used to READ /WRITE to the contract.
* Hoodi ETH: You will be accessing the Hoodi testnet, ensure you have a wallet with HoodiETH.

After reading both guides, come back to continue the implementation of the staking workflow.

## Implementation

### Import libraries

In the backend (Node.js) application you will need to import one library.

```javascript
const { ethers } = require('ethers');
```

app.js

```javascript
const Contract = require('./Contract.json');
```

{% hint style="info" %}
Full code implementation can be see in Appendix, at bottom of the guide.
{% endhint %}

Next, add information about the RPC node Provider being used. This example uses this example you are using [Chainstack](https://chainstack.com/), but there are several options for platforms to host an RPC node.

### Staking transaction

```javascript
(async () => {
  const nodeUrl = 'https://ethereum-hoodi-rpc.publicnode.com';
  const provider = new ethers.providers.JsonRpcProvider(nodeUrl);

  const gasPrice = await provider.getGasPrice();

  const signer = new ethers.Wallet('<INSERT WALLET KEY>', provider);

  const LsETHContract = new ethers.Contract(
    Contract.address,
    Contract.abi,
    signer
  );

  const walletAddress = '<INSERT WALLET ADDRESS>';
})();
```

You can now create the transaction object. In the example below, you are transferring `0.0000001` from address 0xbe79ff177a8F6a0D9656cF47D8687f43666a4d1e to the River address.

{% hint style="warning" %}
Gas Limit uses a built in Ethers.js function. You may need to manually adjust when network has high demand. The code uses the estimateGas function.
{% endhint %}

```javascript
// Get estimate for gas limit
const deposit_estimation = await LsETHContract.estimateGas.deposit({
  from: walletAddress,
  value: ethers.utils.parseUnits('0.00000001', 'ether'),
  gasLimit: ethers.utils.hexlify(1),
  nonce: provider.getTransactionCount(walletAddress, 'latest'),
});

// Deposit tx
let tx = await LsETHContract.deposit({
  from: walletAddress,
  value: ethers.utils.parseUnits('0.00000001', 'ether'),
  gasPrice: gasPrice,
  gasLimit: deposit_estimation,
  nonce: provider.getTransactionCount(walletAddress, 'latest'),
});
let receipt = await tx.wait();
console.log(receipt);
```

Great job! You have successfully staked ETH. You can use the transaction hash returned to view more details.

Congratulations! You've now staked ETH, received LsETH, and retrieved your balance. As a next step, review the guide on how to implement [redemptions](https://docs.alluvial.finance/guides/supplemental_guides/redemptions).

## Appendix

### Full Code

Full code for making deposit transaction

{% hint style="danger" %}
Code is meant for testing purposes and should not be used directly for production workloads.
{% endhint %}

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```javascript
const { ethers } = require('ethers');
const Contract = require('./Contract.json');
const walletAddress = '0xbe79ff177a8F6a0D9656cF47D8687f43666a4d1e'(async () => {
  const nodeUrl = 'https://ethereum-hoodi-rpc.publicnode.com';
  const provider = new ethers.providers.JsonRpcProvider(nodeUrl);

  const gasPrice = await provider.getGasPrice();

  const signer = new ethers.Wallet('<INSERT WALLET KEY>', provider);

  const LsETHContract = new ethers.Contract(
    Contract.address,
    Contract.abi,
    signer
  );

  const walletAddress = '<INSERT WALLET ADDRESS>';

  const value = ethers.utils.parseEther('0.000000000001');

  const deposit_estimation = await LsETHContract.estimateGas.deposit({
    from: walletAddress,
    value: ethers.utils.parseUnits('0.00000001', 'ether'),
    gasLimit: ethers.utils.hexlify(1),
    nonce: provider.getTransactionCount(walletAddress, 'latest'),
  });

  let tx = await LsETHContract.deposit({
    from: walletAddress,
    value: ethers.utils.parseUnits('0.00000001', 'ether'),
    gasPrice: gasPrice,
    gasLimit: deposit_estimation,
    nonce: provider.getTransactionCount(walletAddress, 'latest'),
  });
  let receipt = await tx.wait();
  console.log(receipt);
})();
```

{% endcode %}

{% code title="Contract.json" overflow="wrap" lineNumbers="true" %}

```json
{
  "address": "0x1d8b30cC38Dba8aBce1ac29Ea27d9cFd05379A09",
  "abi": [
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_from",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "_operator",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "_allowance",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "_value",
          "type": "uint256"
        }
      ],
      "name": "AllowanceTooLow",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "BalanceTooLow",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        }
      ],
      "name": "Denied",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "EmptyDeposit",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "ErrorOnDeposit",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "InconsistentPublicKeys",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "InconsistentSignatures",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "InvalidArgument",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "InvalidCall",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "currentValidatorsExitedBalance",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "newValidatorsExitedBalance",
          "type": "uint256"
        }
      ],
      "name": "InvalidDecreasingValidatorsExitedBalance",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "currentValidatorsSkimmedBalance",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "newValidatorsSkimmedBalance",
          "type": "uint256"
        }
      ],
      "name": "InvalidDecreasingValidatorsSkimmedBalance",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "InvalidEmptyString",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "epoch",
          "type": "uint256"
        }
      ],
      "name": "InvalidEpoch",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "InvalidFee",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "version",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "expectedVersion",
          "type": "uint256"
        }
      ],
      "name": "InvalidInitialization",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "InvalidPublicKeyCount",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "requested",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "received",
          "type": "uint256"
        }
      ],
      "name": "InvalidPulledClFundsAmount",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "InvalidSignatureCount",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "providedValidatorCount",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "depositedValidatorCount",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "lastReportedValidatorCount",
          "type": "uint256"
        }
      ],
      "name": "InvalidValidatorCountReport",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "InvalidWithdrawalCredentials",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "InvalidZeroAddress",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "NoAvailableValidatorKeys",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "NotEnoughFunds",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "NullTransfer",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "SliceOutOfBounds",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "SliceOverflow",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "prevTotalEthIncludingExited",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "postTotalEthIncludingExited",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "timeElapsed",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "relativeLowerBound",
          "type": "uint256"
        }
      ],
      "name": "TotalValidatorBalanceDecreaseOutOfBound",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "prevTotalEthIncludingExited",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "postTotalEthIncludingExited",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "timeElapsed",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "annualAprUpperBound",
          "type": "uint256"
        }
      ],
      "name": "TotalValidatorBalanceIncreaseOutOfBound",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "caller",
          "type": "address"
        }
      ],
      "name": "Unauthorized",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_from",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "_to",
          "type": "address"
        }
      ],
      "name": "UnauthorizedTransfer",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "ZeroMintedShares",
      "type": "error"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "owner",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "spender",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "Approval",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "validatorCount",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "validatorTotalBalance",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "bytes32",
          "name": "roundId",
          "type": "bytes32"
        }
      ],
      "name": "ConsensusLayerDataUpdate",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "version",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "bytes",
          "name": "cdata",
          "type": "bytes"
        }
      ],
      "name": "Initialize",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "components": [
            {
              "internalType": "uint256",
              "name": "epoch",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "validatorsBalance",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "validatorsSkimmedBalance",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "validatorsExitedBalance",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "validatorsExitingBalance",
              "type": "uint256"
            },
            {
              "internalType": "uint32",
              "name": "validatorsCount",
              "type": "uint32"
            },
            {
              "internalType": "uint32[]",
              "name": "stoppedValidatorCountPerOperator",
              "type": "uint32[]"
            },
            {
              "internalType": "bool",
              "name": "rebalanceDepositToRedeemMode",
              "type": "bool"
            },
            {
              "internalType": "bool",
              "name": "slashingContainmentMode",
              "type": "bool"
            }
          ],
          "indexed": false,
          "internalType": "struct IOracleManagerV1.ConsensusLayerReport",
          "name": "report",
          "type": "tuple"
        },
        {
          "components": [
            {
              "internalType": "uint256",
              "name": "rewards",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "pulledELFees",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "pulledRedeemManagerExceedingEthBuffer",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "pulledCoverageFunds",
              "type": "uint256"
            }
          ],
          "indexed": false,
          "internalType": "struct IOracleManagerV1.ConsensusLayerDataReportingTrace",
          "name": "trace",
          "type": "tuple"
        }
      ],
      "name": "ProcessedConsensusLayerReport",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "pulledSkimmedEthAmount",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "pullExitedEthAmount",
          "type": "uint256"
        }
      ],
      "name": "PulledCLFunds",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "amount",
          "type": "uint256"
        }
      ],
      "name": "PulledCoverageFunds",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "amount",
          "type": "uint256"
        }
      ],
      "name": "PulledELFees",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "amount",
          "type": "uint256"
        }
      ],
      "name": "PulledRedeemManagerExceedingEth",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "redeemManagerDemand",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "suppliedRedeemManagerDemand",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "suppliedRedeemManagerDemandInEth",
          "type": "uint256"
        }
      ],
      "name": "ReportedRedeemManager",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "_collector",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "_oldTotalUnderlyingBalance",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "_oldTotalSupply",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "_newTotalUnderlyingBalance",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "_newTotalSupply",
          "type": "uint256"
        }
      ],
      "name": "RewardsEarned",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "admin",
          "type": "address"
        }
      ],
      "name": "SetAdmin",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "allowlist",
          "type": "address"
        }
      ],
      "name": "SetAllowlist",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "oldAmount",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "newAmount",
          "type": "uint256"
        }
      ],
      "name": "SetBalanceCommittedToDeposit",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "oldAmount",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "newAmount",
          "type": "uint256"
        }
      ],
      "name": "SetBalanceToDeposit",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "oldAmount",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "newAmount",
          "type": "uint256"
        }
      ],
      "name": "SetBalanceToRedeem",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "annualAprUpperBound",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "relativeLowerBound",
          "type": "uint256"
        }
      ],
      "name": "SetBounds",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "collector",
          "type": "address"
        }
      ],
      "name": "SetCollector",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "coverageFund",
          "type": "address"
        }
      ],
      "name": "SetCoverageFund",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "depositContract",
          "type": "address"
        }
      ],
      "name": "SetDepositContractAddress",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "oldDepositedValidatorCount",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "newDepositedValidatorCount",
          "type": "uint256"
        }
      ],
      "name": "SetDepositedValidatorCount",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "elFeeRecipient",
          "type": "address"
        }
      ],
      "name": "SetELFeeRecipient",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "fee",
          "type": "uint256"
        }
      ],
      "name": "SetGlobalFee",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "minNetAmount",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "maxRelativeAmount",
          "type": "uint256"
        }
      ],
      "name": "SetMaxDailyCommittableAmounts",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "string",
          "name": "metadataURI",
          "type": "string"
        }
      ],
      "name": "SetMetadataURI",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "operatorRegistry",
          "type": "address"
        }
      ],
      "name": "SetOperatorsRegistry",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "oracleAddress",
          "type": "address"
        }
      ],
      "name": "SetOracle",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "pendingAdmin",
          "type": "address"
        }
      ],
      "name": "SetPendingAdmin",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "address",
          "name": "redeemManager",
          "type": "address"
        }
      ],
      "name": "SetRedeemManager",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint64",
          "name": "epochsPerFrame",
          "type": "uint64"
        },
        {
          "indexed": false,
          "internalType": "uint64",
          "name": "slotsPerEpoch",
          "type": "uint64"
        },
        {
          "indexed": false,
          "internalType": "uint64",
          "name": "secondsPerSlot",
          "type": "uint64"
        },
        {
          "indexed": false,
          "internalType": "uint64",
          "name": "genesisTime",
          "type": "uint64"
        },
        {
          "indexed": false,
          "internalType": "uint64",
          "name": "epochsToAssumedFinality",
          "type": "uint64"
        }
      ],
      "name": "SetSpec",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "totalSupply",
          "type": "uint256"
        }
      ],
      "name": "SetTotalSupply",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "bytes32",
          "name": "withdrawalCredentials",
          "type": "bytes32"
        }
      ],
      "name": "SetWithdrawalCredentials",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "from",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "to",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "Transfer",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "depositor",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "recipient",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "amount",
          "type": "uint256"
        }
      ],
      "name": "UserDeposit",
      "type": "event"
    },
    {
      "stateMutability": "payable",
      "type": "fallback"
    },
    {
      "inputs": [],
      "name": "DEPOSIT_SIZE",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "PUBLIC_KEY_LENGTH",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "SIGNATURE_LENGTH",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "_DEPOSIT_SIZE",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "acceptAdmin",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_owner",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "_spender",
          "type": "address"
        }
      ],
      "name": "allowance",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_spender",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "_value",
          "type": "uint256"
        }
      ],
      "name": "approve",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_owner",
          "type": "address"
        }
      ],
      "name": "balanceOf",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_owner",
          "type": "address"
        }
      ],
      "name": "balanceOfUnderlying",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint32[]",
          "name": "_redeemRequestIds",
          "type": "uint32[]"
        },
        {
          "internalType": "uint32[]",
          "name": "_withdrawalEventIds",
          "type": "uint32[]"
        }
      ],
      "name": "claimRedeemRequests",
      "outputs": [
        {
          "internalType": "uint8[]",
          "name": "claimStatuses",
          "type": "uint8[]"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "decimals",
      "outputs": [
        {
          "internalType": "uint8",
          "name": "",
          "type": "uint8"
        }
      ],
      "stateMutability": "pure",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_spender",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "_subtractableValue",
          "type": "uint256"
        }
      ],
      "name": "decreaseAllowance",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "deposit",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_recipient",
          "type": "address"
        }
      ],
      "name": "depositAndTransfer",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "_maxCount",
          "type": "uint256"
        }
      ],
      "name": "depositToConsensusLayer",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getAdmin",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getAllowlist",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getBalanceToDeposit",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getBalanceToRedeem",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getCLSpec",
      "outputs": [
        {
          "components": [
            {
              "internalType": "uint64",
              "name": "epochsPerFrame",
              "type": "uint64"
            },
            {
              "internalType": "uint64",
              "name": "slotsPerEpoch",
              "type": "uint64"
            },
            {
              "internalType": "uint64",
              "name": "secondsPerSlot",
              "type": "uint64"
            },
            {
              "internalType": "uint64",
              "name": "genesisTime",
              "type": "uint64"
            },
            {
              "internalType": "uint64",
              "name": "epochsToAssumedFinality",
              "type": "uint64"
            }
          ],
          "internalType": "struct CLSpec.CLSpecStruct",
          "name": "",
          "type": "tuple"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getCLValidatorCount",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getCLValidatorTotalBalance",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getCollector",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getCommittedBalance",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getCoverageFund",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getCurrentEpochId",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getCurrentFrame",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "_startEpochId",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "_startTime",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "_endTime",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getDailyCommittableLimits",
      "outputs": [
        {
          "components": [
            {
              "internalType": "uint128",
              "name": "minDailyNetCommittableAmount",
              "type": "uint128"
            },
            {
              "internalType": "uint128",
              "name": "maxDailyRelativeCommittableAmount",
              "type": "uint128"
            }
          ],
          "internalType": "struct DailyCommittableLimits.DailyCommittableLimitsStruct",
          "name": "",
          "type": "tuple"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getDepositedValidatorCount",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getELFeeRecipient",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getExpectedEpochId",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "_epochId",
          "type": "uint256"
        }
      ],
      "name": "getFrameFirstEpochId",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getGlobalFee",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getLastCompletedEpochId",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getLastConsensusLayerReport",
      "outputs": [
        {
          "components": [
            {
              "internalType": "uint256",
              "name": "epoch",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "validatorsBalance",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "validatorsSkimmedBalance",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "validatorsExitedBalance",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "validatorsExitingBalance",
              "type": "uint256"
            },
            {
              "internalType": "uint32",
              "name": "validatorsCount",
              "type": "uint32"
            },
            {
              "internalType": "bool",
              "name": "rebalanceDepositToRedeemMode",
              "type": "bool"
            },
            {
              "internalType": "bool",
              "name": "slashingContainmentMode",
              "type": "bool"
            }
          ],
          "internalType": "struct IOracleManagerV1.StoredConsensusLayerReport",
          "name": "",
          "type": "tuple"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getMetadataURI",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getOperatorsRegistry",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getOracle",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getPendingAdmin",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getRedeemManager",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getReportBounds",
      "outputs": [
        {
          "components": [
            {
              "internalType": "uint256",
              "name": "annualAprUpperBound",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "relativeLowerBound",
              "type": "uint256"
            }
          ],
          "internalType": "struct ReportBounds.ReportBoundsStruct",
          "name": "",
          "type": "tuple"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getTime",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getWithdrawalCredentials",
      "outputs": [
        {
          "internalType": "bytes32",
          "name": "",
          "type": "bytes32"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_spender",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "_additionalValue",
          "type": "uint256"
        }
      ],
      "name": "increaseAllowance",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_depositContractAddress",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "_elFeeRecipientAddress",
          "type": "address"
        },
        {
          "internalType": "bytes32",
          "name": "_withdrawalCredentials",
          "type": "bytes32"
        },
        {
          "internalType": "address",
          "name": "_oracleAddress",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "_systemAdministratorAddress",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "_allowlistAddress",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "_operatorRegistryAddress",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "_collectorAddress",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "_globalFee",
          "type": "uint256"
        }
      ],
      "name": "initRiverV1",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_redeemManager",
          "type": "address"
        },
        {
          "internalType": "uint64",
          "name": "_epochsPerFrame",
          "type": "uint64"
        },
        {
          "internalType": "uint64",
          "name": "_slotsPerEpoch",
          "type": "uint64"
        },
        {
          "internalType": "uint64",
          "name": "_secondsPerSlot",
          "type": "uint64"
        },
        {
          "internalType": "uint64",
          "name": "_genesisTime",
          "type": "uint64"
        },
        {
          "internalType": "uint64",
          "name": "_epochsToAssumedFinality",
          "type": "uint64"
        },
        {
          "internalType": "uint256",
          "name": "_annualAprUpperBound",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "_relativeLowerBound",
          "type": "uint256"
        },
        {
          "internalType": "uint128",
          "name": "_minDailyNetCommittableAmount_",
          "type": "uint128"
        },
        {
          "internalType": "uint128",
          "name": "_maxDailyRelativeCommittableAmount_",
          "type": "uint128"
        }
      ],
      "name": "initRiverV1_1",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "initRiverV1_2",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "_epoch",
          "type": "uint256"
        }
      ],
      "name": "isValidEpoch",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "name",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "stateMutability": "pure",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_newAdmin",
          "type": "address"
        }
      ],
      "name": "proposeAdmin",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "_lsETHAmount",
          "type": "uint256"
        },
        {
          "internalType": "address",
          "name": "_recipient",
          "type": "address"
        }
      ],
      "name": "requestRedeem",
      "outputs": [
        {
          "internalType": "uint32",
          "name": "_redeemRequestId",
          "type": "uint32"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint32[]",
          "name": "_redeemRequestIds",
          "type": "uint32[]"
        }
      ],
      "name": "resolveRedeemRequests",
      "outputs": [
        {
          "internalType": "int64[]",
          "name": "withdrawalEventIds",
          "type": "int64[]"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "sendCLFunds",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "sendCoverageFunds",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "sendELFees",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "sendRedeemManagerExceedingFunds",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_newAllowlist",
          "type": "address"
        }
      ],
      "name": "setAllowlist",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "components": [
            {
              "internalType": "uint64",
              "name": "epochsPerFrame",
              "type": "uint64"
            },
            {
              "internalType": "uint64",
              "name": "slotsPerEpoch",
              "type": "uint64"
            },
            {
              "internalType": "uint64",
              "name": "secondsPerSlot",
              "type": "uint64"
            },
            {
              "internalType": "uint64",
              "name": "genesisTime",
              "type": "uint64"
            },
            {
              "internalType": "uint64",
              "name": "epochsToAssumedFinality",
              "type": "uint64"
            }
          ],
          "internalType": "struct CLSpec.CLSpecStruct",
          "name": "_newValue",
          "type": "tuple"
        }
      ],
      "name": "setCLSpec",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_newCollector",
          "type": "address"
        }
      ],
      "name": "setCollector",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "components": [
            {
              "internalType": "uint256",
              "name": "epoch",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "validatorsBalance",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "validatorsSkimmedBalance",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "validatorsExitedBalance",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "validatorsExitingBalance",
              "type": "uint256"
            },
            {
              "internalType": "uint32",
              "name": "validatorsCount",
              "type": "uint32"
            },
            {
              "internalType": "uint32[]",
              "name": "stoppedValidatorCountPerOperator",
              "type": "uint32[]"
            },
            {
              "internalType": "bool",
              "name": "rebalanceDepositToRedeemMode",
              "type": "bool"
            },
            {
              "internalType": "bool",
              "name": "slashingContainmentMode",
              "type": "bool"
            }
          ],
          "internalType": "struct IOracleManagerV1.ConsensusLayerReport",
          "name": "_report",
          "type": "tuple"
        }
      ],
      "name": "setConsensusLayerData",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_newCoverageFund",
          "type": "address"
        }
      ],
      "name": "setCoverageFund",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "components": [
            {
              "internalType": "uint128",
              "name": "minDailyNetCommittableAmount",
              "type": "uint128"
            },
            {
              "internalType": "uint128",
              "name": "maxDailyRelativeCommittableAmount",
              "type": "uint128"
            }
          ],
          "internalType": "struct DailyCommittableLimits.DailyCommittableLimitsStruct",
          "name": "_dcl",
          "type": "tuple"
        }
      ],
      "name": "setDailyCommittableLimits",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_newELFeeRecipient",
          "type": "address"
        }
      ],
      "name": "setELFeeRecipient",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "_newFee",
          "type": "uint256"
        }
      ],
      "name": "setGlobalFee",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "string",
          "name": "_metadataURI",
          "type": "string"
        }
      ],
      "name": "setMetadataURI",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_oracleAddress",
          "type": "address"
        }
      ],
      "name": "setOracle",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "components": [
            {
              "internalType": "uint256",
              "name": "annualAprUpperBound",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "relativeLowerBound",
              "type": "uint256"
            }
          ],
          "internalType": "struct ReportBounds.ReportBoundsStruct",
          "name": "_newValue",
          "type": "tuple"
        }
      ],
      "name": "setReportBounds",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "_underlyingAssetAmount",
          "type": "uint256"
        }
      ],
      "name": "sharesFromUnderlyingBalance",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "symbol",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "stateMutability": "pure",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "totalSupply",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "totalUnderlyingSupply",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_to",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "_value",
          "type": "uint256"
        }
      ],
      "name": "transfer",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_from",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "_to",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "_value",
          "type": "uint256"
        }
      ],
      "name": "transferFrom",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "_shares",
          "type": "uint256"
        }
      ],
      "name": "underlyingBalanceFromShares",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "stateMutability": "payable",
      "type": "receive"
    }
  ]
}
```

{% endcode %}


# APIs

Alluvial exposes several APIs to help developers interact with the Liquid Collective Protocol.

### API-Reference

{% content-ref url="/pages/1xdntdAxDaWaWLm00B2u" %}
[Authentication API](/api-reference/authentication)
{% endcontent-ref %}

{% content-ref url="/pages/SVheWHNKNMrcAAFre08M" %}
[Ethereum Data API](/api-reference/ethereum-data)
{% endcontent-ref %}

{% content-ref url="/pages/Aap6GcEZ2v6se2ncpYDz" %}
[Allowlisting API](/api-reference/allowlisting)
{% endcontent-ref %}

{% content-ref url="/pages/8bMunBsoFWYGbsO9k7rS" %}
[Redemption API](/api-reference/redemption)
{% endcontent-ref %}

{% content-ref url="/pages/ir7hgIBWWp7Yyltgm6gN" %}
[Reporting API](/api-reference/reporting)
{% endcontent-ref %}

{% content-ref url="/pages/9zYQPtzTD0EthQmgxrEJ" %}
[Broken mention](broken://pages/9zYQPtzTD0EthQmgxrEJ)
{% endcontent-ref %}


# Authentication API

This page shows the process for creating an access token.

1. When onboarding with Alluvial, you will receive API credentials. These include a client\_id and client\_secret.
2. After receiving your credentials use the curl request below, updating the client\_id and client\_secret with your specific credentials.

## Staging

```bash
curl -X 'POST' \
  'https://auth.staging.alluvial.finance/oauth/token' \
  -H 'content-type: application/json' \
  -d '{
    "audience": "https://api.staging.alluvial.finance",
  "grant_type": "client_credentials",
  "client_id": "<YOUR_CLIENT_ID>",
  "client_secret": "<YOUR_CLIENT_SECRET>"
}'
```

Response

```json
{
  "access_token": "eyJz93a...k4laUZw",
  "scope": "read:eth-oracle...read:eth-operators",
  "expires_in": 86400,
  "token_type": "Bearer"
}
```

## Production

Request

```bash
curl -X 'POST' \
  'https://auth.alluvial.finance/oauth/token' \
  -H 'content-type: application/json' \
  -d '{
    "audience": "https://api.alluvial.finance",
  "grant_type": "client_credentials",
  "client_id": "<YOUR_CLIENT_ID>",
  "client_secret": "<YOUR_CLIENT_SECRET>"
}'
```

Response

```json
{
  "access_token": "eyJz93a...k4laUWw",
  "scope": "read:eth-oracle...read:eth-operators",
  "expires_in": 86400,
  "token_type": "Bearer"
}
```


# Ethereum Data API

Ethereum Data API is a collection of APIs that expose read data from the Ethereum network.

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/status" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/status/{block\_number}" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/balances" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/balances/{address}" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/balances/{address}/history" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/contracts" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}


# Allowlisting API

## Ethereum

### Platform Account Operations

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/v0/platform/accounts" method="post" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/v0/platform/accounts" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/v0/platform/accounts/{idOrKey}" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/v0/platform/accounts/{idOrKey}/remove" method="patch" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/v0/platform/accounts/{idOrKey}/pause" method="patch" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/v0/platform/accounts/{idOrKey}/reactivate" method="patch" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

### Platform Wallet Operations

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/v0/platform/wallets/{idOrAddress}" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/v0/platform/wallets" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

### Platform Account Wallet Operations

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/v0/platform/accounts/{idOrKey}/wallets" method="post" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/v0/platform/accounts/{idOrKey}/wallets" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/v0/platform/accounts/{idOrKey}/wallets/{idOrAddress}/remove" method="patch" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/v0/platform/accounts/{idOrKey}/wallets/{idOrAddress}/pause" method="patch" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/v0/platform/accounts/{idOrKey}/wallets/{idOrAddress}/reactivate" method="patch" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

## Solana

### Check Allowlist Status

Checks whether a Solana wallet address is allowlisted for staking and redemption operations.

**Endpoint:** `GET https://api.alluvial.finance/sol/v0/allowlist/{address}`

**Path Parameters:**

| Parameter | Type   | Description                    |
| --------- | ------ | ------------------------------ |
| `address` | string | Solana wallet address to check |

**Response:**

```json
{
  "address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
  "allowlisted": true,
  "permissions": {
    "staking": true,
    "redemption": true
  }
}
```

**Response Fields:**

| Field                    | Type    | Description                                       |
| ------------------------ | ------- | ------------------------------------------------- |
| `address`                | string  | The Solana wallet address that was checked        |
| `allowlisted`            | boolean | Whether the wallet is on the allowlist            |
| `permissions.staking`    | boolean | Whether the wallet has permission to stake SOL    |
| `permissions.redemption` | boolean | Whether the wallet has permission to redeem lsSOL |

### Add Wallet to Allowlist

Adds a Solana wallet address to the allowlist, granting permissions for staking and/or redemption operations.

**Endpoint:** `POST https://api.alluvial.finance/sol/v0/allowlist`

**Request Body:**

```json
{
  "address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
  "permissions": {
    "staking": true,
    "redemption": true
  }
}
```

**Request Fields:**

| Field                    | Type    | Required | Description                                   |
| ------------------------ | ------- | -------- | --------------------------------------------- |
| `address`                | string  | Yes      | Solana wallet address to add to the allowlist |
| `permissions.staking`    | boolean | Yes      | Grant permission to stake SOL                 |
| `permissions.redemption` | boolean | Yes      | Grant permission to redeem lsSOL              |

**Response:**

```json
{
  "address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
  "allowlisted": true,
  "permissions": {
    "staking": true,
    "redemption": true
  },
  "created_at": "2025-01-15T10:30:00Z"
}
```

**Response Fields:**

| Field                    | Type    | Description                                          |
| ------------------------ | ------- | ---------------------------------------------------- |
| `address`                | string  | The Solana wallet address that was added             |
| `allowlisted`            | boolean | Confirmation that the wallet is now on the allowlist |
| `permissions.staking`    | boolean | Whether the wallet has permission to stake SOL       |
| `permissions.redemption` | boolean | Whether the wallet has permission to redeem lsSOL    |
| `created_at`             | string  | ISO 8601 timestamp when the wallet was allowlisted   |


# Redemption API

Redemption API is a collection of APIs that expose read data on redemptions.

To see APIs used in an example implementation check out the Redemption guide (below).

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/redeems" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/redeems/{idx}" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/redeems/{idx}/projection" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/redeems\_info" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/redeems\_info/projection" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/validators/queue" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}


# Reporting API

Reporting API is a collection of APIs Platforms use to get information about Liquid Collective Protocol Service Fees.

{% hint style="warning" %}
**Commercials V3 cutover — May 1, 2026.** Use the v1 endpoints for any reporting period on or after 2026-05-01 00:00 UTC. The v0 endpoints remain valid only for historical periods through 2026-04-30.
{% endhint %}

## Public endpoints (no authentication required)

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/protocol" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/protocol/{date}" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/wallet/{address}/transactions" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

## Private endpoints ( authentication required)

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/platforms/{date}" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/originators/{date}" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/operators/{date}" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/rewards" method="post" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/rewards/accounts" method="post" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/rewards/wallets" method="post" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

### Reward Summary

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/rewards/accounts/{idOrKey}/summary" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/rewards/wallets/{idOrAddress}/summary" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}


# Public APIs

Alluvial exposes several non-authenticated APIs to support DApps

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/protocol" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/protocol/{date}" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/wallet/{address}/rewards" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/wallet/{address}/status" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/wallet/{address}/transactions" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/redeems" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}

{% openapi src="<https://api.alluvial.finance/docs/doc.json>" path="/eth/v1/validators/queue" method="get" %}
<https://api.alluvial.finance/docs/doc.json>
{% endopenapi %}


# Third Party Integration Guides

Alluvial's APIs can also interact with other third party services, such as those offered by third party custodians that Platforms may use to custody funds staked using Liquid Collective.

Please note that these guides are to support Platforms who are connecting with third party products that are not offered by or in partnership or affiliation with Alluvial. Products and services offered by third parties are subject to separate terms and conditions. Visit the website of the third parties noted in these guides for more information on their offerings.


# Fireblocks

Liquid Collective Platforms can use [Fireblocks](https://www.fireblocks.com/) to custody digital assets.

Fireblocks is an easy-to-use platform to create new blockchain-based products and manage day-to-day digital asset operations. Fireblocks provides an MPC-based wallet allowing users to store their digital assets.

This guide will provide Platforms with a step-by-step process for using Fireblocks to interact with the Liquid Collective protocol.

{% hint style="info" %}
Fireblocks' offerings are third-party products that are not offered by or in partnership or affiliation with Alluvial. Products and services offered by Fireblocks and other third parties are subject to separate terms and conditions. Please visit <https://www.fireblocks.com/> for more information. Any links provided are for your convenience and informational purposes only. Inclusion of any link does not constitute an endorsement or an approval of any such third-party products by Alluvial or any other Liquid Collective protocol service provider.
{% endhint %}

## Architecture

Below is an example architecture for a Platform that uses Fireblocks as a custodian when the Platform's users stake ETH and/or redeem LsETH.

![Architecture](/files/6haGHogE9BSHfbZYBsJC)

### Implementation

This implementation will take advantage of the Fireblocks SDK, specifically the Javascript SDK. Please follow the [Fireblocks Javascript Guide](https://developers.fireblocks.com/docs/javascript-guide) to install the appropriate dependencies.

{% hint style="info" %}
This guide uses the Goerli network and the Alluvial Staging API.
{% endhint %}

#### Dependencies

Create a new Javascript file, such as `index.js`.

Define dependencies at top of file.

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```javascript
const fs = require('fs');
const path = require('path');
const { FireblocksSDK } = require('fireblocks-sdk');
const { inspect } = require('util');
```

{% endcode %}

#### Vault & wallet

Create or use an existing Fireblocks vault.

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```javascript
const createVault = async () => {
  const name = 'LsETH blog';

  const vaultAccount = await fireblocks.createVaultAccount(name);

  console.log(inspect(vaultAccount, false, null, true));
};
createVault();
```

{% endcode %}

**Response:**

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```json
{
  "id": "6",
  "name": "LsETH blog",
  "hiddenOnUI": false,
  "assets": [],
  "autoFuel": false
}
```

{% endcode %}

Next, put digital assets into the Fireblocks account.

{% hint style="info" %}
Admin permissions are needed to add ERC-20 digital assets to a Fireblocks account. Please review this information on [the Fireblocks website](https://www.fireblocks.com/blog/launch-support-for-any-erc-20-token-instantly-with-fireblocks/#:~:text=Effective%20immediately%2C%20Fireblocks%20users%20can,from%20on%20the%20Fireblocks%20platform.).
{% endhint %}

Define the contract address for Goerli:

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```javascript
const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS;
```

{% endcode %}

Call the supported digital assets in Fireblocks.

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```javascript

const getAssets = async() => {

    const supportedAssets = await fireblocks.getSupportedAssets();

    supportedAssets.forEach((asset, index, array) => {
        if (asset.contractAddress == CONTRACT_ADDRESS) {
            console.log(JSON.stringify(asset))
)
        }
    })
}
getAssets();
```

{% endcode %}

**Response:**

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```json
{
  "id": "LSETH_ETH_TEST3_4E2A",
  "name": "Liquid Staked ETH",
  "type": "ERC20",
  "contractAddress": "0x3ecCAdA3e11c1Cc3e9B5a53176A67cc3ABDD3E46",
  "nativeAsset": "ETH_TEST3",
  "decimals": 18
}
```

{% endcode %}

Add the LsETH to the newly created vault.

**Request:**

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```javascript
const addAssetToVault = async () => {
  const vaultWallet = await fireblocks.createVaultAsset(
    6,
    'LSETH_ETH_TEST3_4E2A'
  );

  console.log(inspect(vaultWallet, false, null, true));
};
addAssetToVault();
```

{% endcode %}

**Response:**

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```json
{
  "id": "6",
  "address": "<FIREBLOCKS ADDRESS>",
  "legacyAddress": "",
  "tag": ""
}
```

{% endcode %}

### Creating Depositor and Allowlisting

Now that you have an address associated with your Fireblocks account, you will create a Depositor object and add the wallet address to the Liquid Collective protocol Allowlist via the Alluvial API.

The steps for onboarding and adding to the Allowlist can be found in the [staking guide](https://docs.alluvial.finance/guides/staking#start-coding). Come back to this guide once your wallet(s) have been successfully added to the Allowlist.

### Stake ETH

Now that you have successfully added your wallets to the Allowlist you can continue with staking.

In order to interact with the Liquid Collective protocol you will need to invoke Smart Contract functions.

This guide will use the Fireblocks Ethers.js provider, created by the Fireblocks team. To install read the [Fireblocks documentation](https://developers.fireblocks.com/docs/ethereum-development).

In the `index.js` file add new dependencies

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```javascript
const {
  FireblocksWeb3Provider,
  ChainId,
} = require('@fireblocks/fireblocks-web3-provider');
const ethers = require('ethers');
```

{% endcode %}

The Liquid Collective uses a TUPProxy architecture. Below are the details about the Proxy address and implementation contract.

| Ethereum Network | Proxy                                      | Implementation                             |
| ---------------- | ------------------------------------------ | ------------------------------------------ |
| Goerli           | 0x3ecCAdA3e11c1Cc3e9B5a53176A67cc3ABDD3E46 | 0xF32fC26C9604a380c311e7eC0c5E545917e7934f |
| Mainnet          | 0x8c1BEd5b9a0928467c9B1341Da1D7BD5e10b6549 | 0x48D93d8C45Fb25125F13cdd40529BbeaA97A6565 |

Create a separate file called `Contract.json`.

In the file, add the ABI for the Liquid Collective protocol:

* Goerli can be found [here](https://goerli.etherscan.io/address/0xf32fc26c9604a380c311e7ec0c5e545917e7934f#code)
* Mainnet can be found [here](https://etherscan.io/address/0x48d93d8c45fb25125f13cdd40529bbeaa97a6565#code)

{% code title="Contract.json" overflow="wrap" lineNumbers="true" %}

```json
{
  "abi": [ { INSERT ABI FILE HERE } ],
}
```

{% endcode %}

Define the ABI address for Goerli.

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```javascript
const ABI = require('./Contract.json').abi;
```

{% endcode %}

Define the EIP-1193 Provider.

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```javascript
const eip1193Provider = new FireblocksWeb3Provider({
  privateKey: process.env.FIREBLOCKS_API_PRIVATE_KEY_PATH,
  apiKey: process.env.FIREBLOCKS_API_KEY,
  vaultAccountIds: process.env.FIREBLOCKS_VAULT_ACCOUNT_IDS,
  chainId: ChainId.GOERLI,
  // apiBaseUrl: ApiBaseUrl.Sandbox // If using a sandbox workspace
});
```

{% endcode %}

Create a function that calls the deposit function.

{% hint style="info" %}
You will need to have ETH in your Fireblocks vault to fund the deposit & gas fee.
{% endhint %}

**Request:**

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```javascript
const createDeposit = async () => {
  const provider = new ethers.providers.Web3Provider(eip1193Provider);
  const LsETHContract = new ethers.Contract(
    CONTRACT_ADDRESS,
    ABI,
    provider.getSigner()
  );

  const gasPrice = await provider.getGasPrice();
  const deposit_estimation = await LsETHContract.estimateGas.deposit({
    from: FIREBLOCKS_ADDRESS,
    value: ethers.utils.parseUnits('0.00000001', 'ether'),
    gasLimit: ethers.utils.hexlify(1),
    nonce: provider.getTransactionCount(FIREBLOCKS_ADDRESS, 'latest'),
  });

  let tx = await LsETHContract.deposit({
    from: FIREBLOCKS_ADDRESS,
    value: ethers.utils.parseUnits('0.000000001', 'ether'),
    gasPrice: gasPrice,
    gasLimit: deposit_estimation,
    nonce: provider.getTransactionCount(FIREBLOCKS_ADDRESS, 'latest'),
  });
  let receipt = await tx.wait();
  console.log(receipt);
};
createDeposit();
```

{% endcode %}

{% hint style="warning" %}
Submitting transactions will involve the Fireblocks TAP policy.
{% endhint %}

You've successfully staked ETH and should see LsETH returned in your Fireblocks vault.

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```javascript
const getTx = async () => {
  const transactions = await fireblocks.getTransactions({
    txHash: '<INSERT TX HASH>',
  });

  console.log(JSON.stringify(transactions));
};
getTx();
```

{% endcode %}

**Response:**

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```json
{
  "id": "LSETH_ETH_TEST3_4E2A",
  "total": "0.000000000974731883",
  "balance": "0.000000000974731883",
  "lockedAmount": "0",
  "available": "0.000000000974731883",
  "pending": "0",
  "frozen": "0",
  "staked": "0",
  "blockHeight": "9212224",
  "blockHash": "0xc1d94ab995a5db95ddaa10b85ef47b24a38b9ea7249ac7c64bb46a1288fa33bc"
}
```

{% endcode %}

Now you can implement the LsETH redemption flow, providing your users the ability to redeem their LsETH for ETH.

### Redeem LsETH

The next step is to allow the allowlisted wallets the ability to redeem their LsETH for ETH, thereby burning their LsETH.

There is a two-step process to receive the redeemed ETH. First, you will create a RedeemRequest that results in a redemption ID being returned. Once the redemption has been satisfied (full or partial) you can make a claimRedeemRequest call.

For more information check out [Liquid Collective's redemption documentation](https://docs.liquidcollective.io/eth/tokenomics/redemptions#redeem-lseth).

#### Create a redemption request

The first function you will call is the [requestRedeem](https://goerli.etherscan.io/address/0x3ecCAdA3e11c1Cc3e9B5a53176A67cc3ABDD3E46#writeProxyContract#F12).

**Request:**

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```javascript
const createRedeemRequest = async () => {
  const provider = new ethers.providers.Web3Provider(eip1193Provider);
  const LsETHContract = new ethers.Contract(
    CONTRACT_ADDRESS,
    ABI,
    provider.getSigner()
  );

  const value = ethers.utils.parseEther('0.000001');

  const redeem_estimation = await LsETHContract.estimateGas.requestRedeem(
    value,
    FIREBLOCKS_ADDRESS,
    { gasLimit: 1 }
  );

  const tx = await LsETHContract.requestRedeem(value, FIREBLOCKS_ADDRESS, {
    gasLimit: redeem_estimation,
  });

  let receipt = await tx.wait();
  console.log(receipt);
};
createRedeemRequest();
```

{% endcode %}

A request redeem ID will be generated.

You can retrieve the request redeem ID either via the Alluvial API or by listening the the Redeem Manager contract events. More information can be found [here](https://docs.alluvial.finance/guides/redemptions#requestedredeem-event).

#### Resolve redeem request

In order to get the Withdrawal Event ID, invoke the [resolveRedeemRequest](https://goerli.etherscan.io/address/0x1b375a15d0b5fbb215bd2678c362350aa0f40142#readContract#F39) or call via the [Alluvial API](https://docs.alluvial.finance/guides/redemptions#eth-v0-redeems).

The example below will show invoking the resolveRedeemRequest function.

**Request:**

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```javascript
const resolveRedeemRequest = async () => {
  const provider = new ethers.providers.Web3Provider(eip1193Provider);
  const LsETHContract = new ethers.Contract(
    CONTRACT_ADDRESS,
    ABI,
    provider.getSigner()
  );

  const value = ethers.utils.parseEther('0.000001');

  const arrRequestId = [53];

  const resolveRedeem = await LsETHContract.resolveRedeemRequests(arrRequestId);
  console.log(resolveRedeem.toString());
};
resolveRedeemRequest();
```

{% endcode %}

**Response:**

`111`

The resolveRedeemRequest function returns the status of the Withdrawal Event ID. More can be found [here](https://docs.alluvial.finance/guides/redemptions#resolve-redemption-request).

You can proceed to make a claim now that you have both your redemption ID (53) and Withdrawal Event ID (111).

#### Create a claim request

To make a claim request provide both the redemption request ID and the Withdrawal Event ID in an array.

{% hint style="warning" %}
Submitting transactions will involve the Fireblocks TAP policy.
{% endhint %}

**Request:**

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```javascript
const createClaimRedeemRequest = async () => {
  const provider = new ethers.providers.Web3Provider(eip1193Provider);
  const LsETHContract = new ethers.Contract(
    CONTRACT_ADDRESS,
    ABI,
    provider.getSigner()
  );

  const arrRequestId = [53];
  const arrWithdrawalId = [111];

  const claim_estimation = await LsETHContract.estimateGas.claimRedeemRequests(
    arrRequestId,
    arrWithdrawalId,
    { gasLimit: 1 }
  );
  const claimRedeemRequests = await LsETHContract.claimRedeemRequests(
    arrRequestId,
    arrWithdrawalId,
    { gasLimit: claim_estimation }
  );
  const receipt = await claimRedeemRequests.wait();
  console.log(claimRedeemRequests);
};
createClaimRedeemRequest();
```

{% endcode %}

After the claim is processed you will see the the deposit of ETH into your Fireblocks wallet.

To see the status of the claim you can either listen for events or use the Alluvial API. More information on the claiming process can be found [here](https://docs.alluvial.finance/guides/redemptions#claim-redemption-requests).

Congratulations! You have now implemented the ETH staking and LsETH redemption flow using a Fireblocks account and SDK.

Appendix:

Below is the full code from the snippets above:

{% hint style="info" %}
To call a function uncomment the function declaration.
{% endhint %}

{% code title="index.js" overflow="wrap" lineNumbers="true" %}

```javascript
//General dependencies
require('dotenv').config();
const fs = require('fs');
const path = require('path');
const { inspect } = require('util');

//Fireblocks SDKs
const { FireblocksSDK } = require('fireblocks-sdk');
const {
  FireblocksWeb3Provider,
  ChainId,
} = require('@fireblocks/fireblocks-web3-provider');

const ethers = require('ethers');

const baseUrl = 'https://api.fireblocks.io';
const apiSecret = fs.readFileSync(path.resolve('./fb.key'), 'utf8');
const apiKey = process.env.API_KEY;

const ABI = require('./Contract.json').abi;
const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS;
const FIREBLOCKS_ADDRESS = process.env.FIREBLOCKS_ADDRESS;

const fireblocks = new FireblocksSDK(apiSecret, apiKey, baseUrl);

const eip1193Provider = new FireblocksWeb3Provider({
  apiBaseUrl: baseUrl,
  privateKey: apiSecret,
  apiKey: apiKey,
  vaultAccountIds: 3,
  chainId: ChainId.GOERLI,
});

const createVault = async () => {
  const name = 'LsETH blog';

  const vaultAccount = await fireblocks.createVaultAccount(name);

  console.log(inspect(vaultAccount, false, null, true));
};

//createVault()

const getAssets = async () => {
  const supportedAssets = await fireblocks.getSupportedAssets();

  supportedAssets.forEach((asset, index, array) => {
    if (asset.contractAddress == CONTRACT_ADDRESS) {
      console.log(JSON.stringify(asset));
    }
  });
};

//getAssets()

const addAssetToVault = async () => {
  const vaultWallet = await fireblocks.createVaultAsset(
    6,
    'LSETH_ETH_TEST3_4E2A'
  );

  console.log(inspect(vaultWallet, false, null, true));
};
//addAssetToVault()

const createDeposit = async () => {
  const provider = new ethers.providers.Web3Provider(eip1193Provider);
  const LsETHContract = new ethers.Contract(
    CONTRACT_ADDRESS,
    ABI,
    provider.getSigner()
  );

  const gasPrice = await provider.getGasPrice();
  const deposit_estimation = await LsETHContract.estimateGas.deposit({
    from: FIREBLOCKS_ADDRESS,
    value: ethers.utils.parseUnits('0.00000001', 'ether'),
    gasLimit: ethers.utils.hexlify(1),
    nonce: provider.getTransactionCount(FIREBLOCKS_ADDRESS, 'latest'),
  });

  let tx = await LsETHContract.deposit({
    from: FIREBLOCKS_ADDRESS,
    value: ethers.utils.parseUnits('0.000000001', 'ether'),
    gasPrice: gasPrice,
    gasLimit: deposit_estimation,
    nonce: provider.getTransactionCount(FIREBLOCKS_ADDRESS, 'latest'),
  });
  let receipt = await tx.wait();
  console.log(receipt);
};
//createDeposit()

const getTx = async () => {
  const transactions = await fireblocks.getTransactions({
    txHash:
      '0x124e9ef06e38ab0e3ff78d066f8f2eea54b18210390a229c251a0d578853ae20',
  });

  console.log(JSON.stringify(transactions));
};
//getTx()

const getBalance = async () => {
  const vaultAsset = await fireblocks.getVaultAccountAsset(
    3,
    'LSETH_ETH_TEST3_4E2A'
  );
  console.log(JSON.stringify(vaultAsset));
};
//getBalance()

const createRedeemRequest = async () => {
  const provider = new ethers.providers.Web3Provider(eip1193Provider);
  const LsETHContract = new ethers.Contract(
    CONTRACT_ADDRESS,
    ABI,
    provider.getSigner()
  );

  const value = ethers.utils.parseEther('0.000001');

  const redeem_estimation = await LsETHContract.estimateGas.requestRedeem(
    value,
    FIREBLOCKS_ADDRESS,
    { gasLimit: 1 }
  );

  const tx = await LsETHContract.requestRedeem(value, FIREBLOCKS_ADDRESS, {
    gasLimit: redeem_estimation,
  });

  let receipt = await tx.wait();
  console.log(receipt);
};
//createRedeemRequest();

const resolveRedeemRequest = async () => {
  const provider = new ethers.providers.Web3Provider(eip1193Provider);
  const LsETHContract = new ethers.Contract(
    CONTRACT_ADDRESS,
    ABI,
    provider.getSigner()
  );

  const value = ethers.utils.parseEther('0.000001');

  const arrRequestId = [53];

  const resolveRedeem = await LsETHContract.resolveRedeemRequests(arrRequestId);
  console.log(resolveRedeem.toString());
};
//resolveRedeemRequest();

const createClaimRedeemRequest = async () => {
  const provider = new ethers.providers.Web3Provider(eip1193Provider);
  const LsETHContract = new ethers.Contract(
    CONTRACT_ADDRESS,
    ABI,
    provider.getSigner()
  );

  const arrRequestId = [53];
  const arrWithdrawalId = [111];

  const claim_estimation = await LsETHContract.estimateGas.claimRedeemRequests(
    arrRequestId,
    arrWithdrawalId,
    { gasLimit: 1 }
  );
  const claimRedeemRequests = await LsETHContract.claimRedeemRequests(
    arrRequestId,
    arrWithdrawalId,
    { gasLimit: claim_estimation }
  );
  const receipt = await claimRedeemRequests.wait();
  console.log(claimRedeemRequests);
};
//createClaimRedeemRequest();
```

{% endcode %}


# Changelog

Alluvial's changelog records changes made to the Alluvial API specification, including new features, improvements, bug fixes, and more.

## May 1, 2026

* Commercials V3 is live. New v1 reporting endpoints expose platform, originator, and operator fees and rates under the V3 fee model: `/eth/v1/platforms/{date}`, `/eth/v1/originators/{date}`, and `/eth/v1/operators/{date}`. See the [Reporting API reference](https://docs.alluvial.finance/api-reference/reporting#commercials-v3-v1).
* The v0 `/eth/v0/platforms/{date}` and `/eth/v0/operators/{date}` endpoints stop populating platform fees and rates from this date forward. They remain valid for historical data through 2026-04-30. Migrate to the v1 endpoints for any period on or after 2026-05-01.

## July 9, 2024

* Discounting APIs are live! These APIs enable adding a new target rate in the event Platforms need to adjust the net Protocol Service Fee rate for specific accounts and/or wallets.
* Summary APIs are now exposed within the Reporting API. Summary APIs allow for teams to get data for a range of dates rather than a specific day.

## May 17, 2024

* Reporting API now exposes an endpoint for Platforms with segregated accounts to get rewards information. You can see examples in our [reporting guide](https://docs.alluvial.finance/guides/reporting#segregated-account-structure).

## April 24, 2024

* Reporting API now exposes an endpoint for Platforms with an omnibus to get rewards information. You can see examples in our [reporting guide](https://docs.alluvial.finance/guides/reporting).

## March 25, 2024

### On-Platform list

* On-Platform list has been created to allow Platforms that only want to allow for a wallet to take part in secondary actions of LsETH to be accounted for. More information can be found in the [Mint and Redeem](https://docs.alluvial.finance/guides/platform-mint_burn) guide or [Secondary interaction](https://docs.alluvial.finance/guides/platform-listing) guide.
* When creating a wallet, Platforms that allow for minting and redeeming, but only want to allow a wallet for secondary interactions can add these wallets to the on\_platform list.

### Platform APIs

* Introduction of [Platform and Account APIs](https://docs.alluvial.finance/api-reference/onboarding#platformg). These replace the Depositor APIs that are now marked for [deprecation](https://docs.alluvial.finance/api-reference/onboarding#deprecated-apis).

## May 5th, 2023

### Release redeems APIs

* Expose redemption requests by address via /eth/v0/redeems
* Expose redemption requests by id via /eth/v0/redeems/{idx}
* Expose redemption manager (heights) via /eth/v0/redeems\_info

This API can be used with the [redemption guide](https://docs.alluvial.finance/guides/redemptions).

## May 18th, 2023

### Release projection APIs for LsETH redemptions

* Expose redeem projected redeemable at timestamp via /eth/v0/redeems/{idx}/projection
* Expose manager projected fulfilled at timestamp via /eth/v0/redeems\_info/projection

This API can be used with the [redemption guide](https://docs.alluvial.finance/guides/redemptions).

## November 20th, 2023

* Remove address from Allowlister via PATCH /v0/depositors/{idOrKey}/wallets/{idOrAddress}/remove

Rather than removing the entire Depositor object, Platforms can now remove only the wallet address. This is the recommended approach for most cases where an a Platform needs to revoke a wallet address' Allowlist permissions.

The wallet will is still able to transfer LsETH to other wallets.


