Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] SI-1183 add-to-sdk-as-method-by-api #117

Merged
merged 13 commits into from
Mar 26, 2024
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -114,7 +114,7 @@ console.log(balanceETH.toString(), 'ETH balance');

## Migration

For breaking changes between versions see [MIGRATION.md](MIGRATION.md)
For breaking changes between versions see [MIGRATION.md](packages/sdk/MIGRATION.md)

## Documentation

3 changes: 3 additions & 0 deletions packages/sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -5,10 +5,13 @@
### Added

- `Sepolia` testnet
- New method `getWithdrawalWaitingTimeByAmount` for fetching withdrawal waiting time for amount of eth
- New method `getWithdrawalWaitingTimeByRequestIds` for fetching withdrawal waiting time for earlier created requests by their ids

## Playground

- Support for `Sepolia` testnet
- Added blocks with new methods `getWithdrawalWaitingTimeByAmount` and `getWithdrawalWaitingTimeByRequestIds`

# 3.1.0

52 changes: 51 additions & 1 deletion packages/sdk/README.md
Original file line number Diff line number Diff line change
@@ -66,6 +66,9 @@ For breaking changes between versions see [MIGRATION.md](MIGRATION.md)
- [Views](#views)
- [Constants](#constants)
- [Requests info](#requests-info)
- [Waiting time](#waiting-time)
- [Get time by amount](#get-time-by-amount)
- [Get time by request ids](#get-time-by-request-ids)
- [(w)stETH](#wsteth)
- [unstETH NFT](#unsteth-nft)
- [Shares](#shares)
@@ -654,7 +657,7 @@ try {
console.log(
'transaction hash, transaction receipt, confirmations',
requestResult,
'array of requests(nfts) created with ids, amounts,creator, owner'
'array of requests(nfts) created with ids, amounts,creator, owner',
request.results.requests,
);
} catch (error) {
@@ -1010,6 +1013,53 @@ try {
- `pendingRequests` (Type: Array[RequestStatusWithId]): A list of requests pending finalization.
- `pendingAmountStETH` (Type: bigint): The amount of ETH pending claiming.

### Waiting time

#### Methods

##### Get time by amount

###### `getWithdrawalWaitingTimeByAmount`

###### Input Parameters:

- `props: { amount?: bigint }`
- `amount?` (Type: bigint **optional**): The amount of withdrawable eth. In case when it is not passed, it is calculated as default information about queue.

##### Output Parameters:

- Type: Object
- Structure:
- `requestInfo` (Type: Object): Information about withdrawal request
- `finalizationIn` (Type: number): The time needed for withdrawal in milliseconds.
- `finalizationAt` (Type: string): The time when request finalized for withdrawal.
- `type` (Type: WaitingTimeCalculationType): Type of final source of eth for withdrawal.
- `status` (Type: WaitingTimeStatus): Status of withdrawal request.
- `nextCalculationAt` (Type: string): Time when next calculation can be changed.

##### Get time by request ids

###### `getWithdrawalWaitingTimeByRequestIds`

###### Input Parameters:

- `props: { ids: bigint[] }`
- `ids` (ids: Array[bigint]): The ids of withdrawal requests.

##### Output Parameters:

- Type: Array of WithdrawalWaitingTimeRequestInfo objects
- Structure of each object:
- `requestInfo` (Type: RequestByIdInfoDto): Information about withdrawal request.
- `finalizationIn` (Type: number): The time needed for withdrawal in milliseconds.
- `finalizationAt` (Type: string): The time when request finalized for withdrawal.
- `requestId` (Type: string): The request id.
- `requestedAt` (Type: string): The time when withdrawal requested.
- `type` (Type: WaitingTimeCalculationType): Type of final source of eth for withdrawal.
- `status` (Type: WaitingTimeStatus): Status of withdrawal request.
- `nextCalculationAt` (Type: string): Time when next calculation can be changed.


## (w)stETH

stETH and wstETH tokens functionality is presented trough modules with same ERC20 interface that exposes balances, allowances, transfers and ERC2612 permits signing.
7 changes: 7 additions & 0 deletions packages/sdk/src/common/constants.ts
Original file line number Diff line number Diff line change
@@ -111,3 +111,10 @@ export const VIEM_CHAINS: { [key in CHAINS]: Chain } = {
[CHAINS.Holesky]: holesky,
[CHAINS.Sepolia]: sepolia,
};

export const WQ_API_URLS: { [key in CHAINS]: string | null } = {
[CHAINS.Mainnet]: 'https://wq-api.lido.fi',
[CHAINS.Goerli]: 'https://wq-api.testnet.fi',
[CHAINS.Holesky]: 'https://wq-api-holesky.testnet.fi',
[CHAINS.Sepolia]: null,
};
1 change: 1 addition & 0 deletions packages/sdk/src/common/decorators/constants.ts
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ export const ConsoleCss: Record<HeadMessage, string> = {
'Events:': 'color: salmon',
'Statistic:': 'color: purple',
'Rewards:': 'color: greenyellow',
'API:': 'color: mediumpurple',
'Init:':
'color: #33F3FF;text-shadow: 0px 0px 0 #899CD5, 1px 1px 0 #8194CD, 2px 2px 0 #788BC4, 3px 3px 0 #6F82BB, 4px 4px 0 #677AB3, 5px 5px 0 #5E71AA, 6px 6px 0 #5568A1, 7px 7px 0 #4C5F98, 8px 8px 0 #445790, 9px 9px 0 #3B4E87, 10px 10px 0 #32457E, 11px 11px 0 #2A3D76, 12px 12px 0 #21346D, 13px 13px 0 #182B64, 14px 14px 0 #0F225B, 15px 15px 0 #071A53, 16px 16px 0 #02114A, 17px 17px 0 #0B0841, 18px 18px 0 #130039, 19px 19px 0 #1C0930, 20px 20px 0 #251227, 21px 21px 20px rgba(0,0,0,1), 21px 21px 1px rgba(0,0,0,0.5), 0px 0px 20px rgba(0,0,0,.2);font-size: 50px;',
};
3 changes: 2 additions & 1 deletion packages/sdk/src/common/decorators/types.ts
Original file line number Diff line number Diff line change
@@ -13,4 +13,5 @@ export type HeadMessage =
| 'Events:'
| 'Statistic:'
| 'Rewards:'
| 'Deprecation:';
| 'Deprecation:'
| 'API:';
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { expect, describe, test } from '@jest/globals';
import { useWithdraw } from '../../../tests/utils/fixtures/use-withdraw.js';
import { WithdrawalWaitingTimeByRequestIdsParams } from '../types.js';

describe('withdraw waiting time', () => {
const withdraw = useWithdraw();
const { waitingTime } = withdraw;

test('can get withdrawal waiting time by amount', async () => {
const amount = 110n;
const requestInfos = await waitingTime.getWithdrawalWaitingTimeByAmount({
amount,
});
expect(typeof requestInfos.status).toEqual('string');
});

test('can get withdrawal waiting time by request ids', async () => {
const requestsIds = [1234n, 1235n];

const requestInfos = await waitingTime.getWithdrawalWaitingTimeByRequestIds(
{
ids: requestsIds,
} as WithdrawalWaitingTimeByRequestIdsParams,
);

expect(Array.isArray(requestInfos)).toEqual(true);
expect(requestInfos.length).toEqual(requestsIds.length);
});
});
14 changes: 14 additions & 0 deletions packages/sdk/src/withdraw/bus.ts
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ import {
LidoSDKWithdrawApprove,
} from './request/index.js';
import { LidoSDKModule } from '../common/class-primitives/sdk-module.js';
import { LidoSDKWithdrawWaitingTime } from './withdraw-waiting-time.js';

export class Bus extends LidoSDKModule {
private version: string | undefined;
@@ -17,6 +18,7 @@ export class Bus extends LidoSDKModule {
private approvalInstance: LidoSDKWithdrawApprove | undefined;
private claimInstance: LidoSDKWithdrawClaim | undefined;
private requestInstance: LidoSDKWithdrawRequest | undefined;
private waitingTimeInstance: LidoSDKWithdrawWaitingTime | undefined;

// Contract

@@ -89,4 +91,16 @@ export class Bus extends LidoSDKModule {
}
return this.requestInstance;
}

// Waiting Time

get waitingTime(): LidoSDKWithdrawWaitingTime {
if (!this.waitingTimeInstance) {
this.waitingTimeInstance = new LidoSDKWithdrawWaitingTime({
bus: this,
version: this.version,
});
}
return this.waitingTimeInstance;
}
}
60 changes: 60 additions & 0 deletions packages/sdk/src/withdraw/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Address } from 'viem';
import type { Bus } from './bus.js';
import type { AccountValue } from '../index.js';
import { CHAINS } from '../index.js';

export type LidoSDKWithdrawModuleProps = { bus: Bus; version?: string };

@@ -51,3 +52,62 @@ export type GetWithdrawalRequestsInfoReturnType = {
pendingInfo: GetPendingRequestsInfoReturnType;
claimableETH: GetClaimableRequestsETHByAccountReturnType;
};

export type WqApiCustomUrlGetter = (
defaultUrl: string | null,
chainId: CHAINS,
) => string;

export type WithdrawalWaitingTimeByAmountParams = {
amount?: bigint;
getCustomApiUrl?: WqApiCustomUrlGetter;
};

export type RequestInfo = {
finalizationIn: number;
finalizationAt: string;
type: WaitingTimeCalculationType;
};

export type WithdrawalWaitingTimeByAmountResponse = {
requestInfo: RequestInfo;
status: WaitingTimeStatus;
nextCalculationAt: string;
};

export type WithdrawalWaitingTimeByRequestIdsParams = {
ids: readonly bigint[];
requestDelay?: number;
getCustomApiUrl?: WqApiCustomUrlGetter;
};

export type RequestByIdInfo = {
finalizationIn: number;
finalizationAt: string;
requestId?: string;
requestedAt?: string;
type: WaitingTimeCalculationType;
};

export type WithdrawalWaitingTimeRequestInfo = {
requestInfo: RequestByIdInfo;
status: WaitingTimeStatus;
nextCalculationAt: string;
};

export enum WaitingTimeStatus {
initializing = 'initializing',
calculating = 'calculating',
finalized = 'finalized',
calculated = 'calculated',
}

export enum WaitingTimeCalculationType {
buffer = 'buffer',
bunker = 'bunker',
vaultsBalance = 'vaultsBalance',
rewardsOnly = 'rewardsOnly',
validatorBalances = 'validatorBalances',
requestTimestampMargin = 'requestTimestampMargin',
exitValidators = 'exitValidators',
}
112 changes: 112 additions & 0 deletions packages/sdk/src/withdraw/withdraw-waiting-time.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { Logger, ErrorHandler } from '../common/decorators/index.js';

import { BusModule } from './bus-module.js';
import type {
WithdrawalWaitingTimeByAmountResponse,
WithdrawalWaitingTimeRequestInfo,
WithdrawalWaitingTimeByAmountParams,
WithdrawalWaitingTimeByRequestIdsParams,
WqApiCustomUrlGetter,
} from './types.js';
import { ERROR_CODE, WQ_API_URLS } from '../common/index.js';
import { formatEther } from 'viem';

const endpoints = {
calculateByAmount: '/v2/request-time/calculate',
calculateByRequestId: '/v2/request-time',
};

export class LidoSDKWithdrawWaitingTime extends BusModule {
// API call integrations
@Logger('API:')
@ErrorHandler()
public async getWithdrawalWaitingTimeByAmount(
props: WithdrawalWaitingTimeByAmountParams,
): Promise<WithdrawalWaitingTimeByAmountResponse> {
const getCustomApiUrl = props?.getCustomApiUrl;

const query = new URLSearchParams();
if (props.amount) {
query.set('amount', formatEther(props.amount));
}

const baseUrl = this.getBaseUrl(getCustomApiUrl);
const url = `${baseUrl}${endpoints.calculateByAmount}?${query.toString()}`;

const response = await fetch(url, {
headers: {
'WQ-Request-Source': 'sdk',
},
});

return response.json();
}

@Logger('API:')
@ErrorHandler()
public async getWithdrawalWaitingTimeByRequestIds(
props: WithdrawalWaitingTimeByRequestIdsParams,
): Promise<readonly WithdrawalWaitingTimeRequestInfo[]> {
const requestDelay = props?.requestDelay ?? 1000;
const getCustomApiUrl = props?.getCustomApiUrl;

if (!Array.isArray(props.ids) || props.ids.length === 0) {
throw this.bus.core.error({
code: ERROR_CODE.INVALID_ARGUMENT,
message: 'expected not empty array ids',
});
}

const idsPages = [];
const pageSize = 20;
const baseUrl = this.getBaseUrl(getCustomApiUrl);
const path = `${baseUrl}${endpoints.calculateByRequestId}`;

for (let i = 0; i < props.ids.length; i += pageSize) {
idsPages.push(props.ids.slice(i, i + pageSize));
}

const result = [];

for (const page of idsPages) {
const query = new URLSearchParams();
query.set('ids', page.toString());

const url = `${path}?${query.toString()}`;

const response = await fetch(url, {
headers: {
'WQ-Request-Source': 'sdk',
},
});

const requests = await response.json();
result.push(...requests);

if (idsPages.length > 1) {
// avoid backend spam
await new Promise((resolve) => setTimeout(resolve, requestDelay));
}
}

return result;
}

getBaseUrl(getCustomApiUrl?: WqApiCustomUrlGetter) {
const defaultUrl = WQ_API_URLS[this.bus.core.chainId];

const baseUrl =
getCustomApiUrl && typeof getCustomApiUrl === 'function'
? getCustomApiUrl(defaultUrl, this.bus.core.chainId)
: defaultUrl;

if (!baseUrl) {
throw this.bus.core.error({
code: ERROR_CODE.INVALID_ARGUMENT,
message: `wq-api URL is not found for chain ${this.bus.core.chainId}, use getCustomApiUrl prop to setup custom URL`,
});
}

return baseUrl;
}
}
23 changes: 23 additions & 0 deletions playground/demo/withdrawals/request.tsx
Original file line number Diff line number Diff line change
@@ -180,6 +180,29 @@ export const WithdrawalsRequestDemo = () => {
})
}
/>

<Action
title={`Withdrawal Waiting Time By Amount`}
walletAction
action={() =>
withdraw.waitingTime.getWithdrawalWaitingTimeByAmount({
amount,
})
}
/>

<Action
title={`Withdrawal Waiting Time For Account Requests`}
walletAction
action={async () => {
const ids = await withdraw.views.getWithdrawalRequestsIds({
account,
});
return withdraw.waitingTime.getWithdrawalWaitingTimeByRequestIds({
ids,
});
}}
/>
</Accordion>
);
};