Skip to content

Commit

Permalink
Merge pull request #44 from lidofinance/develop
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
Jeday authored Oct 23, 2023
2 parents 303771c + 88f7a3b commit 3f86a29
Show file tree
Hide file tree
Showing 32 changed files with 1,742 additions and 52 deletions.
159 changes: 155 additions & 4 deletions packages/sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ The project is currently under development and may change in the future.
- [APR](#apr)
- [Lido events](#lido-events)
- [Rebase](#rebase)
- [Rewards](#Rewards)

## Installation

Expand Down Expand Up @@ -1085,6 +1086,27 @@ const wsteth = await lidoSDK.core.getContractAddress(

### APR

#### Methods

##### `getLastApr`

###### Output Parameters:

- Type: number

##### `getSmaApr`

###### Input Parameters:

- `props: { days }`
- `days` (Type: number): The number of days back to return sma apr.

###### Output Parameters:

- Type: number

#### Examples

```ts
import { LidoSDK } from '@lidofinance/lido-ethereum-sdk';

Expand All @@ -1094,16 +1116,60 @@ const lidoSDK = new LidoSDK({
});

const lastApr = await lidoSDK.statistics.apr.getLastApr();
const smaApr = await lidoSDK.statistics.apr.getSmaApr();
const smaApr = await lidoSDK.statistics.apr.getSmaApr({ days: 7 });

console.log(lastApr, 'last apr');
console.log(smaApr, 'sma apr');
console.log(smaApr, 'sma apr by 7 days');
```

## Lido events

### Rebase

#### Methods

##### `getLastRebaseEvent`

###### Output Parameters:

- Type: RebaseEvent

##### `getFirstRebaseEvent`

###### Input Parameters:

- `props: { days, fromBlockNumber }`
- `days` (Type: number): The number of days ago from which to start searching for the first rebase event.
- `fromBlockNumber` (Type: number | undefined): Block number from which to start the search.

###### Output Parameters:

- Type: RebaseEvent

##### `getRebaseEvents`

###### Input Parameters:

- `props: { count }`
- `count` (Type: number): The number of events to return.

###### Output Parameters:

- Type: Array of RebaseEvent objects

##### `getRebaseEventsByDays`

###### Input Parameters:

- `props: { days }`
- `days` (Type: number): The number of days back to return rebase events.

###### Output Parameters:

- Type: Array of RebaseEvent objects

#### Examples

```ts
import { LidoSDK } from '@lidofinance/lido-ethereum-sdk';

Expand All @@ -1113,12 +1179,97 @@ const lidoSDK = new LidoSDK({
});

const lastRebaseEvent = await lidoSDK.events.stethEvents.getLastRebaseEvent();
const firstRebaseEvent = await lidoSDK.events.stethEvents.getFirstRebaseEvent({
days: 3,
});
const lastRebaseEventsByCount =
await lidoSDK.events.stethEvents.getRebaseEvents({ count: 10 });
await lidoSDK.events.stethEvents.getRebaseEvents({ count: 7 });
const lastRebaseEventsByDays =
await lidoSDK.events.stethEvents.getRebaseEventByDays({ days: 10 });
await lidoSDK.events.stethEvents.getRebaseEventsByDays({ days: 7 });

console.log(lastRebaseEvent, 'last rebase event');
console.log(firstRebaseEvent, 'first rebase event');
console.log(lastRebaseEventsByCount, 'last rebase events by count');
console.log(lastRebaseEventsByDays, 'last rebase events by days');
```

## Rewards

This module allows you to query historical rewards data for given address via chain events or subgraph.

### Common Options

- **address** - address of an account you want to query rewards for
- **toBlock** [default: `latest` ] - block number or tag for upper bound for rewards. `pending` not allowed
- **fromBlock** - block number or tag for lower bound for rewards. `pending` not allowed
- **blocksBack** - alternative to **fromBlock**. Amount of blocks to look back from **toBlock**.
- **step** [default: `1000` ] - step per one request for large queries. For chain method max amount of blocks per one request. For subgraph method max amount of entities returned per one requests.
- **includeZeroRebases** [default: `false` ] - include rebase events when users had no rewards(because of empty balance)

### Common Return

```Typescript
type RewardsResult = {
// pre query states
baseBalance: bigint;
baseBalanceShares: bigint;
baseShareRate: number;
// computed block numbers
fromBlock: bigint;
toBlock: bigint;
// query result in block/logIndex ascending order
rewards: {
type: 'submit' | 'withdrawal' | 'rebase' | 'transfer_in' | 'transfer_out';
change: bigint; // negative or positive change in stETH
changeShares: bigint; // same in shares
balance: bigint; // post event balance in stETH
balanceShares: bigint; // same in shares
shareRate: number; // apx share rate at a time of event
originalEvent: RewardsChainEvents | RewardsSubgraphEvents ; // original event from chain/subgraph, contains extra info
}[]
};
```

### Get Rewards from chain

This method heavily utilizes RPC fetching chain event logs. It's better suited for smaller,recent queries. Beware that this might cause rate limit issues on free RPC endpoints.

```ts
const lidoSDK = new LidoSDK({
chainId: 5,
rpcUrls: ['https://eth-goerli.alchemyapi.io/v2/{ALCHEMY_API_KEY}'],
});

const rewardsQuery = await lidoSDK.rewards.getRewardsFromChain({
address: rewardsAddress,
blocksBack: 1000,
});

console.log(rewardsQuery.rewards);
```

### Get Rewards from subgraph

This method requires you to provide API URL to send subgraph requests to. It's better suited for larger, more historical queries.

#### Important notes

**toBlock** is capped by last indexed block in subgraph. Block number is available in result object by `lastIndexedBlock`.

```ts
const lidoSDK = new LidoSDK({
chainId: 5,
rpcUrls: ['https://eth-goerli.alchemyapi.io/v2/{ALCHEMY_API_KEY}'],
});

const rewardsQuery = await lidoSDK.rewards.getRewardsFromSubgraph({
address: rewardsAddress,
blocksBack: 10000,
step: 500, // defaults to 1000, max entities per one request to endpoint
getSubgraphUrl(graphId, chainId) {
return `https://gateway.thegraph.com/api/${apiKey}/subgraphs/id/${id}`;
},
});

console.log(rewardsQuery.rewards);
```
7 changes: 7 additions & 0 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@
"default": "./dist/cjs/events/index.js",
"types": "./dist/types/events/index.d.ts"
},
"./rewards": {
"import": "./dist/esm/rewards/index.js",
"default": "./dist/cjs/rewards/index.js",
"types": "./dist/types/rewards/index.d.ts"
},
"./package.json": "./package.json"
},
"typesVersions": {
Expand Down Expand Up @@ -111,6 +116,8 @@
},
"dependencies": {
"@ethersproject/bytes": "^5.7.0",
"graphql": "^16.8.1",
"graphql-request": "^6.1.0",
"tiny-invariant": "^1.3.1",
"viem": "^1.15.3"
},
Expand Down
16 changes: 16 additions & 0 deletions packages/sdk/src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@ export const LIDO_LOCATOR_BY_CHAIN: {
[CHAINS.Holesky]: '0x28FAB2059C713A7F9D8c86Db49f9bb0e96Af1ef8',
};

export const SUBRGRAPH_ID_BY_CHAIN: {
[key in CHAINS]: string;
} = {
[CHAINS.Mainnet]: 'HXfMc1jPHfFQoccWd7VMv66km75FoxVHDMvsJj5vG5vf',
[CHAINS.Goerli]: 'QmeDfGTuNbSoZ71zi3Ch4WNRbzALfiFPnJMYUFPinLiFNa',
[CHAINS.Holesky]: '',
};

export const EARLIEST_TOKEN_REBASED_EVENT: {
[key in CHAINS]: bigint;
} = {
[CHAINS.Mainnet]: 17272708n,
[CHAINS.Goerli]: 8712039n,
[CHAINS.Holesky]: 52174n,
} as const;

export const LIDO_TOKENS = {
steth: 'stETH',
wsteth: 'wstETH',
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/common/decorators/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const ConsoleCss: Record<HeadMessage, string> = {
'Permit:': 'color: lime',
'Events:': 'color: salmon',
'Statistic:': 'color: purple',
'Rewards:': 'color: greenyellow',
'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
Expand Up @@ -11,4 +11,5 @@ export type HeadMessage =
| 'Init:'
| 'Permit:'
| 'Events:'
| 'Statistic:';
| 'Statistic:'
| 'Rewards:';
4 changes: 4 additions & 0 deletions packages/sdk/src/common/utils/address-equal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { Address } from 'viem';

export const addressEqual = (a: Address | string, b: Address | string) =>
a.toLowerCase() === b.toLowerCase();
1 change: 1 addition & 0 deletions packages/sdk/src/common/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { getFeeData, type FeeData } from './getFeeData.js';
export { SDKError, type SDKErrorProps } from './SDKError.js';
export { getErrorMessage, type ErrorMessage } from './getErrorMessage.js';
export { isBigint } from './isBigint.js';
export { addressEqual } from './address-equal.js';
9 changes: 9 additions & 0 deletions packages/sdk/src/core/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
LIDO_TOKENS,
PERMIT_MESSAGE_TYPES,
VIEM_CHAINS,
SUBRGRAPH_ID_BY_CHAIN,
} from '../common/constants.js';

import { LidoLocatorAbi } from './abi/lidoLocator.js';
Expand Down Expand Up @@ -351,6 +352,14 @@ export default class LidoSDKCore {
}
}

@Logger('Utils:')
@Cache(30 * 60 * 1000, ['chain.id'])
public getSubgraphId(): string {
const id = SUBRGRAPH_ID_BY_CHAIN[this.chainId];
invariant(id, `Subgraph is not supported for chain ${this.chainId}`);
return id;
}

public async performTransaction(
props: PerformTransactionProps,
getGasLimit: (overrides: TransactionOptions) => Promise<bigint>,
Expand Down
7 changes: 7 additions & 0 deletions packages/sdk/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {

import { LIDO_TOKENS, SUPPORTED_CHAINS } from '../common/constants.js';
import { SDKError } from '../index.js';
import type LidoSDKCore from './core.js';

export type LOG_MODE = 'info' | 'debug';

Expand All @@ -31,6 +32,12 @@ export type LidoSDKCoreProps =
| LidoSDKCorePropsRpcUrls
| LidoSDKCorePropsRpcProvider;

export type LidoSDKCommonProps =
| {
core: LidoSDKCore;
}
| ({ core: undefined } & LidoSDKCoreProps);

export type EtherValue = string | bigint;

export enum TransactionCallbackStage {
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/events/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { LidoSDKEvents } from './events.js';
export * from './types.js';
44 changes: 39 additions & 5 deletions packages/sdk/src/events/stethEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { type LidoSDKEventsProps, RebaseEvent } from './types.js';

const BLOCKS_BY_DAY = 7600n;
const REBASE_EVENT_ABI_INDEX = 8;
const DAYS_LIMIT = 7;

export class LidoSDKStethEvents {
readonly core: LidoSDKCore;
Expand Down Expand Up @@ -58,15 +59,16 @@ export class LidoSDKStethEvents {
@ErrorHandler()
public async getLastRebaseEvent(): Promise<RebaseEvent | undefined> {
const contract = await this.getContractStETH();
const DAYS_LIMIT = 3;
const lastBlock = await this.getLastBlock();

for (let days = 1; days <= DAYS_LIMIT; days++) {
const dayAgoBlock = await this.getBlockByDays({ days });
for (let day = 1; day <= DAYS_LIMIT; day++) {
const fromBlock = lastBlock.number - BLOCKS_BY_DAY * BigInt(day);
const logs = await this.core.rpcProvider.getLogs({
address: contract.address,
event: StethEventsAbi[REBASE_EVENT_ABI_INDEX],
fromBlock: dayAgoBlock.number,
toBlock: 'latest',
fromBlock: fromBlock,
toBlock: fromBlock + BLOCKS_BY_DAY,
strict: true,
});

if (logs.length > 0) return logs[logs.length - 1];
Expand All @@ -75,6 +77,36 @@ export class LidoSDKStethEvents {
return undefined;
}

@Logger('Events:')
@ErrorHandler()
public async getFirstRebaseEvent(props: {
days: number;
fromBlockNumber?: bigint;
}): Promise<RebaseEvent | undefined> {
const { days } = props;
const fromBlockNumber =
props.fromBlockNumber ?? (await this.getLastBlock()).number;

const contract = await this.getContractStETH();

for (let day = 1; day <= DAYS_LIMIT; day++) {
const from = fromBlockNumber - BigInt(days + 1 - day) * BLOCKS_BY_DAY;
const to = from + BLOCKS_BY_DAY;

const logs = await this.core.rpcProvider.getLogs({
address: contract.address,
event: StethEventsAbi[REBASE_EVENT_ABI_INDEX],
fromBlock: from,
toBlock: to,
strict: true,
});

if (logs.length > 0) return logs[0];
}

return undefined;
}

@Logger('Events:')
@ErrorHandler()
public async getRebaseEventsByDays(props: {
Expand All @@ -95,6 +127,7 @@ export class LidoSDKStethEvents {
event: StethEventsAbi[REBASE_EVENT_ABI_INDEX],
fromBlock: block.number,
toBlock: 'latest',
strict: true,
});

const logsByDays = logs.filter((log) => {
Expand All @@ -121,6 +154,7 @@ export class LidoSDKStethEvents {
event: StethEventsAbi[8],
fromBlock: block.number,
toBlock: 'latest',
strict: true,
});

return logs.slice(logs.length - count);
Expand Down
Loading

0 comments on commit 3f86a29

Please sign in to comment.