diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 8c7397b9..76746033 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -40,4 +40,6 @@ jobs: TEST_PRIVATE_KEY: ${{ secrets.TEST_PRIVATE_KEY }} TEST_CHAIN_ID: ${{ vars.TEST_CHAIN_ID }} TEST_RPC_URL: ${{ secrets.TEST_RPC_URL }} + TEST_L2_CHAIN_ID: ${{ vars.TEST_L2_CHAIN_ID }} + TEST_L2_RPC_URL: ${{ secrets.TEST_L2_RPC_URL }} TEST_SUBGRAPH_URL: ${{ secrets.TEST_SUBGRAPH_URL }} diff --git a/.github/workflows/publish-dry-run.yml b/.github/workflows/publish-dry-run.yml index 1d5fe077..2c8996f5 100644 --- a/.github/workflows/publish-dry-run.yml +++ b/.github/workflows/publish-dry-run.yml @@ -10,7 +10,7 @@ on: jobs: publish-dry-run: runs-on: ubuntu-latest - environment: development + environment: pre-publish steps: - name: Checkout repo uses: actions/checkout@v4 @@ -31,14 +31,16 @@ jobs: run: yarn build:packages - name: Dry run Publish - run: yarn multi-semantic-release --dry-run --silent | grep -E '#|###|\*' > dry_run_output.txt + run: | + yarn multi-semantic-release --dry-run --silent > /tmp/multi-semantic-release-output + grep -E '#|###|\*' /tmp/multi-semantic-release-output > dry_run_output.txt || [ $? -eq 1 ] env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Write results to summary run: | - if [ -s dry_run_output.txt ]; then + if [ -s dry_run_output.txt ]; then echo "# Packages to be published:" >> $GITHUB_STEP_SUMMARY cat dry_run_output.txt >> $GITHUB_STEP_SUMMARY else diff --git a/docs/examples/rewards/intro.md b/docs/examples/rewards/intro.md index b651a133..519d8fc8 100644 --- a/docs/examples/rewards/intro.md +++ b/docs/examples/rewards/intro.md @@ -26,7 +26,7 @@ The proposed approach involves maintaining an accounting model based on stETH [s ## Implementation notes Token shares are managed at the contract level, with dedicated methods for handling share-related operations. Detailed documentation on these methods can be found in the [shares-related methods](https://docs.lido.fi/contracts/lido/#shares-related-methods) section. -You can also use [SDK methods](/methods/shares) to work with users’ shares directly. +You can also use [SDK methods](/modules/shares) to work with users’ shares directly. ## Usage @@ -40,13 +40,13 @@ By adopting this approach and leveraging the capabilities of the SDK, developers The [Lido Ethereum SDK](/) has the full set of features in this regard: -- [estimating APR](/methods/lido-statistics#getlastapr) for the latest token rebase. -- [calculating average APR](/methods/lido-statistics#getsmaapr) over a selected period. -- [last rebase event](/methods/lido-events#getlastrebaseevent) (contains share rate) -- [first rebase event](/methods/lido-events#getfirstrebaseevent) starting from the reference point in the past -- [get last N](/methods/lido-events#getlastrebaseevents) rebase events -- [get all rebase events](/methods/lido-events#getrebaseevents) for the last N days +- [estimating APR](/modules/lido-statistics#getlastapr) for the latest token rebase. +- [calculating average APR](/modules/lido-statistics#getsmaapr) over a selected period. +- [last rebase event](/modules/lido-events#getlastrebaseevent) (contains share rate) +- [first rebase event](/modules/lido-events#getfirstrebaseevent) starting from the reference point in the past +- [get last N](/modules/lido-events#getlastrebaseevents) rebase events +- [get all rebase events](/modules/lido-events#getrebaseevents) for the last N days - assessing specific rewards accrued over a chosen period by an address - - [On-chain](/methods/rewards#get-rewards-from-chain) - - [Subgraph](/methods/rewards#get-rewards-from-subgraph) -- work with [user balances](/methods/shares) based on stETH shares + - [On-chain](/modules/rewards#get-rewards-from-chain) + - [Subgraph](/modules/rewards#get-rewards-from-subgraph) +- work with [user balances](/modules/shares) based on stETH shares diff --git a/docs/examples/rewards/retrieve-rewards-onchain.md b/docs/examples/rewards/retrieve-rewards-onchain.md index 19814ee7..fcb90b10 100644 --- a/docs/examples/rewards/retrieve-rewards-onchain.md +++ b/docs/examples/rewards/retrieve-rewards-onchain.md @@ -11,7 +11,7 @@ sidebar_position: 4 [Implementation example](https://github.com/lidofinance/lido-ethereum-sdk/blob/main/examples/rewards/src/rewardsOnChain.ts) Information about the user’s rewards can be calculating from on-chain using SDK without the need to calculate using a formula. -To do this, you need to use the `getRewardsFromChain` method ([Docs](/methods/rewards)) +To do this, you need to use the `getRewardsFromChain` method ([Docs](/modules/rewards)) The method allows you to request rewards for a certain period of time (days, seconds, blocks) Simplified code example: diff --git a/docs/examples/rewards/retrieve-rewards-subgraph.md b/docs/examples/rewards/retrieve-rewards-subgraph.md index ab98ba41..ad0e9947 100644 --- a/docs/examples/rewards/retrieve-rewards-subgraph.md +++ b/docs/examples/rewards/retrieve-rewards-subgraph.md @@ -14,7 +14,7 @@ sidebar_position: 5 [Implementation example](https://github.com/lidofinance/lido-ethereum-sdk/blob/main/examples/rewards/src/rewardsSubgraph.ts) Information about the user’s rewards can be obtained from off-chain using SDK without the need for calculation using a formula. -To do this, you need to use the `getRewardsFromSubgraph` method [[Docs](/methods/rewards)]. You will also need a key to access `The Graph`. ([Docs](https://docs.lido.fi/integrations/subgraph/)) +To do this, you need to use the `getRewardsFromSubgraph` method [[Docs](/modules/rewards)]. You will also need a key to access `The Graph`. ([Docs](https://docs.lido.fi/integrations/subgraph/)) Simplified code example: diff --git a/docs/examples/rewards/subscribe-on-events.md b/docs/examples/rewards/subscribe-on-events.md index 02186bc1..82c15192 100644 --- a/docs/examples/rewards/subscribe-on-events.md +++ b/docs/examples/rewards/subscribe-on-events.md @@ -20,7 +20,7 @@ The first thing you need to do is subscribe to the `TokenRebased` event to recei Next, you need to calculate the user’s balance in stETH before the event (if unknown) and calculate the user’s balance in stETH after the event. The difference between these values will be the user’s rewards for the rebase. -Docs: [Shares](/methods/shares) +Docs: [Shares](/modules/shares) Simplified code example: ```ts diff --git a/docs/lidoPulse/get-started/usage.md b/docs/lidoPulse/get-started/usage.md index e99ff220..69aacd56 100644 --- a/docs/lidoPulse/get-started/usage.md +++ b/docs/lidoPulse/get-started/usage.md @@ -15,7 +15,7 @@ To form a JSON-RPC request, you need to specify: - `params`: The parameters required by the method. - `id`: A unique identifier for the request. -The method names and their required parameters can be found in the [Lido SDK documentation](/category/methods). +The method names and their required parameters can be found in the [Lido SDK documentation](/category/modules). ## Example JSON-RPC Request diff --git a/docs/sdk/intro.md b/docs/sdk/intro.md index 749aef4d..883d105c 100644 --- a/docs/sdk/intro.md +++ b/docs/sdk/intro.md @@ -18,11 +18,11 @@ For changes between versions see [CHANGELOG.MD](https://github.com/lidofinance/l ## Installation -You can install the Lido Ethereum SDK using npm or yarn: +You can install the Lido Ethereum SDK using npm or yarn. `viem` is required as a peer dep: ```bash // SDK (stakes, wrap, withdrawals) -yarn add @lidofinance/lido-ethereum-sdk +yarn add viem @lidofinance/lido-ethereum-sdk ``` ## Usage @@ -107,7 +107,7 @@ For breaking changes between versions see [MIGRATION.md](https://github.com/lido ## Documentation -For additional information about available methods and functionality, refer to the [the documentation for the Lido Ethereum SDK](/category/methods). +For additional information about available methods and functionality, refer to the [the documentation for the Lido Ethereum SDK](/category/modules). ## Playground diff --git a/docs/sdk/methods/_category_.json b/docs/sdk/modules/_category_.json similarity index 75% rename from docs/sdk/methods/_category_.json rename to docs/sdk/modules/_category_.json index 5c995b5f..2e2e35b8 100644 --- a/docs/sdk/methods/_category_.json +++ b/docs/sdk/modules/_category_.json @@ -1,5 +1,5 @@ { - "label": "Methods", + "label": "Modules", "position": 3, "link": { "type": "generated-index" diff --git a/docs/sdk/modules/l2.md b/docs/sdk/modules/l2.md new file mode 100644 index 00000000..714e78b5 --- /dev/null +++ b/docs/sdk/modules/l2.md @@ -0,0 +1,131 @@ +--- +sidebar_position: 14 +--- + +# L2 + +Modules exposes Lido MultiChain deployments. [See full info here](https://lido.fi/lido-multichain). + +## LidoSDKL2 + +This is core module for all L2 functionality. It will throw error if used on with chains that are not currently supported. + +| **Chain** | **wsETH** | **stETH+(un)Wrap** | +| ---------------- | --------- | ------------------ | +| Optimism Sepolia | ✅ | ✅ | +| Optmism | ✅ | ✅ | +| 🔜 | | | + +Use this helper to understand which contracts are supported on chain: + +```ts +import { + LidoSDKL2, + LIDO_L2_CONTRACT_NAMES, + CHAINS, +} from '@lidofinance/lido-ethereum-sdk'; + +LidoSDKL2.isContractAvailableOn( + LIDO_L2_CONTRACT_NAMES.wsteth, + CHAINS.OptimismSepolia, +); // true +// Example +LidoSDKL2.isContractAvailableOn(LIDO_L2_CONTRACT_NAMES.steth, CHAINS.Arbitrum); // false +``` + +### Fields + +- `wsteth`: see [LidoSDKL2Wsteth](#lidosdkl2wsteth) +- `steth`: see [LidoSDKL2Steth](#lidosdkl2steth) + +### Methods + +On L2 with stETH deployments bridged wstETH is wrapped to stETH. And stETH is unwrapped to wstETH. Those semantics are upkept in SDK with more explicit naming. See [LIP-22](https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-22.md#rebasable-token-steth-on-l2) for more details. + +#### Wrap bridged wstETH to stETH + +To wrap stETH you first need to approve stETH to wrap contract: + +```ts +import { LidoSDK } from '@lidofinance/lido-ethereum-sdk'; + +const lidoSDK = new LidoSDK({ + rpcUrls: ['https://rpc-url'], + chainId: 11155420, // OP sepolia + web3Provider: LidoSDKCore.createWeb3Provider(11155420, window.ethereum), +}); + +// get existing allowance +const allowance = await lidoSDK.l2.getWstethForWrapAllowance(); + +// if value is more than allowance perform approve +if (allowance < value) { + const approveResult = await lidoSDK.wrap.approveWstethForWrap({ + value, + callback, + }); +} + +// wrap wstETH +const wrapTx = await lidoSDK.wrap.wrapWstethToSteth({ value, callback }); + +const { stethReceived, wstethWrapped } = wrapTx.results; +``` + +#### Unwrap stETH to wstETH + +```ts +// unwrap stETH to receive wstETH +const unwrapTx = await lidoSDK.l2.unwrapStethToWsteth({ + value: unwrapAmount, + callback, +}); + +console.log(unwrapTx.result.stethUnwrapped, unwrapTx.result.wstethReceived); +``` + +### Wrap utilities + +For all transaction methods helper methods are available similar to `stake` module: + +- `...populateTX`: returns ready to sign transaction object with all data encoded +- `...simulateTX`: performs dry-ran of the transaction to see if it will execute on the network + +## LidoSDKL2Wsteth + +This submodule is built on top of existing ERC20 modules and has extra functionality. See docs for all [ERC20 related methods](./w-steth.md). +For original L2 ABI functionality use `.getL2Contract()` and get raw Viem contract instance. + +## LidoSDKL2Steth + +This submodule is built on top of existing ERC20 modules but has extra L2 stETH related features. See docs for all [ERC20 related methods](./w-steth.md). +For original L2 ABI functionality use `.getL2Contract()` and get raw Viem contract instance. + +```ts +import { LidoSDK } from '@lidofinance/lido-ethereum-sdk'; + +const lidoSDK = new LidoSDK({ + rpcUrls: ['https://rpc-url'], + chainId: 11155420, // OP sepolia + web3Provider: LidoSDKCore.createWeb3Provider(11155420, window.ethereum), +}); + +// balance of stETH for account in shares +const balanceShares = await lidoSDK.l2.steth.balanceShares(address); + +// transferring shares is equivalent to transferring corresponding amount of stETH +const transferTx = await lidoSDK.l2.steth.transferShares({ + account, + amount, + to, +}); + +// converting stETH amount to shares trough on-chain call based on actual share rate +// This also can be used to convert stETH to wstETH as 1 wstETH = 1 share +const shares = await lidoSDK.l2.steth.convertToShares(1000n); +// reverse +const steth = await lidoSDK.l2.steth.convertToSteth(1000n); + +// total supply of shares and ether in protocol +const totalShares = await lidoSDK.totalShares(); +``` diff --git a/docs/sdk/methods/lido-contract-addresses.md b/docs/sdk/modules/lido-contract-addresses.md similarity index 100% rename from docs/sdk/methods/lido-contract-addresses.md rename to docs/sdk/modules/lido-contract-addresses.md diff --git a/docs/sdk/methods/lido-events.md b/docs/sdk/modules/lido-events.md similarity index 100% rename from docs/sdk/methods/lido-events.md rename to docs/sdk/modules/lido-events.md diff --git a/docs/sdk/methods/lido-statistics.md b/docs/sdk/modules/lido-statistics.md similarity index 100% rename from docs/sdk/methods/lido-statistics.md rename to docs/sdk/modules/lido-statistics.md diff --git a/docs/sdk/methods/rewards.md b/docs/sdk/modules/rewards.md similarity index 93% rename from docs/sdk/methods/rewards.md rename to docs/sdk/modules/rewards.md index 33f89fae..f0830897 100644 --- a/docs/sdk/methods/rewards.md +++ b/docs/sdk/modules/rewards.md @@ -10,11 +10,11 @@ y ## Common Options - **address** - (Type: Address) address of an account you want to query rewards for -- **to** (Type: [`blockType`](/methods/lido-events#getrebaseevents)) defaults to `{block: "latest"}`, upper bound for query +- **to** (Type: [`blockType`](/modules/lido-events#getrebaseevents)) defaults to `{block: "latest"}`, upper bound for query -- **from** (Type: [`blockType`](/methods/lido-events#getrebaseevents)) lower bound for query +- **from** (Type: [`blockType`](/modules/lido-events#getrebaseevents)) lower bound for query or -- **back** (Type: [`backType`](/methods/lido-events#getrebaseevents)) alternative way to define lower bound relative to `to` +- **back** (Type: [`backType`](/modules/lido-events#getrebaseevents)) alternative way to define lower bound relative to `to` - **includeZeroRebases** [default: `false` ] - include rebase events when users had no rewards(because of empty balance) - **includeOnlyRewards** [default: `false` ] - include only rebase events diff --git a/docs/sdk/methods/shares.md b/docs/sdk/modules/shares.md similarity index 100% rename from docs/sdk/methods/shares.md rename to docs/sdk/modules/shares.md diff --git a/docs/sdk/methods/stake.md b/docs/sdk/modules/stake.md similarity index 100% rename from docs/sdk/methods/stake.md rename to docs/sdk/modules/stake.md diff --git a/docs/sdk/methods/unsteth-nft.md b/docs/sdk/modules/unsteth-nft.md similarity index 100% rename from docs/sdk/methods/unsteth-nft.md rename to docs/sdk/modules/unsteth-nft.md diff --git a/docs/sdk/methods/w-steth.md b/docs/sdk/modules/w-steth.md similarity index 100% rename from docs/sdk/methods/w-steth.md rename to docs/sdk/modules/w-steth.md diff --git a/docs/sdk/methods/withdraw.md b/docs/sdk/modules/withdraw.md similarity index 100% rename from docs/sdk/methods/withdraw.md rename to docs/sdk/modules/withdraw.md diff --git a/docs/sdk/methods/wrap.md b/docs/sdk/modules/wrap.md similarity index 100% rename from docs/sdk/methods/wrap.md rename to docs/sdk/modules/wrap.md diff --git a/examples/erlang-bridge/package.json b/examples/erlang-bridge/package.json index 643e58f6..215804bc 100644 --- a/examples/erlang-bridge/package.json +++ b/examples/erlang-bridge/package.json @@ -9,6 +9,6 @@ }, "dependencies": { "@lidofinance/lido-ethereum-sdk": "workspace:*", - "viem": "^2.0.6" + "viem": "^2.21.9" } } diff --git a/examples/rewards/package.json b/examples/rewards/package.json index cc2d9170..59a39c71 100644 --- a/examples/rewards/package.json +++ b/examples/rewards/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@lidofinance/lido-ethereum-sdk": "workspace:*", - "viem": "^2.0.6" + "viem": "^2.21.9" }, "devDependencies": { "rimraf": "^5.0.5", diff --git a/packages/lido-pulse/package.json b/packages/lido-pulse/package.json index dd752956..3dc086ec 100644 --- a/packages/lido-pulse/package.json +++ b/packages/lido-pulse/package.json @@ -53,7 +53,7 @@ "fastify": "^4.28.1", "fastify-plugin": "^4.5.1", "reflect-metadata": "^0.2.2", - "viem": "^2.0.6" + "viem": "^2.21.9" }, "devDependencies": { "@types/node": "^20.14.10", diff --git a/packages/sdk/CHANGELOG.md b/packages/sdk/CHANGELOG.md index 2b91e223..43257697 100644 --- a/packages/sdk/CHANGELOG.md +++ b/packages/sdk/CHANGELOG.md @@ -1,3 +1,26 @@ +# 3.5.0 + +## Breaking change + +- `viem` is no longer an internal dependency and is listed as peer dependency + +## SDK + +### Added + +- `LidoSDKL2` module is added to support Lido on L2 networks functionality +- `Optimism` and `Optimism-sepolia` chains are added as separate L2 chains +- `core.getL2ContractAddress` and `LIDO_L2_CONTRACT_NAMES enum` are added to support l2 contracts +- ABIs are exported from corresponding modules to support custom functionality and direct viem access + +### Fixed + +- `multicall` is used only if supported by client + +## Playground + +- L2 and updated reef-knot support + # 3.4.0 ## SDK diff --git a/packages/sdk/MIGRATION.md b/packages/sdk/MIGRATION.md index ed23239b..206b19a9 100644 --- a/packages/sdk/MIGRATION.md +++ b/packages/sdk/MIGRATION.md @@ -1,3 +1,7 @@ +# Migrating from V3 -> V4 + +- `viem` is now a peer dependency and you will need to install it separately. + # Migrating from V2 -> V3 ## Common diff --git a/packages/sdk/package.json b/packages/sdk/package.json index da82685f..7ecb6639 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -71,6 +71,11 @@ "default": "./dist/cjs/shares/index.js", "types": "./dist/types/shares/index.d.ts" }, + "./l2": { + "import": "./dist/esm/l2/index.js", + "default": "./dist/cjs/l2/index.js", + "types": "./dist/types/l2/index.d.ts" + }, "./package.json": "./package.json" }, "typesVersions": { @@ -104,6 +109,9 @@ ], "shares": [ "./dist/types/shares/index.d.ts" + ], + "l2": [ + "./dist/types/l2/index.d.ts" ] } }, @@ -145,8 +153,10 @@ "dependencies": { "@ethersproject/bytes": "^5.7.0", "graphql": "^16.8.1", - "graphql-request": "^6.1.0", - "viem": "^2.0.6" + "graphql-request": "^6.1.0" + }, + "peerDependencies": { + "viem": "^2.21" }, "devDependencies": { "@jest/globals": "^29.7.0", diff --git a/packages/sdk/src/common/constants.ts b/packages/sdk/src/common/constants.ts index b81e29a9..e1cfe09c 100644 --- a/packages/sdk/src/common/constants.ts +++ b/packages/sdk/src/common/constants.ts @@ -1,11 +1,20 @@ import { type Address, type Chain } from 'viem'; -import { goerli, mainnet, holesky, sepolia } from 'viem/chains'; +import { + goerli, + mainnet, + holesky, + sepolia, + optimismSepolia, + optimism, +} from 'viem/chains'; export enum CHAINS { Goerli = 5, Mainnet = 1, Holesky = 17000, Sepolia = 11155111, + Optimism = 10, + OptimismSepolia = 11155420, } export const APPROX_BLOCKS_BY_DAY = 7600n; @@ -15,14 +24,15 @@ export const SUPPORTED_CHAINS: CHAINS[] = [ CHAINS.Mainnet, CHAINS.Holesky, CHAINS.Sepolia, + CHAINS.Optimism, + CHAINS.OptimismSepolia, ]; export const SUBMIT_EXTRA_GAS_TRANSACTION_RATIO = 1.05; export const GAS_TRANSACTION_RATIO_PRECISION = 10 ** 7; -export const ESTIMATE_ACCOUNT = '0x87c0e047F4e4D3e289A56a36570D4CB957A37Ef1'; export const LIDO_LOCATOR_BY_CHAIN: { - [key in CHAINS]: Address; + [key in CHAINS]?: Address; } = { [CHAINS.Mainnet]: '0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb', [CHAINS.Goerli]: '0x1eDf09b5023DC86737b59dE68a8130De878984f5', @@ -31,16 +41,14 @@ export const LIDO_LOCATOR_BY_CHAIN: { }; export const SUBRGRAPH_ID_BY_CHAIN: { - [key in CHAINS]: string | null; + [key in CHAINS]?: string; } = { [CHAINS.Mainnet]: 'Sxx812XgeKyzQPaBpR5YZWmGV5fZuBaPdh7DFhzSwiQ', [CHAINS.Goerli]: 'QmeDfGTuNbSoZ71zi3Ch4WNRbzALfiFPnJMYUFPinLiFNa', - [CHAINS.Holesky]: null, - [CHAINS.Sepolia]: null, }; export const EARLIEST_TOKEN_REBASED_EVENT: { - [key in CHAINS]: bigint; + [key in CHAINS]?: bigint; } = { [CHAINS.Mainnet]: 17272708n, [CHAINS.Goerli]: 8712039n, @@ -73,6 +81,24 @@ export const enum LIDO_CONTRACT_NAMES { wsteth = 'wsteth', } +export const enum LIDO_L2_CONTRACT_NAMES { + wsteth = 'wsteth', + steth = 'steth', +} + +export const LIDO_L2_CONTRACT_ADDRESSES: { + [key in CHAINS]?: { [key2 in LIDO_L2_CONTRACT_NAMES]?: Address }; +} = { + [CHAINS.OptimismSepolia]: { + wsteth: '0x24B47cd3A74f1799b32B2de11073764Cb1bb318B', + steth: '0xf49d208b5c7b10415c7beafe9e656f2df9edfe3b', + }, + [CHAINS.Optimism]: { + wsteth: '0x1F32b1c2345538c0c6f582fCB022739c4A194Ebb', + steth: '0x76A50b8c7349cCDDb7578c6627e79b5d99D24138', + }, +}; + export const CONTRACTS_BY_TOKENS = { [LIDO_TOKENS.steth]: LIDO_CONTRACT_NAMES.lido, [LIDO_TOKENS.wsteth]: LIDO_CONTRACT_NAMES.wsteth, @@ -110,11 +136,12 @@ export const VIEM_CHAINS: { [key in CHAINS]: Chain } = { [CHAINS.Goerli]: goerli, [CHAINS.Holesky]: holesky, [CHAINS.Sepolia]: sepolia, + [CHAINS.Optimism]: optimism, + [CHAINS.OptimismSepolia]: optimismSepolia, }; -export const WQ_API_URLS: { [key in CHAINS]: string | null } = { +export const WQ_API_URLS: { [key in CHAINS]?: string } = { [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, }; diff --git a/packages/sdk/src/core/abi/wq.ts b/packages/sdk/src/core/abi/wq.ts index d24e641b..ad0503f0 100644 --- a/packages/sdk/src/core/abi/wq.ts +++ b/packages/sdk/src/core/abi/wq.ts @@ -1,4 +1,4 @@ -export const wqAbi = [ +export const wqWstethAddressAbi = [ { inputs: [], name: 'WSTETH', diff --git a/packages/sdk/src/core/core.ts b/packages/sdk/src/core/core.ts index c471b65c..8aeb0b18 100644 --- a/packages/sdk/src/core/core.ts +++ b/packages/sdk/src/core/core.ts @@ -37,10 +37,12 @@ import { SUBRGRAPH_ID_BY_CHAIN, APPROX_SECONDS_PER_BLOCK, NOOP, + LIDO_L2_CONTRACT_NAMES, + LIDO_L2_CONTRACT_ADDRESSES, } from '../common/constants.js'; import { LidoLocatorAbi } from './abi/lidoLocator.js'; -import { wqAbi } from './abi/wq.js'; +import { wqWstethAddressAbi } from './abi/wq.js'; import type { LidoSDKCoreProps, PermitSignature, @@ -57,6 +59,7 @@ import type { import { TransactionCallbackStage } from './types.js'; import { permitAbi } from './abi/permit.js'; import { LidoSDKCacheable } from '../common/class-primitives/cacheable.js'; +import { readContract } from 'viem/actions'; export default class LidoSDKCore extends LidoSDKCacheable { public static readonly INFINITY_DEADLINE_VALUE = maxUint256; @@ -170,7 +173,13 @@ export default class LidoSDKCore extends LidoSDKCacheable { @Logger('Contracts:') @Cache(30 * 60 * 1000, ['chain.id']) public contractAddressLidoLocator(): Address { - return LIDO_LOCATOR_BY_CHAIN[this.chain.id as CHAINS]; + const locator = LIDO_LOCATOR_BY_CHAIN[this.chain.id as CHAINS]; + invariant( + locator, + `Lido Ethereum Contacts are not supported on ${this.chain.name}(${this.chain.id})`, + ERROR_CODE.NOT_SUPPORTED, + ); + return locator; } @Logger('Contracts:') @@ -186,18 +195,6 @@ export default class LidoSDKCore extends LidoSDKCacheable { }); } - @Logger('Contracts:') - @Cache(30 * 60 * 1000, ['chain.id']) - private getContractWQ( - address: Address, - ): GetContractReturnType { - return getContract({ - address, - abi: wqAbi, - client: this.rpcProvider, - }); - } - // PERMIT @Logger('Permit:') public async signPermit(props: SignPermitProps): Promise { @@ -360,7 +357,7 @@ export default class LidoSDKCore extends LidoSDKCacheable { // eth_getCode returns hex string of bytecode at address // for accounts it's "0x" // for contract it's potentially very long hex (can't be safely&quickly parsed) - const result = await this.rpcProvider.getBytecode({ address: address }); + const result = await this.rpcProvider.getCode({ address: address }); return result ? result !== '0x' : false; } @@ -377,8 +374,12 @@ export default class LidoSDKCore extends LidoSDKCacheable { const lidoLocator = this.getContractLidoLocator(); if (contract === 'wsteth') { const withdrawalQueue = await lidoLocator.read.withdrawalQueue(); - const contract = this.getContractWQ(withdrawalQueue); - const wstethAddress = await contract.read.WSTETH(); + + const wstethAddress = await readContract(this.rpcProvider, { + abi: wqWstethAddressAbi, + address: withdrawalQueue, + functionName: 'WSTETH', + }); return wstethAddress; } else { @@ -386,10 +387,28 @@ export default class LidoSDKCore extends LidoSDKCacheable { } } + @Logger('Utils:') + @Cache(30 * 60 * 1000, ['chain.id']) + public getL2ContractAddress(contract: LIDO_L2_CONTRACT_NAMES): Address { + const chainConfig = LIDO_L2_CONTRACT_ADDRESSES[this.chain.id as CHAINS]; + invariant( + chainConfig, + `Lido L2 contracts are not supported for ${this.chain.name}(${this.chain.id})`, + ERROR_CODE.NOT_SUPPORTED, + ); + const address = chainConfig[contract]; + invariant( + address, + `Lido L2 on ${this.chain.name}(${this.chain.id}) does not have ${contract} contract`, + ERROR_CODE.NOT_SUPPORTED, + ); + return address; + } + @Logger('Utils:') @Cache(30 * 60 * 1000, ['chain.id']) public getSubgraphId(): string | null { - const id = SUBRGRAPH_ID_BY_CHAIN[this.chainId]; + const id = SUBRGRAPH_ID_BY_CHAIN[this.chainId] ?? null; return id; } diff --git a/packages/sdk/src/erc20/erc20.ts b/packages/sdk/src/erc20/erc20.ts index 135a2328..52f0c58d 100644 --- a/packages/sdk/src/erc20/erc20.ts +++ b/packages/sdk/src/erc20/erc20.ts @@ -121,7 +121,9 @@ export abstract class AbstractLidoSDKErc20 extends LidoSDKModule { const contract = await this.getContract(); return isTransferFrom - ? contract.simulate.transferFrom([from, to, amount], { account }) + ? contract.simulate.transferFrom([from, to, amount], { + account, + }) : contract.simulate.transfer([to, amount], { account }); } @@ -217,7 +219,9 @@ export abstract class AbstractLidoSDKErc20 extends LidoSDKModule { public async simulateApprove(props: NoCallback) { const { account, amount, to } = await this.parseProps(props); const contract = await this.getContract(); - return contract.simulate.approve([to, amount], { account }); + return contract.simulate.approve([to, amount], { + account, + }); } @Logger('Views:') @@ -239,35 +243,51 @@ export abstract class AbstractLidoSDKErc20 extends LidoSDKModule { decimals: number; domainSeparator: Hash; }> { - const contract = { address: await this.contractAddress(), abi: erc20abi }; - const [decimals, name, symbol, domainSeparator] = - await this.core.rpcProvider.multicall({ - allowFailure: false, - contracts: [ - { - ...contract, - functionName: 'decimals', - }, - { - ...contract, - functionName: 'name', - }, - { - ...contract, - functionName: 'symbol', - }, - { - ...contract, - functionName: 'DOMAIN_SEPARATOR', - }, - ] as const, - }); - return { - decimals, - name, - symbol, - domainSeparator, - }; + if (this.core.rpcProvider.multicall) { + const contract = { address: await this.contractAddress(), abi: erc20abi }; + const [decimals, name, symbol, domainSeparator] = + await this.core.rpcProvider.multicall({ + allowFailure: false, + contracts: [ + { + ...contract, + functionName: 'decimals', + }, + { + ...contract, + functionName: 'name', + }, + { + ...contract, + functionName: 'symbol', + }, + { + ...contract, + functionName: 'DOMAIN_SEPARATOR', + }, + ] as const, + }); + return { + decimals, + name, + symbol, + domainSeparator, + }; + } else { + const contract = await this.getContract(); + const [decimals, name, symbol, domainSeparator] = await Promise.all([ + contract.read.decimals(), + contract.read.name(), + contract.read.symbol(), + contract.read.DOMAIN_SEPARATOR(), + ]); + return { + decimals, + name, + symbol, + domainSeparator, + }; + } } @Logger('Views:') diff --git a/packages/sdk/src/erc20/index.ts b/packages/sdk/src/erc20/index.ts index 468bc773..17498525 100644 --- a/packages/sdk/src/erc20/index.ts +++ b/packages/sdk/src/erc20/index.ts @@ -1,3 +1,4 @@ +export { erc20abi } from './abi/erc20abi.js'; export { AbstractLidoSDKErc20 } from './erc20.js'; export { LidoSDKstETH } from './steth.js'; export { LidoSDKwstETH } from './wsteth.js'; diff --git a/packages/sdk/src/erc20/types.ts b/packages/sdk/src/erc20/types.ts index d0fa0c04..3421020d 100644 --- a/packages/sdk/src/erc20/types.ts +++ b/packages/sdk/src/erc20/types.ts @@ -9,7 +9,7 @@ import { export type InnerTransactionProps = Required; export type ParsedTransactionProps = - Omit & { + Omit & { callback: NonNullable; account: JsonRpcAccount; } & (TProps extends { amount: EtherValue } ? { amount: bigint } : object); diff --git a/packages/sdk/src/erc20/wsteth.ts b/packages/sdk/src/erc20/wsteth.ts index 75723a43..e278e34a 100644 --- a/packages/sdk/src/erc20/wsteth.ts +++ b/packages/sdk/src/erc20/wsteth.ts @@ -12,6 +12,8 @@ export class LidoSDKwstETH extends AbstractLidoSDKErc20 { return this.core.getContractAddress(LIDO_CONTRACT_NAMES.wsteth); } + @Logger('Contracts:') + @Cache(30 * 60 * 1000, ['core.chain.id']) public override async erc721Domain(): Promise<{ name: string; version: string; diff --git a/packages/sdk/src/events/index.ts b/packages/sdk/src/events/index.ts index df5449fa..86bde4bf 100644 --- a/packages/sdk/src/events/index.ts +++ b/packages/sdk/src/events/index.ts @@ -1,2 +1,3 @@ +export { StethEventsAbi } from './abi/stethEvents.js'; export { LidoSDKEvents } from './events.js'; export * from './types.js'; diff --git a/packages/sdk/src/l2/__test__/l2.test.ts b/packages/sdk/src/l2/__test__/l2.test.ts new file mode 100644 index 00000000..1cf4c768 --- /dev/null +++ b/packages/sdk/src/l2/__test__/l2.test.ts @@ -0,0 +1,344 @@ +import { beforeAll, describe, expect, jest, test } from '@jest/globals'; +import { expectSDKModule } from '../../../tests/utils/expect/expect-sdk-module.js'; +import { + useL2, + useL2Rpc, + useTestL2RpcProvider, +} from '../../../tests/utils/fixtures/use-l2.js'; +import { LidoSDKL2 } from '../l2.js'; +import { LidoSDKL2Steth, LidoSDKL2Wsteth } from '../tokens.js'; +import { expectERC20 } from '../../../tests/utils/expect/expect-erc20.js'; +import { LIDO_L2_CONTRACT_NAMES } from '../../common/constants.js'; +import { expectERC20Wallet } from '../../../tests/utils/expect/expect-erc20-wallet.js'; +import { + useAccount, + useAltAccount, +} from '../../../tests/utils/fixtures/use-wallet-client.js'; +import { getContract } from 'viem'; +import { bridgedWstethAbi } from '../abi/brigedWsteth.js'; +import { expectAddress } from '../../../tests/utils/expect/expect-address.js'; +import { expectContract } from '../../../tests/utils/expect/expect-contract.js'; +import { + expectAlmostEqualBn, + expectNonNegativeBn, +} from '../../../tests/utils/expect/expect-bn.js'; +import { + SPENDING_TIMEOUT, + testSpending, +} from '../../../tests/utils/test-spending.js'; +import { expectTxCallback } from '../../../tests/utils/expect/expect-tx-callback.js'; +import { + expectPopulatedTx, + expectPopulatedTxToRun, +} from '../../../tests/utils/expect/expect-populated-tx.js'; + +const prepareL2Wsteth = async () => { + const l2 = useL2(); + const account = useAccount(); + const { testClient } = useTestL2RpcProvider(); + const wstethAddress = await l2.wsteth.contractAddress(); + + const wstethImpersonated = getContract({ + abi: bridgedWstethAbi, + address: wstethAddress, + client: testClient, + }); + + const bridge = await wstethImpersonated.read.bridge(); + + await testClient.setBalance({ + address: account.address, + value: 100000000000000n, + }); + + await testClient.setBalance({ + address: bridge, + value: 100000000000000n, + }); + + await testClient.request({ + method: 'evm_addAccount' as any, + params: [bridge, 'pass'], + }); + + await testClient.request({ + method: 'personal_unlockAccount' as any, + params: [bridge, 'pass'], + }); + + await wstethImpersonated.write.bridgeMint([account.address, 2000n], { + account: bridge, + chain: testClient.chain, + }); +}; + +describe('LidoSDKL2', () => { + const l2 = useL2(); + const account = useAccount(); + beforeAll(async () => { + await prepareL2Wsteth(); + }); + + test('is correct module', () => { + expectSDKModule(LidoSDKL2); + }); + + test('has correct address', async () => { + const address = await l2.contractAddress(); + const stethAddress = l2.core.getL2ContractAddress( + LIDO_L2_CONTRACT_NAMES.steth, + ); + expectAddress(address, stethAddress); + }); + + test('has contract', async () => { + const stethAddress = l2.core.getL2ContractAddress( + LIDO_L2_CONTRACT_NAMES.steth, + ); + const contract = await l2.getContract(); + expectContract(contract, stethAddress); + }); + + test('get allowance', async () => { + const allowance = await l2.getWstethForWrapAllowance(account.address); + expectNonNegativeBn(allowance); + + const contractAddress = await l2.contractAddress(); + const altAllowance = await l2.wsteth.allowance({ + account: account.address, + to: contractAddress, + }); + expect(allowance).toEqual(altAllowance); + }); +}); + +describe('LidoSDKL2 wrap', () => { + const l2 = useL2(); + const account = useAccount(); + + const value = 100n; + + beforeAll(prepareL2Wsteth); + + testSpending('set allowance', async () => { + const mock = jest.fn(); + const tx = await l2.approveWstethForWrap({ value, callback: mock }); + expectTxCallback(mock, tx); + await expect(l2.getWstethForWrapAllowance(account)).resolves.toEqual(value); + }); + + testSpending('wrap populate', async () => { + const stethAddress = l2.core.getL2ContractAddress( + LIDO_L2_CONTRACT_NAMES.steth, + ); + const tx = await l2.wrapWstethToStethPopulateTx({ value }); + expectAddress(tx.to, stethAddress); + expectAddress(tx.from, account.address); + expectPopulatedTx(tx, undefined); + await expectPopulatedTxToRun(tx, l2.core.rpcProvider); + }); + + testSpending('wrap simulate', async () => { + const stethAddress = l2.core.getL2ContractAddress( + LIDO_L2_CONTRACT_NAMES.steth, + ); + const tx = await l2.wrapWstethToStethSimulateTx({ value }); + expectAddress(tx.address, stethAddress); + }); + + testSpending('wrap wsteth to steth', async () => { + const stethValue = await l2.steth.convertToSteth(value); + const stethBalanceBefore = await l2.steth.balance(account.address); + const wstethBalanceBefore = await l2.wsteth.balance(account.address); + const mock = jest.fn(); + const tx = await l2.wrapWstethToSteth({ value, callback: mock }); + expectTxCallback(mock, tx); + const stethBalanceAfter = await l2.steth.balance(account.address); + const wstethBalanceAfter = await l2.wsteth.balance(account.address); + + const stethDiff = stethBalanceAfter - stethBalanceBefore; + const wstethDiff = wstethBalanceAfter - wstethBalanceBefore; + + expectAlmostEqualBn(stethDiff, stethValue); + expectAlmostEqualBn(wstethDiff, -value); + + expect(tx.result).toBeDefined(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const result = tx.result!; + expectAlmostEqualBn(result.stethReceived, stethDiff); + expect(result.wstethWrapped).toEqual(-wstethDiff); + + await expect( + l2.getWstethForWrapAllowance(account.address), + ).resolves.toEqual(0n); + }); + + testSpending('unwrap steth populate', async () => { + const stethAddress = l2.core.getL2ContractAddress( + LIDO_L2_CONTRACT_NAMES.steth, + ); + const tx = await l2.unwrapStethPopulateTx({ value }); + expectAddress(tx.to, stethAddress); + expectAddress(tx.from, account.address); + expectPopulatedTx(tx, undefined); + await expectPopulatedTxToRun(tx, l2.core.rpcProvider); + }); + + testSpending('unwrap steth simulate', async () => { + const stethAddress = l2.core.getL2ContractAddress( + LIDO_L2_CONTRACT_NAMES.steth, + ); + const tx = await l2.unwrapStethSimulateTx({ value }); + expectAddress(tx.address, stethAddress); + }); + + testSpending('unwrap', async () => { + const stethValue = await l2.steth.convertToSteth(value); + const stethBalanceBefore = await l2.steth.balance(account.address); + const wstethBalanceBefore = await l2.wsteth.balance(account.address); + const mock = jest.fn(); + const tx = await l2.unwrapStethToWsteth({ + value: stethValue, + callback: mock, + }); + expectTxCallback(mock, tx); + const stethBalanceAfter = await l2.steth.balance(account.address); + const wstethBalanceAfter = await l2.wsteth.balance(account.address); + + const stethDiff = stethBalanceAfter - stethBalanceBefore; + const wstethDiff = wstethBalanceAfter - wstethBalanceBefore; + + expectAlmostEqualBn(stethDiff, -stethValue); + expectAlmostEqualBn(wstethDiff, value); + + expect(tx.result).toBeDefined(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const { stethUnwrapped, wstethReceived } = tx.result!; + expectAlmostEqualBn(stethUnwrapped, -stethDiff); + expect(wstethReceived).toEqual(wstethDiff); + }); +}); + +describe('LidoSDKL2Wsteth', () => { + const l2 = useL2(); + const l2Rpc = useL2Rpc(); + + beforeAll(async () => { + await prepareL2Wsteth(); + }); + + // wstETH erc20 tests + expectERC20({ + contractName: LIDO_L2_CONTRACT_NAMES.wsteth, + constructedWithWeb3Core: l2.wsteth, + isL2: true, + ModulePrototype: LidoSDKL2Wsteth, + constructedWithRpcCore: l2Rpc.wsteth, + }); + + expectERC20Wallet({ + contractName: LIDO_L2_CONTRACT_NAMES.wsteth, + constructedWithWeb3Core: l2.wsteth, + isL2: true, + constructedWithRpcCore: l2Rpc.wsteth, + }); +}); + +describe('LidoSDKL2Steth', () => { + const l2 = useL2(); + const l2Rpc = useL2Rpc(); + const account = useAccount(); + + beforeAll(async () => { + await prepareL2Wsteth(); + + await l2.approveWstethForWrap({ value: 1000n, account }); + await l2.wrapWstethToSteth({ value: 1000n, account }); + }); + + // stETH erc20 tests + expectERC20({ + ModulePrototype: LidoSDKL2Steth, + contractName: LIDO_L2_CONTRACT_NAMES.steth, + constructedWithWeb3Core: l2.steth, + isL2: true, + constructedWithRpcCore: l2Rpc.steth, + }); + + expectERC20Wallet({ + contractName: LIDO_L2_CONTRACT_NAMES.steth, + constructedWithWeb3Core: l2.steth, + isL2: true, + constructedWithRpcCore: l2Rpc.steth, + }); +}); + +describe('LidoSDKL2Steth shares', () => { + const l2 = useL2(); + const account = useAccount(); + const { address: altAddress } = useAltAccount(); + const value = 1000n; + + beforeAll(async () => { + await prepareL2Wsteth(); + + await l2.approveWstethForWrap({ value, account }); + await l2.wrapWstethToSteth({ value, account }); + }); + + test('shares balance and conversions', async () => { + const balanceSteth = await l2.steth.balance(); + const shares = await l2.steth.balanceShares(account); + + const convertedToShares = await l2.steth.convertToShares(balanceSteth); + expectAlmostEqualBn(shares, convertedToShares); + const convertedToSteth = await l2.steth.convertToSteth(shares); + expectAlmostEqualBn(balanceSteth, convertedToSteth); + }); + + test('populate transfer', async () => { + const tx = await l2.steth.populateTransferShares({ + to: altAddress, + amount: 100n, + }); + expectPopulatedTx(tx, undefined, true); + await expectPopulatedTxToRun(tx, l2.core.rpcProvider); + }); + + test('simulate transfer', async () => { + const contractAddressSteth = l2.steth.core.getL2ContractAddress( + LIDO_L2_CONTRACT_NAMES.steth, + ); + const tx = await l2.steth.simulateTransferShares({ + to: altAddress, + amount: 100n, + }); + expectAddress(tx.request.address, contractAddressSteth); + expectAddress(tx.request.functionName, 'transferShares'); + }); + + testSpending( + 'can transfer shares', + async () => { + const amount = 100n; + const amountSteth = await l2.steth.convertToSteth(amount); + const balanceStethBefore = await l2.steth.balance(account.address); + const balanceSharesBefore = await l2.steth.balanceShares(account.address); + const mockTxCallback = jest.fn(); + + const tx = await l2.steth.transferShares({ + amount, + to: altAddress, + callback: mockTxCallback, + }); + expectTxCallback(mockTxCallback, tx); + + const balanceStethAfter = await l2.steth.balance(account.address); + const balanceSharesAfter = await l2.steth.balanceShares(account.address); + expect(balanceSharesAfter - balanceSharesBefore).toEqual(-amount); + // due to protocol rounding error this can happen + expectAlmostEqualBn(balanceStethAfter - balanceStethBefore, -amountSteth); + }, + SPENDING_TIMEOUT, + ); +}); diff --git a/packages/sdk/src/l2/abi/brigedWsteth.ts b/packages/sdk/src/l2/abi/brigedWsteth.ts new file mode 100644 index 00000000..c54530f8 --- /dev/null +++ b/packages/sdk/src/l2/abi/brigedWsteth.ts @@ -0,0 +1,265 @@ +export const bridgedWstethAbi = [ + { inputs: [], name: 'ErrorAccountIsZeroAddress', type: 'error' }, + { inputs: [], name: 'ErrorDeadlineExpired', type: 'error' }, + { inputs: [], name: 'ErrorInvalidSignature', type: 'error' }, + { inputs: [], name: 'ErrorMetadataIsAlreadyInitialized', type: 'error' }, + { inputs: [], name: 'ErrorMetadataIsNotInitialized', type: 'error' }, + { inputs: [], name: 'ErrorNameIsEmpty', type: 'error' }, + { inputs: [], name: 'ErrorNotBridge', type: 'error' }, + { inputs: [], name: 'ErrorNotEnoughAllowance', type: 'error' }, + { inputs: [], name: 'ErrorNotEnoughBalance', type: 'error' }, + { inputs: [], name: 'ErrorSymbolIsEmpty', type: 'error' }, + { inputs: [], name: 'ErrorZeroAddressBridge', type: 'error' }, + { inputs: [], name: 'ErrorZeroDecimals', type: 'error' }, + { inputs: [], name: 'InvalidContractVersionIncrement', type: 'error' }, + { inputs: [], name: 'NonZeroContractVersionOnInit', type: 'error' }, + { + inputs: [ + { internalType: 'uint256', name: 'expected', type: 'uint256' }, + { internalType: 'uint256', name: 'received', type: 'uint256' }, + ], + name: 'UnexpectedContractVersion', + 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: 'version', + type: 'uint256', + }, + ], + name: 'ContractVersionSet', + 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', + }, + { + inputs: [], + name: 'DOMAIN_SEPARATOR', + outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: '', type: 'address' }, + { internalType: 'address', name: '', type: 'address' }, + ], + name: 'allowance', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'spender_', type: 'address' }, + { internalType: 'uint256', name: 'amount_', type: 'uint256' }, + ], + name: 'approve', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: '', type: 'address' }], + name: 'balanceOf', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'bridge', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'account_', type: 'address' }, + { internalType: 'uint256', name: 'amount_', type: 'uint256' }, + ], + name: 'bridgeBurn', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'account_', type: 'address' }, + { internalType: 'uint256', name: 'amount_', type: 'uint256' }, + ], + name: 'bridgeMint', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'decimals', + outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'eip712Domain', + outputs: [ + { internalType: 'bytes1', name: 'fields', type: 'bytes1' }, + { internalType: 'string', name: 'name', type: 'string' }, + { internalType: 'string', name: 'version', type: 'string' }, + { internalType: 'uint256', name: 'chainId', type: 'uint256' }, + { internalType: 'address', name: 'verifyingContract', type: 'address' }, + { internalType: 'bytes32', name: 'salt', type: 'bytes32' }, + { internalType: 'uint256[]', name: 'extensions', type: 'uint256[]' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'string', name: 'name_', type: 'string' }, + { internalType: 'string', name: 'version_', type: 'string' }, + ], + name: 'finalizeUpgrade_v2', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'getContractVersion', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'string', name: 'name_', type: 'string' }, + { internalType: 'string', name: 'symbol_', type: 'string' }, + { internalType: 'string', name: 'version_', type: 'string' }, + ], + name: 'initialize', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'name', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'owner', type: 'address' }], + name: 'nonces', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'owner_', type: 'address' }, + { internalType: 'address', name: 'spender_', type: 'address' }, + { internalType: 'uint256', name: 'value_', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline_', type: 'uint256' }, + { internalType: 'bytes', name: 'signature_', type: 'bytes' }, + ], + name: 'permit', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'owner_', type: 'address' }, + { internalType: 'address', name: 'spender_', type: 'address' }, + { internalType: 'uint256', name: 'value_', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline_', type: 'uint256' }, + { internalType: 'uint8', name: 'v_', type: 'uint8' }, + { internalType: 'bytes32', name: 'r_', type: 'bytes32' }, + { internalType: 'bytes32', name: 's_', type: 'bytes32' }, + ], + name: 'permit', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'symbol', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'totalSupply', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'to_', type: 'address' }, + { internalType: 'uint256', name: 'amount_', 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: 'amount_', type: 'uint256' }, + ], + name: 'transferFrom', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/packages/sdk/src/l2/abi/rebasableL2Steth.ts b/packages/sdk/src/l2/abi/rebasableL2Steth.ts new file mode 100644 index 00000000..50a48f53 --- /dev/null +++ b/packages/sdk/src/l2/abi/rebasableL2Steth.ts @@ -0,0 +1,390 @@ +export const rebasableL2StethAbi = [ + { + inputs: [ + { internalType: 'string', name: 'name_', type: 'string' }, + { internalType: 'string', name: 'symbol_', type: 'string' }, + { internalType: 'string', name: 'version_', type: 'string' }, + { internalType: 'uint8', name: 'decimals_', type: 'uint8' }, + { internalType: 'address', name: 'tokenToWrapFrom_', type: 'address' }, + { internalType: 'address', name: 'tokenRateOracle_', type: 'address' }, + { internalType: 'address', name: 'bridge_', type: 'address' }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { inputs: [], name: 'ErrorAccountIsZeroAddress', type: 'error' }, + { inputs: [], name: 'ErrorDeadlineExpired', type: 'error' }, + { inputs: [], name: 'ErrorInvalidSignature', type: 'error' }, + { inputs: [], name: 'ErrorNameIsEmpty', type: 'error' }, + { inputs: [], name: 'ErrorNotBridge', type: 'error' }, + { inputs: [], name: 'ErrorNotEnoughAllowance', type: 'error' }, + { inputs: [], name: 'ErrorNotEnoughBalance', type: 'error' }, + { inputs: [], name: 'ErrorSymbolIsEmpty', type: 'error' }, + { inputs: [], name: 'ErrorTransferToRebasableContract', type: 'error' }, + { inputs: [], name: 'ErrorZeroAddressL2ERC20TokenBridge', type: 'error' }, + { inputs: [], name: 'ErrorZeroAddressTokenRateOracle', type: 'error' }, + { inputs: [], name: 'ErrorZeroAddressTokenToWrapFrom', type: 'error' }, + { inputs: [], name: 'ErrorZeroDecimals', type: 'error' }, + { inputs: [], name: 'ErrorZeroSharesUnwrap', type: 'error' }, + { inputs: [], name: 'ErrorZeroSharesWrap', type: 'error' }, + { inputs: [], name: 'ErrorZeroTokensUnwrap', type: 'error' }, + { inputs: [], name: 'InvalidContractVersionIncrement', type: 'error' }, + { inputs: [], name: 'NonZeroContractVersionOnInit', type: 'error' }, + { + inputs: [ + { internalType: 'uint256', name: 'expected', type: 'uint256' }, + { internalType: 'uint256', name: 'received', type: 'uint256' }, + ], + name: 'UnexpectedContractVersion', + 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: 'version', + type: 'uint256', + }, + ], + name: 'ContractVersionSet', + 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: 'from', type: 'address' }, + { indexed: true, internalType: 'address', name: 'to', type: 'address' }, + { + indexed: false, + internalType: 'uint256', + name: 'sharesValue', + type: 'uint256', + }, + ], + name: 'TransferShares', + type: 'event', + }, + { + inputs: [], + name: 'DOMAIN_SEPARATOR', + outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'L2_ERC20_TOKEN_BRIDGE', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'TOKEN_RATE_ORACLE', + outputs: [ + { internalType: 'contract ITokenRateOracle', name: '', type: 'address' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'TOKEN_RATE_ORACLE_DECIMALS', + outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'TOKEN_TO_WRAP_FROM', + outputs: [{ internalType: 'contract IERC20', name: '', type: 'address' }], + stateMutability: 'view', + 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: 'amount_', type: 'uint256' }, + ], + name: 'approve', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'account_', type: 'address' }], + name: 'balanceOf', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'account_', type: 'address' }, + { internalType: 'uint256', name: 'tokenAmount_', type: 'uint256' }, + ], + name: 'bridgeUnwrap', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'account_', type: 'address' }, + { internalType: 'uint256', name: 'sharesAmount_', type: 'uint256' }, + ], + name: 'bridgeWrap', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'decimals', + outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'eip712Domain', + outputs: [ + { internalType: 'bytes1', name: 'fields', type: 'bytes1' }, + { internalType: 'string', name: 'name', type: 'string' }, + { internalType: 'string', name: 'version', type: 'string' }, + { internalType: 'uint256', name: 'chainId', type: 'uint256' }, + { internalType: 'address', name: 'verifyingContract', type: 'address' }, + { internalType: 'bytes32', name: 'salt', type: 'bytes32' }, + { internalType: 'uint256[]', name: 'extensions', type: 'uint256[]' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getContractVersion', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: 'tokenAmount_', type: 'uint256' }, + ], + name: 'getSharesByTokens', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: 'sharesAmount_', type: 'uint256' }, + ], + name: 'getTokensByShares', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTotalShares', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'string', name: 'name_', type: 'string' }, + { internalType: 'string', name: 'symbol_', type: 'string' }, + { internalType: 'string', name: 'version_', type: 'string' }, + ], + name: 'initialize', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'name', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'owner', type: 'address' }], + name: 'nonces', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'owner_', type: 'address' }, + { internalType: 'address', name: 'spender_', type: 'address' }, + { internalType: 'uint256', name: 'value_', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline_', type: 'uint256' }, + { internalType: 'bytes', name: 'signature_', type: 'bytes' }, + ], + name: 'permit', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'owner_', type: 'address' }, + { internalType: 'address', name: 'spender_', type: 'address' }, + { internalType: 'uint256', name: 'value_', type: 'uint256' }, + { internalType: 'uint256', name: 'deadline_', type: 'uint256' }, + { internalType: 'uint8', name: 'v_', type: 'uint8' }, + { internalType: 'bytes32', name: 'r_', type: 'bytes32' }, + { internalType: 'bytes32', name: 's_', type: 'bytes32' }, + ], + name: 'permit', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'account_', type: 'address' }], + name: 'sharesOf', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'symbol', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'totalSupply', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'to_', type: 'address' }, + { internalType: 'uint256', name: 'amount_', 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: 'amount_', type: 'uint256' }, + ], + name: 'transferFrom', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'recipient_', type: 'address' }, + { internalType: 'uint256', name: 'sharesAmount_', type: 'uint256' }, + ], + name: 'transferShares', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'sender_', type: 'address' }, + { internalType: 'address', name: 'recipient_', type: 'address' }, + { internalType: 'uint256', name: 'sharesAmount_', type: 'uint256' }, + ], + name: 'transferSharesFrom', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: 'tokenAmount_', type: 'uint256' }, + ], + name: 'unwrap', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: 'sharesAmount_', type: 'uint256' }, + ], + name: 'unwrapShares', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: 'sharesAmount_', type: 'uint256' }, + ], + name: 'wrap', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/packages/sdk/src/l2/index.ts b/packages/sdk/src/l2/index.ts new file mode 100644 index 00000000..f4c8c6bd --- /dev/null +++ b/packages/sdk/src/l2/index.ts @@ -0,0 +1,11 @@ +export { bridgedWstethAbi } from './abi/brigedWsteth.js'; +export { rebasableL2StethAbi } from './abi/rebasableL2Steth.js'; +export { LidoSDKL2 } from './l2.js'; +export { LidoSDKL2Wsteth, LidoSDKL2Steth } from './tokens.js'; + +export type { + SharesTransferProps, + UnwrapResults, + WrapProps, + WrapResults, +} from './types.js'; diff --git a/packages/sdk/src/l2/l2.ts b/packages/sdk/src/l2/l2.ts new file mode 100644 index 00000000..28667ad4 --- /dev/null +++ b/packages/sdk/src/l2/l2.ts @@ -0,0 +1,304 @@ +import { LidoSDKModule } from '../common/class-primitives/sdk-module.js'; +import type { AccountValue, LidoSDKCommonProps } from '../core/types.js'; +import { LidoSDKL2Steth, LidoSDKL2Wsteth } from './tokens.js'; + +import { + getContract, + encodeFunctionData, + decodeEventLog, + isAddressEqual, + getAbiItem, + toEventHash, + type GetContractReturnType, + type WalletClient, + type Address, + type WriteContractParameters, + type TransactionReceipt, +} from 'viem'; + +import { + CHAINS, + LIDO_L2_CONTRACT_ADDRESSES, + LIDO_L2_CONTRACT_NAMES, + NOOP, +} from '../common/constants.js'; +import { Cache, Logger, ErrorHandler } from '../common/decorators/index.js'; + +import { rebasableL2StethAbi } from './abi/rebasableL2Steth.js'; +import { parseValue } from '../common/utils/parse-value.js'; +import { + UnwrapResults, + WrapInnerProps, + WrapProps, + WrapPropsWithoutCallback, + WrapResults, +} from './types.js'; +import { PopulatedTransaction, TransactionResult } from '../core/types.js'; +import { invariant, ERROR_CODE } from '../common/index.js'; + +export class LidoSDKL2 extends LidoSDKModule { + private static TRANSFER_SIGNATURE = toEventHash( + getAbiItem({ abi: rebasableL2StethAbi, name: 'Transfer' }), + ); + + public static isContractAvailableOn( + contract: LIDO_L2_CONTRACT_NAMES, + chain: CHAINS, + ) { + return !!LIDO_L2_CONTRACT_ADDRESSES[chain]?.[contract]; + } + + public readonly wsteth: LidoSDKL2Wsteth; + public readonly steth: LidoSDKL2Steth; + + constructor(props: LidoSDKCommonProps) { + super(props); + this.wsteth = new LidoSDKL2Wsteth({ core: this.core }); + this.steth = new LidoSDKL2Steth({ core: this.core }); + } + + @Logger('Contracts:') + @Cache(30 * 60 * 1000, ['core.chain.id']) + public async contractAddress(): Promise
{ + return this.core.getL2ContractAddress(LIDO_L2_CONTRACT_NAMES.steth); + } + + @Logger('Contracts:') + @Cache(30 * 60 * 1000, ['core.chain.id']) + public async getContract(): Promise< + GetContractReturnType + > { + const address = await this.contractAddress(); + return getContract({ + address, + abi: rebasableL2StethAbi, + client: { + public: this.core.rpcProvider, + wallet: this.core.web3Provider as WalletClient, + }, + }); + } + + // Wrap wstETH to stETH + + @Logger('Call:') + @ErrorHandler() + public async approveWstethForWrap( + props: WrapProps, + ): Promise { + const stethAddress = await this.contractAddress(); + return this.wsteth.approve({ + ...props, + amount: props.value, + to: stethAddress, + }); + } + + @Logger('Utils:') + @ErrorHandler() + public async getWstethForWrapAllowance( + account?: AccountValue, + ): Promise { + const stethAddress = await this.contractAddress(); + return this.wsteth.allowance({ account, to: stethAddress }); + } + + @Logger('Call:') + @ErrorHandler() + public async wrapWstethToSteth( + props: WrapProps, + ): Promise> { + this.core.useWeb3Provider(); + const { account, callback, value, ...rest } = await this.parseProps(props); + const contract = await this.getContract(); + + return this.core.performTransaction({ + ...rest, + account, + callback, + getGasLimit: (options) => contract.estimateGas.wrap([value], options), + sendTransaction: (options) => contract.write.wrap([value], options), + decodeResult: (receipt) => + this.wrapWstethToStethParseEvents(receipt, account.address), + }); + } + + @Logger('Utils:') + public async wrapWstethToStethPopulateTx( + props: WrapPropsWithoutCallback, + ): Promise { + const { value, account } = await this.parseProps(props); + const address = await this.contractAddress(); + + return { + to: address, + from: account.address, + data: encodeFunctionData({ + abi: rebasableL2StethAbi, + functionName: 'wrap', + args: [value], + }), + }; + } + + @Logger('Utils:') + private async wrapWstethToStethParseEvents( + receipt: TransactionReceipt, + address: Address, + ): Promise { + let stethReceived: bigint | undefined; + let wstethWrapped: bigint | undefined; + for (const log of receipt.logs) { + // skips non-relevant events + if (log.topics[0] !== LidoSDKL2.TRANSFER_SIGNATURE) continue; + const parsedLog = decodeEventLog({ + abi: rebasableL2StethAbi, + strict: true, + eventName: 'Transfer', + data: log.data, + topics: log.topics, + }); + if (isAddressEqual(parsedLog.args.from, address)) { + wstethWrapped = parsedLog.args.value; + } else if (isAddressEqual(parsedLog.args.to, address)) { + stethReceived = parsedLog.args.value; + } + } + invariant( + stethReceived, + 'could not find Transfer event in wrap transaction', + ERROR_CODE.TRANSACTION_ERROR, + ); + invariant( + wstethWrapped, + 'could not find Transfer event in wrap transaction', + ERROR_CODE.TRANSACTION_ERROR, + ); + return { + stethReceived, + wstethWrapped, + }; + } + + @Logger('Call:') + @ErrorHandler() + public async wrapWstethToStethSimulateTx( + props: WrapPropsWithoutCallback, + ): Promise { + const { value, account } = await this.parseProps(props); + const contract = await this.getContract(); + + const { request } = await contract.simulate.wrap([value], { + account, + }); + + return request; + } + + // unwrap stETH to wstETH + + @Logger('Call:') + @ErrorHandler() + public async unwrapStethToWsteth( + props: WrapProps, + ): Promise> { + this.core.useWeb3Provider(); + const { account, callback, value, ...rest } = await this.parseProps(props); + const contract = await this.getContract(); + + return this.core.performTransaction({ + ...rest, + account, + callback, + getGasLimit: (options) => contract.estimateGas.unwrap([value], options), + sendTransaction: (options) => contract.write.unwrap([value], options), + decodeResult: (receipt) => + this.unwrapParseEvents(receipt, account.address), + }); + } + + @Logger('Utils:') + @ErrorHandler() + public async unwrapStethPopulateTx( + props: Omit, + ): Promise { + const { value, account } = await this.parseProps(props); + const to = await this.contractAddress(); + + return { + to, + from: account.address, + data: encodeFunctionData({ + abi: rebasableL2StethAbi, + functionName: 'unwrap', + args: [value], + }), + }; + } + + @Logger('Call:') + @ErrorHandler() + public async unwrapStethSimulateTx( + props: Omit, + ): Promise { + const { value, account } = await this.parseProps(props); + const contract = await this.getContract(); + + const { request } = await contract.simulate.unwrap([value], { + account, + }); + + return request; + } + + @Logger('Utils:') + private async unwrapParseEvents( + receipt: TransactionReceipt, + address: Address, + ): Promise { + let stethUnwrapped: bigint | undefined; + let wstethReceived: bigint | undefined; + for (const log of receipt.logs) { + // skips non-relevant events + if (log.topics[0] !== LidoSDKL2.TRANSFER_SIGNATURE) continue; + const parsedLog = decodeEventLog({ + abi: rebasableL2StethAbi, + strict: true, + topics: log.topics, + data: log.data, + eventName: 'Transfer', + }); + if (isAddressEqual(parsedLog.args.from, address)) { + stethUnwrapped = parsedLog.args.value; + } else if (isAddressEqual(parsedLog.args.to, address)) { + wstethReceived = parsedLog.args.value; + } + } + invariant( + stethUnwrapped, + 'could not find Transfer event in unwrap transaction', + ERROR_CODE.TRANSACTION_ERROR, + ); + invariant( + wstethReceived, + 'could not find Transfer event in unwrap transaction', + ERROR_CODE.TRANSACTION_ERROR, + ); + return { + stethUnwrapped, + wstethReceived, + }; + } + + // Utils + + @Logger('Utils:') + private async parseProps(props: WrapProps): Promise { + return { + ...props, + account: await this.core.useAccount(props.account), + value: parseValue(props.value), + callback: props.callback ?? NOOP, + }; + } +} diff --git a/packages/sdk/src/l2/tokens.ts b/packages/sdk/src/l2/tokens.ts new file mode 100644 index 00000000..d2ff086b --- /dev/null +++ b/packages/sdk/src/l2/tokens.ts @@ -0,0 +1,266 @@ +/* eslint-disable sonarjs/no-identical-functions */ +import { + getContract, + type GetContractReturnType, + type WalletClient, + type Address, + encodeFunctionData, + Hash, +} from 'viem'; + +import { CHAINS, LIDO_L2_CONTRACT_NAMES, NOOP } from '../common/constants.js'; +import { parseValue } from '../common/utils/parse-value.js'; +import { Cache, ErrorHandler, Logger } from '../common/decorators/index.js'; +import { AbstractLidoSDKErc20 } from '../erc20/erc20.js'; + +import { rebasableL2StethAbi } from './abi/rebasableL2Steth.js'; +import { bridgedWstethAbi } from './abi/brigedWsteth.js'; + +import type { + AccountValue, + EtherValue, + NoCallback, + TransactionOptions, + TransactionResult, +} from '../core/types.js'; +import type { SharesTransferProps } from './types.js'; + +export class LidoSDKL2Wsteth extends AbstractLidoSDKErc20 { + @Logger('Contracts:') + @Cache(30 * 60 * 1000, ['core.chain.id']) + public override async contractAddress(): Promise
{ + return this.core.getL2ContractAddress(LIDO_L2_CONTRACT_NAMES.wsteth); + } + + @Logger('Contracts:') + @Cache(30 * 60 * 1000, ['core.chain.id']) + public async getL2Contract(): Promise< + GetContractReturnType + > { + const address = await this.contractAddress(); + return getContract({ + address, + abi: bridgedWstethAbi, + client: { + public: this.core.rpcProvider, + wallet: this.core.web3Provider as WalletClient, + }, + }); + } + + @Cache(30 * 60 * 1000, ['core.chain.id']) + public override async erc721Domain(): Promise<{ + name: string; + version: string; + chainId: bigint; + verifyingContract: Address; + fields: Hash; + salt: Hash; + extensions: readonly bigint[]; + }> { + const contract = await this.getL2Contract(); + const [ + fields, + name, + version, + chainId, + verifyingContract, + salt, + extensions, + ] = await contract.read.eip712Domain(); + + // Testnet misconfiguration + const fixedVersion = + this.core.chainId === CHAINS.OptimismSepolia ? '1' : version; + + return { + fields, + name, + version: fixedVersion, + chainId, + verifyingContract, + salt, + extensions, + }; + } +} + +export class LidoSDKL2Steth extends AbstractLidoSDKErc20 { + public override async contractAddress(): Promise
{ + return this.core.getL2ContractAddress(LIDO_L2_CONTRACT_NAMES.steth); + } + + @Logger('Contracts:') + @Cache(30 * 60 * 1000, ['core.chain.id']) + public async getL2Contract(): Promise< + GetContractReturnType + > { + const address = await this.contractAddress(); + return getContract({ + address, + abi: rebasableL2StethAbi, + client: { + public: this.core.rpcProvider, + wallet: this.core.web3Provider as WalletClient, + }, + }); + } + + @Cache(30 * 60 * 1000, ['core.chain.id']) + public override async erc721Domain(): Promise<{ + name: string; + version: string; + chainId: bigint; + verifyingContract: Address; + fields: Hash; + salt: Hash; + extensions: readonly bigint[]; + }> { + const contract = await this.getL2Contract(); + const [ + fields, + name, + version, + chainId, + verifyingContract, + salt, + extensions, + ] = await contract.read.eip712Domain(); + return { + fields, + name, + version, + chainId, + verifyingContract, + salt, + extensions, + }; + } + + @Logger('Balances:') + @ErrorHandler() + public async balanceShares(address?: AccountValue): Promise { + const contract = await this.getL2Contract(); + const account = await this.core.useAccount(address); + return contract.read.sharesOf([account.address]); + } + + // convert + @Logger('Views:') + @ErrorHandler() + public async convertToShares(stethAmount: EtherValue): Promise { + const amount = parseValue(stethAmount); + const contract = await this.getL2Contract(); + return contract.read.getSharesByTokens([amount]); + } + + // convert + @Logger('Views:') + @ErrorHandler() + public async convertToSteth(sharesAmount: EtherValue): Promise { + const amount = parseValue(sharesAmount); + const contract = await this.getL2Contract(); + return contract.read.getTokensByShares([amount]); + } + + // convert + @Logger('Views:') + @ErrorHandler() + public async totalShares(): Promise { + const contract = await this.getL2Contract(); + return contract.read.getTotalShares(); + } + + // TransferShares + // Transfer + + @Logger('Call:') + @ErrorHandler() + public async transferShares({ + account: accountProp, + to, + amount: _amount, + callback = NOOP, + from: _from, + ...rest + }: SharesTransferProps): Promise { + this.core.useWeb3Provider(); + const account = await this.core.useAccount(accountProp); + const from = _from ?? account.address; + const amount = parseValue(_amount); + + const isTransferFrom = from !== account.address; + const contract = await this.getL2Contract(); + + const getGasLimit = async (overrides: TransactionOptions) => + isTransferFrom + ? contract.estimateGas.transferSharesFrom([from, to, amount], overrides) + : contract.estimateGas.transferShares([to, amount], overrides); + + const sendTransaction = async (overrides: TransactionOptions) => + isTransferFrom + ? contract.write.transferSharesFrom([from, to, amount], overrides) + : contract.write.transferShares([to, amount], overrides); + + return this.core.performTransaction({ + ...rest, + account, + callback, + getGasLimit, + sendTransaction, + }); + } + + @Logger('Utils:') + @ErrorHandler() + public async populateTransferShares({ + account: accountProp, + to, + amount: _amount, + from: _from, + }: NoCallback) { + const account = await this.core.useAccount(accountProp); + const amount = parseValue(_amount); + const from = _from ?? account.address; + const address = await this.contractAddress(); + const isTransferFrom = from !== account.address; + + return { + to: address, + from: account.address, + data: isTransferFrom + ? encodeFunctionData({ + abi: rebasableL2StethAbi, + functionName: 'transferSharesFrom', + args: [from, to, amount], + }) + : encodeFunctionData({ + abi: rebasableL2StethAbi, + functionName: 'transferShares', + args: [to, amount], + }), + }; + } + + @Logger('Utils:') + @ErrorHandler() + public async simulateTransferShares({ + account: _account, + to, + amount: _amount, + from: _from, + }: NoCallback) { + const amount = parseValue(_amount); + const account = await this.core.useAccount(_account); + const from = _from ?? account.address; + const contract = await this.getL2Contract(); + const isTransferFrom = from !== account.address; + return isTransferFrom + ? contract.simulate.transferSharesFrom([from, to, amount], { + account, + }) + : contract.simulate.transferShares([to, amount], { + account, + }); + } +} diff --git a/packages/sdk/src/l2/types.ts b/packages/sdk/src/l2/types.ts new file mode 100644 index 00000000..d10f2c61 --- /dev/null +++ b/packages/sdk/src/l2/types.ts @@ -0,0 +1,32 @@ +import type { Address } from 'viem'; +import type { CommonTransactionProps, EtherValue } from '../core/types.js'; +import type { FormattedTransactionRequest, JsonRpcAccount } from 'viem'; + +export type SharesTransferProps = CommonTransactionProps & { + from?: Address; + to: Address; + amount: EtherValue; +}; + +export type WrapProps = CommonTransactionProps & { + value: EtherValue; +}; + +export type WrapResults = { + stethReceived: bigint; + wstethWrapped: bigint; +}; + +export type UnwrapResults = { + stethUnwrapped: bigint; + wstethReceived: bigint; +}; + +export type WrapPropsWithoutCallback = Omit; + +export type WrapInnerProps = Omit & { + value: bigint; + account: JsonRpcAccount; +}; + +export type PopulatedTx = Omit; diff --git a/packages/sdk/src/rewards/rewards.ts b/packages/sdk/src/rewards/rewards.ts index ab76db91..83baeda1 100644 --- a/packages/sdk/src/rewards/rewards.ts +++ b/packages/sdk/src/rewards/rewards.ts @@ -66,7 +66,13 @@ export class LidoSDKRewards extends LidoSDKModule { @Logger('Contracts:') @Cache(30 * 60 * 1000, ['core.chain.id']) private earliestRebaseEventBlock(): bigint { - return EARLIEST_TOKEN_REBASED_EVENT[this.core.chainId]; + const block = EARLIEST_TOKEN_REBASED_EVENT[this.core.chainId]; + invariant( + block, + `No rebase event for chain:${this.core.chainId}`, + ERROR_CODE.NOT_SUPPORTED, + ); + return block; } @Logger('Contracts:') diff --git a/packages/sdk/src/sdk.ts b/packages/sdk/src/sdk.ts index 3caf4c52..5420c5a4 100644 --- a/packages/sdk/src/sdk.ts +++ b/packages/sdk/src/sdk.ts @@ -11,6 +11,7 @@ import { LidoSDKRewards } from './rewards/index.js'; import { LidoSDKShares } from './shares/shares.js'; import { version } from './version.js'; +import { LidoSDKL2 } from './l2/l2.js'; export class LidoSDK { readonly core: LidoSDKCore; @@ -24,6 +25,7 @@ export class LidoSDK { readonly events: LidoSDKEvents; readonly statistics: LidoSDKStatistics; readonly rewards: LidoSDKRewards; + readonly l2: LidoSDKL2; constructor(props: LidoSDKCoreProps) { // Core functionality @@ -46,5 +48,7 @@ export class LidoSDK { this.statistics = new LidoSDKStatistics({ ...props, core }); // Rewards functionality this.rewards = new LidoSDKRewards({ ...props, core }); + // L2 functionality + this.l2 = new LidoSDKL2({ ...props, core }); } } diff --git a/packages/sdk/src/shares/index.ts b/packages/sdk/src/shares/index.ts index 3861cdb8..ca4f495f 100644 --- a/packages/sdk/src/shares/index.ts +++ b/packages/sdk/src/shares/index.ts @@ -1,2 +1,3 @@ +export { stethSharesAbi } from './abi/steth-shares-abi.js'; export { LidoSDKShares } from './shares.js'; export type { SharesTransferProps } from './types.js'; diff --git a/packages/sdk/src/shares/shares.ts b/packages/sdk/src/shares/shares.ts index d3f46a00..677e0ce9 100644 --- a/packages/sdk/src/shares/shares.ts +++ b/packages/sdk/src/shares/shares.ts @@ -177,20 +177,28 @@ export class LidoSDKShares extends LidoSDKModule { address: sharesContract.address, abi: sharesContract.abi, }; - const [totalShares, totalEther] = await this.core.rpcProvider.multicall({ - allowFailure: false, - contracts: [ - { - ...contract, - functionName: 'getTotalShares', - }, - { - ...contract, - functionName: 'getTotalPooledEther', - }, - ] as const, - }); - return { totalEther, totalShares }; + if (this.core.rpcProvider.multicall) { + const [totalShares, totalEther] = await this.core.rpcProvider.multicall({ + allowFailure: false, + contracts: [ + { + ...contract, + functionName: 'getTotalShares', + }, + { + ...contract, + functionName: 'getTotalPooledEther', + }, + ] as const, + }); + return { totalEther, totalShares }; + } else { + const [totalShares, totalEther] = await Promise.all([ + sharesContract.read.getTotalShares(), + sharesContract.read.getTotalPooledEther(), + ]); + return { totalEther, totalShares }; + } } @Logger('Views:') diff --git a/packages/sdk/src/stake/index.ts b/packages/sdk/src/stake/index.ts index 61793c44..62531b9d 100644 --- a/packages/sdk/src/stake/index.ts +++ b/packages/sdk/src/stake/index.ts @@ -1,2 +1,3 @@ +export { StethAbi } from './abi/steth.js'; export { LidoSDKStake } from './stake.js'; export type { StakeProps, StakeEncodeDataProps } from './types.js'; diff --git a/packages/sdk/src/stake/types.ts b/packages/sdk/src/stake/types.ts index 11d34545..be9f00f7 100644 --- a/packages/sdk/src/stake/types.ts +++ b/packages/sdk/src/stake/types.ts @@ -7,7 +7,7 @@ export type StakeProps = CommonTransactionProps & { referralAddress?: Address; }; -export type StakeInnerProps = CommonTransactionProps & { +export type StakeInnerProps = Omit & { value: bigint; referralAddress: Address; account: JsonRpcAccount; diff --git a/packages/sdk/src/unsteth/index.ts b/packages/sdk/src/unsteth/index.ts index 4d0f8a4b..c36ca1f2 100644 --- a/packages/sdk/src/unsteth/index.ts +++ b/packages/sdk/src/unsteth/index.ts @@ -1,3 +1,4 @@ +export { unstethAbi } from './abi/unsteth-abi.js'; export { LidoSDKUnstETH } from './unsteth.js'; export type { UnstethNFT, diff --git a/packages/sdk/src/unsteth/unsteth.ts b/packages/sdk/src/unsteth/unsteth.ts index 4a0ddfaa..b540161d 100644 --- a/packages/sdk/src/unsteth/unsteth.ts +++ b/packages/sdk/src/unsteth/unsteth.ts @@ -281,36 +281,52 @@ export class LidoSDKUnstETH extends LidoSDKModule { @ErrorHandler() @Cache(30 * 60 * 1000, ['core.chain.id']) public async getContractMetadata() { - const address = await this.contractAddress(); - const common = { abi: unstethAbi, address } as const; - const [name, version, symbol, baseURI] = - await this.core.rpcProvider.multicall({ - allowFailure: false, - contracts: [ - { - ...common, - functionName: 'name', - }, - { - ...common, - functionName: 'getContractVersion', - }, - { - ...common, - functionName: 'symbol', - }, - { - ...common, - functionName: 'getBaseURI', - }, - ] as const, - }); - return { - name, - version, - symbol, - baseURI, - }; + if (this.core.rpcProvider.multicall) { + const address = await this.contractAddress(); + const common = { abi: unstethAbi, address } as const; + const [name, version, symbol, baseURI] = + await this.core.rpcProvider.multicall({ + allowFailure: false, + contracts: [ + { + ...common, + functionName: 'name', + }, + { + ...common, + functionName: 'getContractVersion', + }, + { + ...common, + functionName: 'symbol', + }, + { + ...common, + functionName: 'getBaseURI', + }, + ] as const, + }); + return { + name, + version, + symbol, + baseURI, + }; + } else { + const contract = await this.getContract(); + const [name, version, symbol, baseURI] = await Promise.all([ + contract.read.name(), + contract.read.getContractVersion(), + contract.read.symbol(), + contract.read.getBaseURI(), + ]); + return { + name, + version, + symbol, + baseURI, + }; + } } @Logger('Views:') diff --git a/packages/sdk/src/withdraw/index.ts b/packages/sdk/src/withdraw/index.ts index 5b2ce111..54a9e2e0 100644 --- a/packages/sdk/src/withdraw/index.ts +++ b/packages/sdk/src/withdraw/index.ts @@ -1,3 +1,4 @@ +export { WithdrawalQueueAbi } from './abi/withdrawalQueue.js'; export { LidoSDKWithdraw } from './withdraw.js'; export type { ClaimRequestsProps, diff --git a/packages/sdk/src/withdraw/withdraw-waiting-time.ts b/packages/sdk/src/withdraw/withdraw-waiting-time.ts index 4727674f..7ef8a829 100644 --- a/packages/sdk/src/withdraw/withdraw-waiting-time.ts +++ b/packages/sdk/src/withdraw/withdraw-waiting-time.ts @@ -97,7 +97,7 @@ export class LidoSDKWithdrawWaitingTime extends BusModule { const baseUrl = getCustomApiUrl && typeof getCustomApiUrl === 'function' - ? getCustomApiUrl(defaultUrl, this.bus.core.chainId) + ? getCustomApiUrl(defaultUrl ?? null, this.bus.core.chainId) : defaultUrl; if (!baseUrl) { diff --git a/packages/sdk/src/wrap/index.ts b/packages/sdk/src/wrap/index.ts index fe4d762b..8f666594 100644 --- a/packages/sdk/src/wrap/index.ts +++ b/packages/sdk/src/wrap/index.ts @@ -1,2 +1,3 @@ +export { abi as WstethABI } from './abi/wsteth.js'; export { LidoSDKWrap } from './wrap.js'; export type { WrapProps, WrapPropsWithoutCallback } from './types.js'; diff --git a/packages/sdk/tests/global-setup.cjs b/packages/sdk/tests/global-setup.cjs index 3ce86ca9..fc42f123 100644 --- a/packages/sdk/tests/global-setup.cjs +++ b/packages/sdk/tests/global-setup.cjs @@ -2,49 +2,64 @@ const path = require('path'); const dotenv = require('dotenv'); const ganache = require('ganache'); -module.exports = async function () { - dotenv.config({ - path: path.resolve(process.cwd(), '.env'), - }); - - const rpcUrl = process.env.TEST_RPC_URL; - const chainId = Number(process.env.TEST_CHAIN_ID); - +const setupGanacheProvider = async (chainId, rpcUrl) => { const ganacheProvider = ganache.provider({ fork: { url: rpcUrl }, logging: { quiet: true }, chain: { chainId, asyncRequestProcessing: true }, }); - - console.debug('\nInitializing ganache provider...'); + console.debug(`\n[${chainId}]Initializing ganache provider...`); await ganacheProvider.initialize(); - console.debug('Initialized ganache provider OK'); + console.debug(`[${chainId}]Initialized ganache provider OK`); - console.debug('Testing direct RPC provider...'); + console.debug(`[${chainId}]Testing direct RPC provider...`); const { result } = await fetch(rpcUrl, { method: 'POST', body: JSON.stringify({ method: 'eth_chainId', params: [], + id: 1, + jsonrpc: '2.0', }), headers: { 'Content-Type': 'application/json', }, }).then((response) => response.json()); - if (Number(result) !== chainId) { - throw new Error(`Invalid direct RPC provider response: ${result}`); + if (parseInt(result, 16) !== chainId) { + throw new Error( + `[${chainId}]Invalid direct RPC provider response: ${result}`, + ); } - console.debug('Direct RPC provider OK'); + console.debug(`[${chainId}]Direct RPC provider OK`); - console.debug('Testing ganache fork RPC provider...'); + console.debug(`[${chainId}]Testing ganache fork RPC provider...`); const testRequest = await ganacheProvider.request({ method: 'eth_chainId', params: [], }); - if (Number(testRequest) !== chainId) { - throw new Error(`Invalid ganache response: ${testRequest}`); + if (parseInt(testRequest, 16) !== chainId) { + throw new Error(`[${chainId}]Invalid ganache response: ${testRequest}`); } - console.debug('Ganache fork RPC provider OK'); + console.debug(`[${chainId}]Ganache fork RPC provider OK`); + return ganacheProvider; +}; + +module.exports = async function () { + dotenv.config({ + path: path.resolve(process.cwd(), '.env'), + }); + + // L1 + const chainId = Number(process.env.TEST_CHAIN_ID); + const rpcUrl = process.env.TEST_RPC_URL; + globalThis.__ganache_provider__ = await setupGanacheProvider(chainId, rpcUrl); + + // L2 + const l2RpcUrl = process.env.TEST_L2_RPC_URL; + const l2ChainId = Number(process.env.TEST_L2_CHAIN_ID); - globalThis.__ganache_provider__ = ganacheProvider; + globalThis.__l2_ganache_provider__ = await setupGanacheProvider( + l2ChainId, + l2RpcUrl, + ); }; diff --git a/packages/sdk/tests/global-teardown.cjs b/packages/sdk/tests/global-teardown.cjs index 2413a5b6..460e8d3c 100644 --- a/packages/sdk/tests/global-teardown.cjs +++ b/packages/sdk/tests/global-teardown.cjs @@ -4,4 +4,9 @@ module.exports = async function () { await globalThis.__ganache_provider__.disconnect(); console.debug('Disconnected ganache provider'); } + if (globalThis.__l2_ganache_provider__) { + console.debug('Disconnecting L2 ganache provider...'); + await globalThis.__l2_ganache_provider__.disconnect(); + console.debug('Disconnected L2 ganache provider'); + } }; diff --git a/packages/sdk/tests/utils/expect/expect-erc20-wallet.ts b/packages/sdk/tests/utils/expect/expect-erc20-wallet.ts index bb6d703a..e4e039e4 100644 --- a/packages/sdk/tests/utils/expect/expect-erc20-wallet.ts +++ b/packages/sdk/tests/utils/expect/expect-erc20-wallet.ts @@ -1,8 +1,9 @@ -import { encodeFunctionData, getContract } from 'viem'; +import { encodeFunctionData, getContract, maxUint256 } from 'viem'; import { expect, describe, test, jest } from '@jest/globals'; import { AbstractLidoSDKErc20 } from '../../../src/erc20/erc20.js'; import { LIDO_CONTRACT_NAMES, + LIDO_L2_CONTRACT_NAMES, PERMIT_MESSAGE_TYPES, } from '../../../src/index.js'; import { @@ -51,10 +52,12 @@ export const expectERC20Wallet = ({ contractName, constructedWithRpcCore, constructedWithWeb3Core, + isL2 = false, }: { - contractName: LIDO_CONTRACT_NAMES; + contractName: LIDO_CONTRACT_NAMES | LIDO_L2_CONTRACT_NAMES; constructedWithRpcCore: I; constructedWithWeb3Core: I; + isL2?: boolean; }) => { const token = constructedWithWeb3Core; const tokenRpc = constructedWithRpcCore; @@ -62,11 +65,15 @@ export const expectERC20Wallet = ({ const rpcCore = tokenRpc.core; const getTokenAddress = async () => { - const address = - await constructedWithRpcCore.core.getContractAddress(contractName); + const address = await (isL2 + ? constructedWithRpcCore.core.getL2ContractAddress( + contractName as LIDO_L2_CONTRACT_NAMES, + ) + : constructedWithRpcCore.core.getContractAddress( + contractName as LIDO_CONTRACT_NAMES, + )); return address; }; - const getTokenContract = async () => { const address = await getTokenAddress(); return getContract({ @@ -268,37 +275,51 @@ export const expectERC20Wallet = ({ describe('permit', () => { test('signPermit', async () => { const { address } = useAccount(); + const altAccount = useAltAccount(); const contract = await getTokenContract(); const nonce = await contract.read.nonces([address]); const chainId = token.core.chainId; const params = { amount: 100n, - spender: address, - deadline: 86400n, + spender: altAccount.address, + deadline: maxUint256, }; - const tx = await token.signPermit(params); + const signedPermit = await token.signPermit(params); - expect(tx).toHaveProperty('v'); - expect(tx).toHaveProperty('r'); - expect(tx).toHaveProperty('s'); - expect(tx).toHaveProperty('chainId'); + expect(signedPermit).toHaveProperty('v'); + expect(signedPermit).toHaveProperty('r'); + expect(signedPermit).toHaveProperty('s'); + expect(signedPermit).toHaveProperty('chainId'); - expect(typeof tx.v).toBe('number'); - expect(typeof tx.r).toBe('string'); - expect(typeof tx.s).toBe('string'); - expect(tx.chainId).toBe(BigInt(chainId)); + expect(typeof signedPermit.v).toBe('number'); + expect(typeof signedPermit.r).toBe('string'); + expect(typeof signedPermit.s).toBe('string'); + expect(signedPermit.chainId).toBe(BigInt(chainId)); - const { v, r, s, chainId: _, ...permitMessage } = tx; + const { v, r, s, chainId: _, ...permitMessage } = signedPermit; expectPermitMessage(permitMessage, { address: address, - spender: address, + spender: altAccount.address, amount: params.amount, nonce, deadline: params.deadline, }); + + await contract.simulate.permit( + [ + permitMessage.owner, + permitMessage.spender, + permitMessage.value, + permitMessage.deadline, + v, + r, + s, + ], + { account: altAccount, maxFeePerGas: 0n, maxPriorityFeePerGas: 0n }, + ); }); testSpending('populatePermit', async () => { @@ -327,6 +348,7 @@ export const expectERC20Wallet = ({ expect(tx.domain).toMatchObject(domain); expect(tx.types).toBe(PERMIT_MESSAGE_TYPES); expect(tx.primaryType).toBe('Permit'); + expectPermitMessage(tx.message, { address: address, spender: address, diff --git a/packages/sdk/tests/utils/expect/expect-erc20.ts b/packages/sdk/tests/utils/expect/expect-erc20.ts index ea80600a..20a098f8 100644 --- a/packages/sdk/tests/utils/expect/expect-erc20.ts +++ b/packages/sdk/tests/utils/expect/expect-erc20.ts @@ -1,7 +1,10 @@ import { getContract } from 'viem'; import { expect, describe, test } from '@jest/globals'; import { AbstractLidoSDKErc20 } from '../../../src/erc20/erc20.js'; -import { LIDO_CONTRACT_NAMES } from '../../../src/index.js'; +import { + LIDO_CONTRACT_NAMES, + LIDO_L2_CONTRACT_NAMES, +} from '../../../src/index.js'; import { expectAddress } from '../../../tests/utils/expect/expect-address.js'; import { expectContract } from '../../../tests/utils/expect/expect-contract.js'; import { useAccount } from '../../../tests/utils/fixtures/use-wallet-client.js'; @@ -10,25 +13,34 @@ import { expectBn } from '../../../tests/utils/expect/expect-bn.js'; import { expectSDKModule } from '../../../tests/utils/expect/expect-sdk-module.js'; import { LidoSDKCommonProps } from '../../../src/core/types.js'; +type expectERC20Options = { + contractName: LIDO_CONTRACT_NAMES | LIDO_L2_CONTRACT_NAMES; + constructedWithRpcCore: I; + constructedWithWeb3Core: I; + ModulePrototype: new (props: LidoSDKCommonProps) => I; + isL2?: boolean; +}; + export const expectERC20 = ({ contractName, constructedWithRpcCore, constructedWithWeb3Core, ModulePrototype, -}: { - contractName: LIDO_CONTRACT_NAMES; - constructedWithRpcCore: I; - constructedWithWeb3Core: I; - ModulePrototype: new (props: LidoSDKCommonProps) => I; -}) => { + isL2 = false, +}: expectERC20Options) => { const token = constructedWithWeb3Core; const tokenRpc = constructedWithRpcCore; const web3Core = token.core; const rpcCore = tokenRpc.core; const getTokenAddress = async () => { - const address = - await constructedWithRpcCore.core.getContractAddress(contractName); + const address = await (isL2 + ? constructedWithRpcCore.core.getL2ContractAddress( + contractName as LIDO_L2_CONTRACT_NAMES, + ) + : constructedWithRpcCore.core.getContractAddress( + contractName as LIDO_CONTRACT_NAMES, + )); return address; }; diff --git a/packages/sdk/tests/utils/fixtures/use-l2.ts b/packages/sdk/tests/utils/fixtures/use-l2.ts new file mode 100644 index 00000000..9230d7d9 --- /dev/null +++ b/packages/sdk/tests/utils/fixtures/use-l2.ts @@ -0,0 +1,110 @@ +import type { EthereumProvider } from 'ganache'; +import { + createTestClient, + createWalletClient, + custom, + PrivateKeyAccount, + publicActions, + PublicClient, + TestClient, +} from 'viem'; +import { useTestsEnvs } from './use-test-envs.js'; +import { CHAINS, LidoSDKCore, VIEM_CHAINS } from '../../../src/index.js'; +import { useAccount } from './use-wallet-client.js'; +import { LidoSDKL2 } from '../../../src/l2/l2.js'; + +let cached: { + testClient: TestClient<'ganache'>; + ganacheProvider: EthereumProvider; +} | null = null; + +export const useTestL2RpcProvider = () => { + if (cached) return cached; + const { l2ChainId } = useTestsEnvs(); + + const ganacheProvider = (globalThis as any) + .__l2_ganache_provider__ as EthereumProvider; + + const testClient = createTestClient({ + mode: 'ganache', + transport: custom({ + async request(args) { + if (args.method === 'eth_estimateGas') { + delete args.params[0].gas; + } + return ganacheProvider.request(args); + }, + }), + name: 'testClient', + chain: VIEM_CHAINS[l2ChainId as CHAINS], + }); + + cached = { ganacheProvider, testClient }; + return cached; +}; + +let cachedPublicProvider: PublicClient | null = null; + +export const usePublicL2RpcProvider = () => { + if (cachedPublicProvider) return cachedPublicProvider; + const { testClient } = useTestL2RpcProvider(); + const rpcProvider = testClient.extend(publicActions) as PublicClient; + cachedPublicProvider = rpcProvider; + return rpcProvider; +}; + +export const useL2WalletClient = (_account?: PrivateKeyAccount) => { + const { l2ChainId } = useTestsEnvs(); + const { testClient } = useTestL2RpcProvider(); + const account = _account ?? useAccount(); + + const chain = VIEM_CHAINS[l2ChainId as CHAINS]; + + return createWalletClient({ + account, + chain, + transport: custom({ request: testClient.request }), + }); +}; + +let cachedWeb3Core: LidoSDKCore | null = null; + +export const useL2Web3Core = () => { + if (!cachedWeb3Core) { + const walletClient = useL2WalletClient(); + const { l2ChainId } = useTestsEnvs(); + const rpcProvider = usePublicL2RpcProvider(); + cachedWeb3Core = new LidoSDKCore({ + chainId: l2ChainId, + rpcProvider: rpcProvider, + logMode: 'none', + web3Provider: walletClient, + }); + } + return cachedWeb3Core; +}; + +let cachedRpcCore: LidoSDKCore | null = null; + +export const useL2RpcCore = () => { + if (!cachedRpcCore) { + const { l2ChainId } = useTestsEnvs(); + const rpcProvider = usePublicL2RpcProvider(); + cachedRpcCore = new LidoSDKCore({ + chainId: l2ChainId, + rpcProvider: rpcProvider, + logMode: 'none', + }); + } + return cachedRpcCore; +}; + +export const useL2Rpc = () => { + const rpcCore = useL2RpcCore(); + return new LidoSDKL2({ core: rpcCore }); +}; + +export const useL2 = () => { + const web3Core = useL2Web3Core(); + return new LidoSDKL2({ core: web3Core }); +}; diff --git a/packages/sdk/tests/utils/fixtures/use-test-envs.ts b/packages/sdk/tests/utils/fixtures/use-test-envs.ts index a01827de..4d0b968e 100644 --- a/packages/sdk/tests/utils/fixtures/use-test-envs.ts +++ b/packages/sdk/tests/utils/fixtures/use-test-envs.ts @@ -3,6 +3,8 @@ export const useTestsEnvs = () => { privateKey: process.env.TEST_PRIVATE_KEY as string, rpcUrl: process.env.TEST_RPC_URL as string, chainId: Number(process.env.TEST_CHAIN_ID), + l2RpcUrl: process.env.TEST_L2_RPC_URL as string, + l2ChainId: Number(process.env.TEST_L2_CHAIN_ID), skipSpendingTests: process.env.TEST_SKIP_SPENDING_TESTS == 'true', subgraphUrl: process.env.TEST_SUBGRAPH_URL, }; diff --git a/playground/components/layout/header/headerWallet.tsx b/playground/components/layout/header/headerWallet.tsx index 1bd09c0b..b4526ba8 100644 --- a/playground/components/layout/header/headerWallet.tsx +++ b/playground/components/layout/header/headerWallet.tsx @@ -1,5 +1,5 @@ import { FC } from 'react'; -import { CHAINS, getChainColor } from '@lido-sdk/constants'; +import { getChainColor } from '@lido-sdk/constants'; import { useWeb3 } from 'reef-knot/web3-react'; import { ThemeToggler } from '@lidofinance/lido-ui'; @@ -9,15 +9,27 @@ import WalletConnect from 'components/layout/header/walletConnect'; import { HeaderWalletChainStyle } from './headerWalletStyles'; +import { useChains } from 'wagmi'; + +const tryGetColor = (chainId: number) => { + try { + return getChainColor(chainId); + } catch { + return '#FFFFFF'; + } +}; + const HeaderWallet: FC = () => { const { active, chainId } = useWeb3(); - const chainName = chainId && CHAINS[chainId]; + const chains = useChains(); + + const currentChain = chains.find((chain) => chain.id === chainId); return ( <> {chainId && ( - - {chainName} + + {currentChain?.name} )} {active ? : } diff --git a/playground/demo/core/index.tsx b/playground/demo/core/index.tsx index a8fe0408..ea916ac4 100644 --- a/playground/demo/core/index.tsx +++ b/playground/demo/core/index.tsx @@ -20,6 +20,7 @@ export const CoreDemo = () => { const dateAtTimestamp = new Date( Number(timestampSeconds) * 1000, ).toLocaleString(locale); + const getStethContract = useCallback(async () => { const address = await stake.contractAddressStETH(); setContractAddress(address); diff --git a/playground/demo/index.tsx b/playground/demo/index.tsx index 09a1dc95..9f264e0d 100644 --- a/playground/demo/index.tsx +++ b/playground/demo/index.tsx @@ -13,8 +13,13 @@ import { EventsDemo } from './events'; import { StatisticsDemo } from './statistics'; import { RewardsDemo } from './rewards'; import { ShareDemo } from './shares'; +import { useChainId } from 'wagmi'; +import { L2_CHAINS } from 'providers/web3'; +import { L2 } from './l2'; export const Demo = () => { + const chain = useChainId(); + if (L2_CHAINS.includes(chain)) return ; return ( <> diff --git a/playground/demo/l2/index.tsx b/playground/demo/l2/index.tsx new file mode 100644 index 00000000..46edd4de --- /dev/null +++ b/playground/demo/l2/index.tsx @@ -0,0 +1,12 @@ +import { StethL2Demo, WstethL2Demo } from './tokens'; +import { WrapL2Demo } from './wrap-l2'; + +export const L2 = () => { + return ( + <> + + + + + ); +}; diff --git a/playground/demo/l2/tokens.tsx b/playground/demo/l2/tokens.tsx new file mode 100644 index 00000000..618e2ed7 --- /dev/null +++ b/playground/demo/l2/tokens.tsx @@ -0,0 +1,16 @@ +import { TokenDemo } from 'demo/tokens'; +import { useLidoSDK } from 'providers/sdk'; + +export const WstethL2Demo = () => { + const { l2 } = useLidoSDK(); + return ; +}; + +export const StethL2Demo = () => { + const { l2 } = useLidoSDK(); + return ( + <> + + + ); +}; diff --git a/playground/demo/l2/wrap-l2.tsx b/playground/demo/l2/wrap-l2.tsx new file mode 100644 index 00000000..54b4e3d8 --- /dev/null +++ b/playground/demo/l2/wrap-l2.tsx @@ -0,0 +1,124 @@ +import { Accordion } from '@lidofinance/lido-ui'; + +import { Action, renderTokenResult } from 'components/action'; +import { DEFAULT_VALUE, ValueType } from 'components/tokenInput'; +import TokenInput from 'components/tokenInput/tokenInput'; +import { useLidoSDK } from 'providers/sdk'; +import { useState } from 'react'; +import { transactionToast } from 'utils/transaction-toast'; + +const ZERO = BigInt(0); + +export const WrapL2Demo = () => { + const [wstethValue, setWstethValue] = useState(DEFAULT_VALUE); + const [stethValue, setStethValue] = useState(DEFAULT_VALUE); + + const { l2 } = useLidoSDK(); + + return ( + + l2.wsteth.balance()} + /> + l2.steth.balance()} + /> + l2.getWstethForWrapAllowance()} + /> + + l2.approveWstethForWrap({ + value: wstethValue ?? ZERO, + + callback: transactionToast, + }) + } + > + + + + l2.wrapWstethToSteth({ + value: wstethValue ?? ZERO, + + callback: transactionToast, + }) + } + /> + + l2.wrapWstethToStethPopulateTx({ + value: wstethValue ?? ZERO, + }) + } + /> + + l2.wrapWstethToStethSimulateTx({ + value: wstethValue ?? ZERO, + }) + } + /> + + + l2.unwrapStethToWsteth({ + value: stethValue ?? ZERO, + + callback: transactionToast, + }) + } + > + + + + l2.unwrapStethPopulateTx({ + value: stethValue ?? ZERO, + }) + } + /> + + l2.unwrapStethSimulateTx({ + value: stethValue ?? ZERO, + }) + } + /> + + ); +}; diff --git a/playground/demo/tokens/index.tsx b/playground/demo/tokens/index.tsx index 8df64b0b..670ec9b1 100644 --- a/playground/demo/tokens/index.tsx +++ b/playground/demo/tokens/index.tsx @@ -25,7 +25,7 @@ export const WstethDemo = () => { return ; }; -const TokenDemo = ({ instance, name }: TokenDemoProps) => { +export const TokenDemo = ({ instance, name }: TokenDemoProps) => { const { account: web3account = '0x0' } = useWeb3(); const [transferAmountState, setTransferAmount] = useState(DEFAULT_VALUE); diff --git a/playground/package.json b/playground/package.json index c11a821a..3cab4ac1 100644 --- a/playground/package.json +++ b/playground/package.json @@ -34,8 +34,8 @@ "styled-components": "^5.3.5", "swr": "^1.3.0", "tiny-invariant": "^1.3.1", - "viem": "^2.0.6", - "wagmi": "2.11.2" + "viem": "^2.21.9", + "wagmi": "2.12.12" }, "devDependencies": { "@next/bundle-analyzer": "^13.4.19", diff --git a/playground/providers/sdk.tsx b/playground/providers/sdk.tsx index fd0dd51c..3704a598 100644 --- a/playground/providers/sdk.tsx +++ b/playground/providers/sdk.tsx @@ -2,7 +2,7 @@ import { createContext, useMemo, PropsWithChildren, useContext } from 'react'; import { LidoSDK } from '@lidofinance/lido-ethereum-sdk'; import invariant from 'tiny-invariant'; -import { useClient, useConnectorClient } from 'wagmi'; +import { usePublicClient, useWalletClient } from 'wagmi'; const context = createContext(null); @@ -13,9 +13,9 @@ export const useLidoSDK = () => { }; export const LidoSDKProvider: React.FC = ({ children }) => { - const publicClient = useClient(); + const publicClient = usePublicClient(); const chainId = publicClient?.chain.id; - const { data: walletClient } = useConnectorClient(); + const { data: walletClient } = useWalletClient(); const value = useMemo(() => { const sdk = new LidoSDK({ chainId: chainId as any, diff --git a/playground/providers/web3.tsx b/playground/providers/web3.tsx index 99c4fffc..b246f0f4 100644 --- a/playground/providers/web3.tsx +++ b/playground/providers/web3.tsx @@ -24,6 +24,8 @@ import { useThemeToggle } from '@lidofinance/lido-ui'; type ChainsList = [wagmiChains.Chain, ...wagmiChains.Chain[]]; +export const L2_CHAINS = [10, 11155420]; + const wagmiChainsArray = Object.values(wagmiChains) as any as ChainsList; const supportedChains = wagmiChainsArray.filter((chain) => @@ -63,6 +65,8 @@ const Web3Provider: FC = ({ children }) => { [CHAINS.Goerli]: getRpc(CHAINS.Goerli), [CHAINS.Holesky]: getRpc(CHAINS.Holesky), [CHAINS.Sepolia]: getRpc(CHAINS.Sepolia), + // OP sepolia + [11155420]: getRpc(11155420), }; }, [customRpc]); diff --git a/yarn.lock b/yarn.lock index b9d50ced..f54ddb5a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5097,7 +5097,8 @@ __metadata: rimraf: ^5.0.1 ts-jest: ^29.1.2 typescript: ^5.4.5 - viem: ^2.0.6 + peerDependencies: + viem: ^2.21 languageName: unknown linkType: soft @@ -5117,7 +5118,7 @@ __metadata: rimraf: ^5.0.5 ts-node: ^10.9.2 typescript: 5.1.6 - viem: ^2.0.6 + viem: ^2.21.9 languageName: unknown linkType: soft @@ -5363,9 +5364,9 @@ __metadata: languageName: node linkType: hard -"@metamask/sdk-communication-layer@npm:0.26.4": - version: 0.26.4 - resolution: "@metamask/sdk-communication-layer@npm:0.26.4" +"@metamask/sdk-communication-layer@npm:0.28.2": + version: 0.28.2 + resolution: "@metamask/sdk-communication-layer@npm:0.28.2" dependencies: bufferutil: ^4.0.8 date-fns: ^2.29.3 @@ -5378,13 +5379,13 @@ __metadata: eventemitter2: ^6.4.7 readable-stream: ^3.6.2 socket.io-client: ^4.5.1 - checksum: a355826fb3b91af1e52e6afb53590441765b4b47c06047d5e9330e90ab8c9e576f5a12a9ed72782de274632c6751326c3b7d895c5b9b0c85f2008510d2a382fb + checksum: 349103ca72018fc4077ddf3d84d3976572525cf27b17b65308c6a00710c66f06d2d3880bb611a4a6462d1fb54bc59edd6855826f78796f49d8c7cd9904742577 languageName: node linkType: hard -"@metamask/sdk-install-modal-web@npm:0.26.5": - version: 0.26.5 - resolution: "@metamask/sdk-install-modal-web@npm:0.26.5" +"@metamask/sdk-install-modal-web@npm:0.28.1": + version: 0.28.1 + resolution: "@metamask/sdk-install-modal-web@npm:0.28.1" dependencies: qr-code-styling: ^1.6.0-rc.1 peerDependencies: @@ -5399,19 +5400,20 @@ __metadata: optional: true react-native: optional: true - checksum: 3aba8d39bac0d320727b7abfcd803d843906962cea28685e946acba3f198b14d0a672ac8dfd6ec97a7d0efd4f92d00dae956af8872ca4cb2e677f91342bc58b0 + checksum: 8ee147c63927323105bdf7d76667c06618119b30b355543a74f3a08e7559448d217bdf9a4fee0900efa0fc3f5a13f6376a76b2679e0b8322f6811789868dce42 languageName: node linkType: hard -"@metamask/sdk@npm:0.26.5": - version: 0.26.5 - resolution: "@metamask/sdk@npm:0.26.5" +"@metamask/sdk@npm:0.28.4": + version: 0.28.4 + resolution: "@metamask/sdk@npm:0.28.4" dependencies: "@metamask/onboarding": ^1.0.1 "@metamask/providers": 16.1.0 - "@metamask/sdk-communication-layer": 0.26.4 - "@metamask/sdk-install-modal-web": 0.26.5 + "@metamask/sdk-communication-layer": 0.28.2 + "@metamask/sdk-install-modal-web": 0.28.1 "@types/dom-screen-wake-lock": ^1.0.0 + "@types/uuid": ^10.0.0 bowser: ^2.9.0 cross-fetch: ^4.0.0 debug: ^4.3.4 @@ -5437,7 +5439,7 @@ __metadata: optional: true react-dom: optional: true - checksum: 63d53041a44669fd3110647a485e77e43038806c4548d86716e973c22b9f6ad019499beb9419cb8b08f243828aa3753485ba121d89169154462c90828f13f07f + checksum: 419880a65c09a340dac6ca756ef81193dbc441db6fab620a3bd610fa077b11b2dec96f4651e81aac49c266ac00dbd109ffc184e067738ded7ea325174be94f31 languageName: node linkType: hard @@ -5667,15 +5669,6 @@ __metadata: languageName: node linkType: hard -"@noble/curves@npm:1.2.0, @noble/curves@npm:~1.2.0": - version: 1.2.0 - resolution: "@noble/curves@npm:1.2.0" - dependencies: - "@noble/hashes": 1.3.2 - checksum: bb798d7a66d8e43789e93bc3c2ddff91a1e19fdb79a99b86cd98f1e5eff0ee2024a2672902c2576ef3577b6f282f3b5c778bebd55761ddbb30e36bf275e83dd0 - languageName: node - linkType: hard - "@noble/curves@npm:1.3.0, @noble/curves@npm:~1.3.0": version: 1.3.0 resolution: "@noble/curves@npm:1.3.0" @@ -5694,6 +5687,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:^1.4.0": + version: 1.6.0 + resolution: "@noble/curves@npm:1.6.0" + dependencies: + "@noble/hashes": 1.5.0 + checksum: 258f3feb2a6098cf35521562ecb7d452fd728e8a008ff9f1ef435184f9d0c782ceb8f7b7fa8df3317c3be7a19f53995ee124cd05c8080b130bd42e3cb072f24d + languageName: node + linkType: hard + "@noble/curves@npm:~1.4.0": version: 1.4.2 resolution: "@noble/curves@npm:1.4.2" @@ -5703,14 +5705,7 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:1.3.2": - version: 1.3.2 - resolution: "@noble/hashes@npm:1.3.2" - checksum: fe23536b436539d13f90e4b9be843cc63b1b17666a07634a2b1259dded6f490be3d050249e6af98076ea8f2ea0d56f578773c2197f2aa0eeaa5fba5bc18ba474 - languageName: node - linkType: hard - -"@noble/hashes@npm:1.3.3, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:~1.3.0, @noble/hashes@npm:~1.3.2": +"@noble/hashes@npm:1.3.3, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:~1.3.2": version: 1.3.3 resolution: "@noble/hashes@npm:1.3.3" checksum: 8a6496d1c0c64797339bc694ad06cdfaa0f9e56cd0c3f68ae3666cfb153a791a55deb0af9c653c7ed2db64d537aa3e3054629740d2f2338bb1dcb7ab60cd205b @@ -5724,6 +5719,13 @@ __metadata: languageName: node linkType: hard +"@noble/hashes@npm:1.5.0, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:~1.5.0": + version: 1.5.0 + resolution: "@noble/hashes@npm:1.5.0" + checksum: 9cc031d5c888c455bfeef76af649b87f75380a4511405baea633c1e4912fd84aff7b61e99716f0231d244c9cfeda1fafd7d718963e6a0c674ed705e9b1b4f76b + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -6688,7 +6690,7 @@ __metadata: languageName: node linkType: hard -"@scure/base@npm:^1.1.3, @scure/base@npm:~1.1.0, @scure/base@npm:~1.1.2, @scure/base@npm:~1.1.4": +"@scure/base@npm:^1.1.3, @scure/base@npm:~1.1.4": version: 1.1.5 resolution: "@scure/base@npm:1.1.5" checksum: 9e9ee6088cb3aa0fb91f5a48497d26682c7829df3019b1251d088d166d7a8c0f941c68aaa8e7b96bbad20c71eb210397cb1099062cde3e29d4bad6b975c18519 @@ -6702,14 +6704,10 @@ __metadata: languageName: node linkType: hard -"@scure/bip32@npm:1.3.2": - version: 1.3.2 - resolution: "@scure/bip32@npm:1.3.2" - dependencies: - "@noble/curves": ~1.2.0 - "@noble/hashes": ~1.3.2 - "@scure/base": ~1.1.2 - checksum: c5ae84fae43490853693b481531132b89e056d45c945fc8b92b9d032577f753dfd79c5a7bbcbf0a7f035951006ff0311b6cf7a389e26c9ec6335e42b20c53157 +"@scure/base@npm:~1.1.8": + version: 1.1.9 + resolution: "@scure/base@npm:1.1.9" + checksum: 120820a37dfe9dfe4cab2b7b7460552d08e67dee8057ed5354eb68d8e3440890ae983ce3bee957d2b45684950b454a2b6d71d5ee77c1fd3fddc022e2a510337f languageName: node linkType: hard @@ -6735,16 +6733,6 @@ __metadata: languageName: node linkType: hard -"@scure/bip39@npm:1.2.1": - version: 1.2.1 - resolution: "@scure/bip39@npm:1.2.1" - dependencies: - "@noble/hashes": ~1.3.0 - "@scure/base": ~1.1.0 - checksum: c5bd6f1328fdbeae2dcdd891825b1610225310e5e62a4942714db51066866e4f7bef242c7b06a1b9dcc8043a4a13412cf5c5df76d3b10aa9e36b82e9b6e3eeaa - languageName: node - linkType: hard - "@scure/bip39@npm:1.2.2": version: 1.2.2 resolution: "@scure/bip39@npm:1.2.2" @@ -6765,6 +6753,16 @@ __metadata: languageName: node linkType: hard +"@scure/bip39@npm:1.4.0": + version: 1.4.0 + resolution: "@scure/bip39@npm:1.4.0" + dependencies: + "@noble/hashes": ~1.5.0 + "@scure/base": ~1.1.8 + checksum: 211f2c01361993bfe54c0e4949f290224381457c7f76d7cd51d6a983f3f4b6b9f85adfd0e623977d777ed80417a5fe729eb19dd34e657147810a0e58a8e7b9e0 + languageName: node + linkType: hard + "@semantic-release/commit-analyzer@npm:^10.0.0": version: 10.0.4 resolution: "@semantic-release/commit-analyzer@npm:10.0.4" @@ -8357,6 +8355,13 @@ __metadata: languageName: node linkType: hard +"@types/uuid@npm:^10.0.0": + version: 10.0.0 + resolution: "@types/uuid@npm:10.0.0" + checksum: e3958f8b0fe551c86c14431f5940c3470127293280830684154b91dc7eb3514aeb79fe3216968833cf79d4d1c67f580f054b5be2cd562bebf4f728913e73e944 + languageName: node + linkType: hard + "@types/ws@npm:^8.5.5": version: 8.5.10 resolution: "@types/ws@npm:8.5.10" @@ -8573,31 +8578,31 @@ __metadata: languageName: node linkType: hard -"@wagmi/connectors@npm:5.0.26": - version: 5.0.26 - resolution: "@wagmi/connectors@npm:5.0.26" +"@wagmi/connectors@npm:5.1.11": + version: 5.1.11 + resolution: "@wagmi/connectors@npm:5.1.11" dependencies: "@coinbase/wallet-sdk": 4.0.4 - "@metamask/sdk": 0.26.5 + "@metamask/sdk": 0.28.4 "@safe-global/safe-apps-provider": 0.18.3 "@safe-global/safe-apps-sdk": 9.1.0 - "@walletconnect/ethereum-provider": 2.13.0 + "@walletconnect/ethereum-provider": 2.16.1 "@walletconnect/modal": 2.6.2 cbw-sdk: "npm:@coinbase/wallet-sdk@3.9.3" peerDependencies: - "@wagmi/core": 2.12.2 + "@wagmi/core": 2.13.5 typescript: ">=5.0.4" viem: 2.x peerDependenciesMeta: typescript: optional: true - checksum: 286c641c912fb2d4620fa867d920d79f5730372ae49f26eff5484b36a1d21f03c984fcde9d85d4f2c732f2891bebc579e3d7613702b9ced8426cc7f10627c118 + checksum: c05beff4aa98dced7ba070dc2a2f9144f48026c19725c1e9fe04c843c4add0a4935de0aa8fddb8cb9d4cf2454d6504bfcfb2fa3bd6e5bb166e9b8d13eddfa087 languageName: node linkType: hard -"@wagmi/core@npm:2.12.2": - version: 2.12.2 - resolution: "@wagmi/core@npm:2.12.2" +"@wagmi/core@npm:2.13.5": + version: 2.13.5 + resolution: "@wagmi/core@npm:2.13.5" dependencies: eventemitter3: 5.0.1 mipd: 0.0.7 @@ -8611,13 +8616,13 @@ __metadata: optional: true typescript: optional: true - checksum: 06d3fde29b53474805460474158dfe7e3d6356dc11cf8f90033a9edae605cbe56839da7cbc20bb82e72d1ee68cf7b02ef493aa5d4f7041b8247017e89879b52e + checksum: e0fce057b613a36e35f43ceaaea3f300d92e37a53590d767cc4934035e0fff36dfe5c8ec64486fc78b3a11abcb10b0ac0b65f369ea0034afb5932572cb6f1187 languageName: node linkType: hard -"@walletconnect/core@npm:2.13.0": - version: 2.13.0 - resolution: "@walletconnect/core@npm:2.13.0" +"@walletconnect/core@npm:2.16.1": + version: 2.16.1 + resolution: "@walletconnect/core@npm:2.16.1" dependencies: "@walletconnect/heartbeat": 1.2.2 "@walletconnect/jsonrpc-provider": 1.0.14 @@ -8626,17 +8631,16 @@ __metadata: "@walletconnect/jsonrpc-ws-connection": 1.0.14 "@walletconnect/keyvaluestorage": 1.1.1 "@walletconnect/logger": 2.1.2 - "@walletconnect/relay-api": 1.0.10 + "@walletconnect/relay-api": 1.0.11 "@walletconnect/relay-auth": 1.0.4 "@walletconnect/safe-json": 1.0.2 "@walletconnect/time": 1.0.2 - "@walletconnect/types": 2.13.0 - "@walletconnect/utils": 2.13.0 + "@walletconnect/types": 2.16.1 + "@walletconnect/utils": 2.16.1 events: 3.3.0 - isomorphic-unfetch: 3.1.0 lodash.isequal: 4.5.0 uint8arrays: 3.1.0 - checksum: 6e503bdc7d678ccaeaa9d93fdc6311298d326febef87f233b80c12340178ae3eff54a3a79e19400d65298f109466c508dbef0d5710fffd09d357b7b6bec8b56f + checksum: ab658833fb845624ccb2d109c5218f5b4624b948b4a8b01bf22eb964c5e5d3cd890ee43645e196ad73d1be9a25d6a1bcd16a83893266ee1b30c9ec5377086820 languageName: node linkType: hard @@ -8649,21 +8653,21 @@ __metadata: languageName: node linkType: hard -"@walletconnect/ethereum-provider@npm:2.13.0": - version: 2.13.0 - resolution: "@walletconnect/ethereum-provider@npm:2.13.0" +"@walletconnect/ethereum-provider@npm:2.16.1": + version: 2.16.1 + resolution: "@walletconnect/ethereum-provider@npm:2.16.1" dependencies: "@walletconnect/jsonrpc-http-connection": 1.0.8 "@walletconnect/jsonrpc-provider": 1.0.14 "@walletconnect/jsonrpc-types": 1.0.4 "@walletconnect/jsonrpc-utils": 1.0.8 "@walletconnect/modal": 2.6.2 - "@walletconnect/sign-client": 2.13.0 - "@walletconnect/types": 2.13.0 - "@walletconnect/universal-provider": 2.13.0 - "@walletconnect/utils": 2.13.0 + "@walletconnect/sign-client": 2.16.1 + "@walletconnect/types": 2.16.1 + "@walletconnect/universal-provider": 2.16.1 + "@walletconnect/utils": 2.16.1 events: 3.3.0 - checksum: 24356a41b72fea5125ef0e6605df4469f023141ce5de8cc92f1ae23b35215efb0ee2c1e5857f483f34eccd4a051915b64518daadc4c8a2145bf91473c2f5a7bc + checksum: f88c6e41b7718a75452795efa7a1b51f5286aa728358a6b5f477e26f9c37e6cf5df1ec69fa227fe67674be53c2871296d767becbc23fc287ca6298a12d2c4984 languageName: node linkType: hard @@ -8811,12 +8815,12 @@ __metadata: languageName: node linkType: hard -"@walletconnect/relay-api@npm:1.0.10": - version: 1.0.10 - resolution: "@walletconnect/relay-api@npm:1.0.10" +"@walletconnect/relay-api@npm:1.0.11": + version: 1.0.11 + resolution: "@walletconnect/relay-api@npm:1.0.11" dependencies: "@walletconnect/jsonrpc-types": ^1.0.2 - checksum: a332cbfdf0d3bad7046b0559653a5121a4b5a540f029cc01eeb8ef466681b10626a5a24d55668405e7c635535f35b8038d4aa5a2f0d16c8b512c41fecff2448c + checksum: 9fcddf055de01c04b9fa59035e8c6e31d523743c848d266f528009048aeadaa1b4d9b544bdcb6928e7a69f738d5f0352d1cdebbaa34b1346b937942cb5f6f144 languageName: node linkType: hard @@ -8843,20 +8847,20 @@ __metadata: languageName: node linkType: hard -"@walletconnect/sign-client@npm:2.13.0": - version: 2.13.0 - resolution: "@walletconnect/sign-client@npm:2.13.0" +"@walletconnect/sign-client@npm:2.16.1": + version: 2.16.1 + resolution: "@walletconnect/sign-client@npm:2.16.1" dependencies: - "@walletconnect/core": 2.13.0 + "@walletconnect/core": 2.16.1 "@walletconnect/events": 1.0.1 "@walletconnect/heartbeat": 1.2.2 "@walletconnect/jsonrpc-utils": 1.0.8 "@walletconnect/logger": 2.1.2 "@walletconnect/time": 1.0.2 - "@walletconnect/types": 2.13.0 - "@walletconnect/utils": 2.13.0 + "@walletconnect/types": 2.16.1 + "@walletconnect/utils": 2.16.1 events: 3.3.0 - checksum: d8516d5bc7f554962651d59af36c13716da35216e78a92b4ab2632d6c2e65dccc9fda83e5ef8ceaab3195c2436cdd038ff7ed1176b25c57142f823735a5f987c + checksum: e25808e2fbfc01cff47391281e7cb050927cde2e83f5eec640e5be52c8adc9ee50bd70447a2e72defc9cad3d3e3009ef48ac7d1b694dafa5d6a62e046863b58a languageName: node linkType: hard @@ -8869,9 +8873,9 @@ __metadata: languageName: node linkType: hard -"@walletconnect/types@npm:2.13.0": - version: 2.13.0 - resolution: "@walletconnect/types@npm:2.13.0" +"@walletconnect/types@npm:2.16.1": + version: 2.16.1 + resolution: "@walletconnect/types@npm:2.16.1" dependencies: "@walletconnect/events": 1.0.1 "@walletconnect/heartbeat": 1.2.2 @@ -8879,46 +8883,48 @@ __metadata: "@walletconnect/keyvaluestorage": 1.1.1 "@walletconnect/logger": 2.1.2 events: 3.3.0 - checksum: 868e12449026154c5a8945359ab03c2f2dd7dd329e631fea721e8399928823b93585013784253d787daf184adb76de6bccd76525679b4c87fd830300c70275d4 + checksum: 9ea47bfb0d5db8f0e440e040d55b05b4932aa3f56e976d42290e831c39d4bc5f2d4cd400f64ca86d8a6a195e34d6370f8db878f7b339ff7cac60a12bbfd9e445 languageName: node linkType: hard -"@walletconnect/universal-provider@npm:2.13.0": - version: 2.13.0 - resolution: "@walletconnect/universal-provider@npm:2.13.0" +"@walletconnect/universal-provider@npm:2.16.1": + version: 2.16.1 + resolution: "@walletconnect/universal-provider@npm:2.16.1" dependencies: "@walletconnect/jsonrpc-http-connection": 1.0.8 "@walletconnect/jsonrpc-provider": 1.0.14 "@walletconnect/jsonrpc-types": 1.0.4 "@walletconnect/jsonrpc-utils": 1.0.8 "@walletconnect/logger": 2.1.2 - "@walletconnect/sign-client": 2.13.0 - "@walletconnect/types": 2.13.0 - "@walletconnect/utils": 2.13.0 + "@walletconnect/sign-client": 2.16.1 + "@walletconnect/types": 2.16.1 + "@walletconnect/utils": 2.16.1 events: 3.3.0 - checksum: 3eb26d07bebbebe67e7f1e666d7b37cbdb6513a807262b9fd9026e8340238bc715b80f99d81127939aa53ff9f9027f903d9828e649e9f6c3c1e536c557b0840d + checksum: 2852ff1b1b06628bf08015ade517f73b743f11873f56e4358063668fd13c290adc648274cc965b34789f2c91879ec338135b42c01e466a2ef76e82ccec74400e languageName: node linkType: hard -"@walletconnect/utils@npm:2.13.0": - version: 2.13.0 - resolution: "@walletconnect/utils@npm:2.13.0" +"@walletconnect/utils@npm:2.16.1": + version: 2.16.1 + resolution: "@walletconnect/utils@npm:2.16.1" dependencies: "@stablelib/chacha20poly1305": 1.0.1 "@stablelib/hkdf": 1.0.1 "@stablelib/random": 1.0.2 "@stablelib/sha256": 1.0.1 "@stablelib/x25519": 1.0.3 - "@walletconnect/relay-api": 1.0.10 + "@walletconnect/relay-api": 1.0.11 + "@walletconnect/relay-auth": 1.0.4 "@walletconnect/safe-json": 1.0.2 "@walletconnect/time": 1.0.2 - "@walletconnect/types": 2.13.0 + "@walletconnect/types": 2.16.1 "@walletconnect/window-getters": 1.0.1 "@walletconnect/window-metadata": 1.0.1 detect-browser: 5.3.0 + elliptic: ^6.5.7 query-string: 7.1.3 uint8arrays: 3.1.0 - checksum: ab3c008aa72e573d67f342042e62c04e4aa779bde94f850de53f7bda31a4458665b39af2e33ae6ee6f237aa19f55cef542c75cabbe647218c02075700d2c713f + checksum: 404c5f262e020c208ab30283c1dbe23f7a4876d3d89ebb23dde95ea32deb8ada72886d64151f6a826d21774797fa44feed70d33729661aa0de4b6850b3ace0d5 languageName: node linkType: hard @@ -9148,21 +9154,6 @@ __metadata: languageName: node linkType: hard -"abitype@npm:1.0.0": - version: 1.0.0 - resolution: "abitype@npm:1.0.0" - peerDependencies: - typescript: ">=5.0.4" - zod: ^3 >=3.22.0 - peerDependenciesMeta: - typescript: - optional: true - zod: - optional: true - checksum: ea2c0548c3ba58c37a6de7483d63389074da498e63d803b742bbe94eb4eaa1f51a35d000c424058b2583aef56698cf07c696eb3bc4dd0303bc20c6f0826a241a - languageName: node - linkType: hard - "abitype@npm:1.0.5": version: 1.0.5 resolution: "abitype@npm:1.0.5" @@ -10239,9 +10230,9 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:1.20.2": - version: 1.20.2 - resolution: "body-parser@npm:1.20.2" +"body-parser@npm:1.20.3": + version: 1.20.3 + resolution: "body-parser@npm:1.20.3" dependencies: bytes: 3.1.2 content-type: ~1.0.5 @@ -10251,11 +10242,11 @@ __metadata: http-errors: 2.0.0 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.11.0 + qs: 6.13.0 raw-body: 2.5.2 type-is: ~1.6.18 unpipe: 1.0.0 - checksum: 14d37ec638ab5c93f6099ecaed7f28f890d222c650c69306872e00b9efa081ff6c596cd9afb9930656aae4d6c4e1c17537bea12bb73c87a217cb3cfea8896737 + checksum: 1a35c59a6be8d852b00946330141c4f142c6af0f970faa87f10ad74f1ee7118078056706a05ae3093c54dabca9cd3770fa62a170a85801da1a4324f04381167d languageName: node linkType: hard @@ -13016,6 +13007,21 @@ __metadata: languageName: node linkType: hard +"elliptic@npm:^6.5.7": + version: 6.5.7 + resolution: "elliptic@npm:6.5.7" + dependencies: + bn.js: ^4.11.9 + brorand: ^1.1.0 + hash.js: ^1.0.0 + hmac-drbg: ^1.0.1 + inherits: ^2.0.4 + minimalistic-assert: ^1.0.1 + minimalistic-crypto-utils: ^1.0.1 + checksum: af0ffddffdbc2fea4eeec74388cd73e62ed5a0eac6711568fb28071566319785df529c968b0bf1250ba4bc628e074b2d64c54a633e034aa6f0c6b152ceb49ab8 + languageName: node + linkType: hard + "emittery@npm:0.10.0": version: 0.10.0 resolution: "emittery@npm:0.10.0" @@ -13079,6 +13085,13 @@ __metadata: languageName: node linkType: hard +"encodeurl@npm:~2.0.0": + version: 2.0.0 + resolution: "encodeurl@npm:2.0.0" + checksum: abf5cd51b78082cf8af7be6785813c33b6df2068ce5191a40ca8b1afe6a86f9230af9a9ce694a5ce4665955e5c1120871826df9c128a642e09c58d592e2807fe + languageName: node + linkType: hard + "encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" @@ -13184,7 +13197,7 @@ __metadata: resolution: "erlang-bridge-examples@workspace:examples/erlang-bridge" dependencies: "@lidofinance/lido-ethereum-sdk": "workspace:*" - viem: ^2.0.6 + viem: ^2.21.9 languageName: unknown linkType: soft @@ -14065,41 +14078,41 @@ __metadata: linkType: hard "express@npm:^4.17.3": - version: 4.19.2 - resolution: "express@npm:4.19.2" + version: 4.21.0 + resolution: "express@npm:4.21.0" dependencies: accepts: ~1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.2 + body-parser: 1.20.3 content-disposition: 0.5.4 content-type: ~1.0.4 cookie: 0.6.0 cookie-signature: 1.0.6 debug: 2.6.9 depd: 2.0.0 - encodeurl: ~1.0.2 + encodeurl: ~2.0.0 escape-html: ~1.0.3 etag: ~1.8.1 - finalhandler: 1.2.0 + finalhandler: 1.3.1 fresh: 0.5.2 http-errors: 2.0.0 - merge-descriptors: 1.0.1 + merge-descriptors: 1.0.3 methods: ~1.1.2 on-finished: 2.4.1 parseurl: ~1.3.3 - path-to-regexp: 0.1.7 + path-to-regexp: 0.1.10 proxy-addr: ~2.0.7 - qs: 6.11.0 + qs: 6.13.0 range-parser: ~1.2.1 safe-buffer: 5.2.1 - send: 0.18.0 - serve-static: 1.15.0 + send: 0.19.0 + serve-static: 1.16.2 setprototypeof: 1.2.0 statuses: 2.0.1 type-is: ~1.6.18 utils-merge: 1.0.1 vary: ~1.1.2 - checksum: 212dbd6c2c222a96a61bc927639c95970a53b06257080bb9e2838adb3bffdb966856551fdad1ab5dd654a217c35db94f987d0aa88d48fb04d306340f5f34dca5 + checksum: 1c5212993f665809c249bf00ab550b989d1365a5b9171cdfaa26d93ee2ef10cd8add520861ec8d5da74b3194d8374e1d9d53e85ef69b89fd9c4196b87045a5d4 languageName: node linkType: hard @@ -14422,18 +14435,18 @@ __metadata: languageName: node linkType: hard -"finalhandler@npm:1.2.0": - version: 1.2.0 - resolution: "finalhandler@npm:1.2.0" +"finalhandler@npm:1.3.1": + version: 1.3.1 + resolution: "finalhandler@npm:1.3.1" dependencies: debug: 2.6.9 - encodeurl: ~1.0.2 + encodeurl: ~2.0.0 escape-html: ~1.0.3 on-finished: 2.4.1 parseurl: ~1.3.3 statuses: 2.0.1 unpipe: ~1.0.0 - checksum: 92effbfd32e22a7dff2994acedbd9bcc3aa646a3e919ea6a53238090e87097f8ef07cced90aa2cc421abdf993aefbdd5b00104d55c7c5479a8d00ed105b45716 + checksum: a8c58cd97c9cd47679a870f6833a7b417043f5a288cd6af6d0f49b476c874a506100303a128b6d3b654c3d74fa4ff2ffed68a48a27e8630cda5c918f2977dcf4 languageName: node linkType: hard @@ -14448,13 +14461,13 @@ __metadata: linkType: hard "find-my-way@npm:^8.0.0": - version: 8.2.0 - resolution: "find-my-way@npm:8.2.0" + version: 8.2.2 + resolution: "find-my-way@npm:8.2.2" dependencies: fast-deep-equal: ^3.1.3 fast-querystring: ^1.0.0 safe-regex2: ^3.1.0 - checksum: 4f59fe17a1431511ec172403da0d1ac05bf9efebfdd4c7149b658d748b2570b63d798847e08ceea00f57543611fdb64ba3793dfc67a9ed7b5bfa0d77c8693eb5 + checksum: bba4feafece3ef012180e561d904ead38e1f2e089bd97dbcb2c628d38cb869918626c673b08b698d22ef46265a886aa535a650574801ecab9c8eb8db5437736a languageName: node linkType: hard @@ -16836,25 +16849,6 @@ __metadata: languageName: node linkType: hard -"isomorphic-unfetch@npm:3.1.0": - version: 3.1.0 - resolution: "isomorphic-unfetch@npm:3.1.0" - dependencies: - node-fetch: ^2.6.1 - unfetch: ^4.2.0 - checksum: 82b92fe4ec2823a81ab0fc0d11bd94d710e6f9a940d56b3cba31896d4345ec9ffc7949f4ff31ebcae84f6b95f7ebf3474c4c7452b834eb4078ea3f2c37e459c5 - languageName: node - linkType: hard - -"isows@npm:1.0.3": - version: 1.0.3 - resolution: "isows@npm:1.0.3" - peerDependencies: - ws: "*" - checksum: 9cacd5cf59f67deb51e825580cd445ab1725ecb05a67c704050383fb772856f3cd5e7da8ad08f5a3bd2823680d77d099459d0c6a7037972a74d6429af61af440 - languageName: node - linkType: hard - "isows@npm:1.0.4": version: 1.0.4 resolution: "isows@npm:1.0.4" @@ -18096,8 +18090,8 @@ __metadata: tiny-invariant: ^1.3.1 typescript: ^5.4.5 url-loader: ^4.1.1 - viem: ^2.0.6 - wagmi: 2.11.2 + viem: ^2.21.9 + wagmi: 2.12.12 languageName: unknown linkType: soft @@ -19018,10 +19012,10 @@ __metadata: languageName: node linkType: hard -"merge-descriptors@npm:1.0.1": - version: 1.0.1 - resolution: "merge-descriptors@npm:1.0.1" - checksum: 5abc259d2ae25bb06d19ce2b94a21632583c74e2a9109ee1ba7fd147aa7362b380d971e0251069f8b3eb7d48c21ac839e21fa177b335e82c76ec172e30c31a26 +"merge-descriptors@npm:1.0.3": + version: 1.0.3 + resolution: "merge-descriptors@npm:1.0.3" + checksum: 52117adbe0313d5defa771c9993fe081e2d2df9b840597e966aadafde04ae8d0e3da46bac7ca4efc37d4d2b839436582659cd49c6a43eacb3fe3050896a105d1 languageName: node linkType: hard @@ -20416,7 +20410,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.12": +"node-fetch@npm:^2.6.12": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -21496,10 +21490,10 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:0.1.7": - version: 0.1.7 - resolution: "path-to-regexp@npm:0.1.7" - checksum: 69a14ea24db543e8b0f4353305c5eac6907917031340e5a8b37df688e52accd09e3cebfe1660b70d76b6bd89152f52183f28c74813dbf454ba1a01c82a38abce +"path-to-regexp@npm:0.1.10": + version: 0.1.10 + resolution: "path-to-regexp@npm:0.1.10" + checksum: ab7a3b7a0b914476d44030340b0a65d69851af2a0f33427df1476100ccb87d409c39e2182837a96b98fb38c4ef2ba6b87bdad62bb70a2c153876b8061760583c languageName: node linkType: hard @@ -22584,12 +22578,12 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.11.0": - version: 6.11.0 - resolution: "qs@npm:6.11.0" +"qs@npm:6.13.0": + version: 6.13.0 + resolution: "qs@npm:6.13.0" dependencies: - side-channel: ^1.0.4 - checksum: 6e1f29dd5385f7488ec74ac7b6c92f4d09a90408882d0c208414a34dd33badc1a621019d4c799a3df15ab9b1d0292f97c1dd71dc7c045e69f81a8064e5af7297 + side-channel: ^1.0.6 + checksum: e9404dc0fc2849245107108ce9ec2766cde3be1b271de0bf1021d049dc5b98d1a2901e67b431ac5509f865420a7ed80b7acb3980099fe1c118a1c5d2e1432ad8 languageName: node linkType: hard @@ -23675,7 +23669,7 @@ __metadata: "@lidofinance/lido-ethereum-sdk": "workspace:*" rimraf: ^5.0.5 typescript: 5.1.6 - viem: ^2.0.6 + viem: ^2.21.9 languageName: unknown linkType: soft @@ -24077,9 +24071,9 @@ __metadata: languageName: node linkType: hard -"send@npm:0.18.0": - version: 0.18.0 - resolution: "send@npm:0.18.0" +"send@npm:0.19.0": + version: 0.19.0 + resolution: "send@npm:0.19.0" dependencies: debug: 2.6.9 depd: 2.0.0 @@ -24094,7 +24088,7 @@ __metadata: on-finished: 2.4.1 range-parser: ~1.2.1 statuses: 2.0.1 - checksum: 74fc07ebb58566b87b078ec63e5a3e41ecd987e4272ba67b7467e86c6ad51bc6b0b0154133b6d8b08a2ddda360464f71382f7ef864700f34844a76c8027817a8 + checksum: 5ae11bd900c1c2575525e2aa622e856804e2f96a09281ec1e39610d089f53aa69e13fd8db84b52f001d0318cf4bb0b3b904ad532fc4c0014eb90d32db0cff55f languageName: node linkType: hard @@ -24138,15 +24132,15 @@ __metadata: languageName: node linkType: hard -"serve-static@npm:1.15.0": - version: 1.15.0 - resolution: "serve-static@npm:1.15.0" +"serve-static@npm:1.16.2": + version: 1.16.2 + resolution: "serve-static@npm:1.16.2" dependencies: - encodeurl: ~1.0.2 + encodeurl: ~2.0.0 escape-html: ~1.0.3 parseurl: ~1.3.3 - send: 0.18.0 - checksum: af57fc13be40d90a12562e98c0b7855cf6e8bd4c107fe9a45c212bf023058d54a1871b1c89511c3958f70626fff47faeb795f5d83f8cf88514dbaeb2b724464d + send: 0.19.0 + checksum: dffc52feb4cc5c68e66d0c7f3c1824d4e989f71050aefc9bd5f822a42c54c9b814f595fc5f2b717f4c7cc05396145f3e90422af31186a93f76cf15f707019759 languageName: node linkType: hard @@ -24286,6 +24280,18 @@ __metadata: languageName: node linkType: hard +"side-channel@npm:^1.0.6": + version: 1.0.6 + resolution: "side-channel@npm:1.0.6" + dependencies: + call-bind: ^1.0.7 + es-errors: ^1.3.0 + get-intrinsic: ^1.2.4 + object-inspect: ^1.13.1 + checksum: bfc1afc1827d712271453e91b7cd3878ac0efd767495fd4e594c4c2afaa7963b7b510e249572bfd54b0527e66e4a12b61b80c061389e129755f34c493aad9b97 + languageName: node + linkType: hard + "signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" @@ -26019,13 +26025,6 @@ __metadata: languageName: node linkType: hard -"unfetch@npm:^4.2.0": - version: 4.2.0 - resolution: "unfetch@npm:4.2.0" - checksum: 6a4b2557e1d921eaa80c4425ce27a404945ec26491ed06e62598f333996a91a44c7908cb26dc7c2746d735762b13276cf4aa41829b4c8f438dde63add3045d7a - languageName: node - linkType: hard - "unicode-canonical-property-names-ecmascript@npm:^2.0.0": version: 2.0.0 resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.0" @@ -26648,54 +26647,55 @@ __metadata: languageName: node linkType: hard -"viem@npm:^2.0.6": - version: 2.7.9 - resolution: "viem@npm:2.7.9" +"viem@npm:^2.1.1": + version: 2.17.11 + resolution: "viem@npm:2.17.11" dependencies: "@adraffy/ens-normalize": 1.10.0 - "@noble/curves": 1.2.0 - "@noble/hashes": 1.3.2 - "@scure/bip32": 1.3.2 - "@scure/bip39": 1.2.1 - abitype: 1.0.0 - isows: 1.0.3 - ws: 8.13.0 + "@noble/curves": 1.4.0 + "@noble/hashes": 1.4.0 + "@scure/bip32": 1.4.0 + "@scure/bip39": 1.3.0 + abitype: 1.0.5 + isows: 1.0.4 + ws: 8.17.1 peerDependencies: typescript: ">=5.0.4" peerDependenciesMeta: typescript: optional: true - checksum: 71ff08ca18506a7217f1f87f44ea84ba2dcdb81f7d5a35670701aefbb05512892f4e32c80c9ca9584db119749d53576f37802290da6234fb1196d4c0c28beed5 + checksum: 7eefb347d0f717a6bc0f286ef82ecb6b52c9ff6e257205d470c3d92b4e2443cbb8a28a7fe0a49b3155a9f7c884eb9242c3cd56f3db4ab112cc0c97f591342f6d languageName: node linkType: hard -"viem@npm:^2.1.1": - version: 2.17.11 - resolution: "viem@npm:2.17.11" +"viem@npm:^2.21.9": + version: 2.21.9 + resolution: "viem@npm:2.21.9" dependencies: "@adraffy/ens-normalize": 1.10.0 "@noble/curves": 1.4.0 "@noble/hashes": 1.4.0 "@scure/bip32": 1.4.0 - "@scure/bip39": 1.3.0 + "@scure/bip39": 1.4.0 abitype: 1.0.5 isows: 1.0.4 + webauthn-p256: 0.0.5 ws: 8.17.1 peerDependencies: typescript: ">=5.0.4" peerDependenciesMeta: typescript: optional: true - checksum: 7eefb347d0f717a6bc0f286ef82ecb6b52c9ff6e257205d470c3d92b4e2443cbb8a28a7fe0a49b3155a9f7c884eb9242c3cd56f3db4ab112cc0c97f591342f6d + checksum: de6b9789a86cd217ce62833db80bcf3b5a6fbca3a8ef29d74083a5822c8a9c5fabcfecd050a5c5f0aa476eac02164b57a7aef3334030f93b649d4b52040f9332 languageName: node linkType: hard -"wagmi@npm:2.11.2": - version: 2.11.2 - resolution: "wagmi@npm:2.11.2" +"wagmi@npm:2.12.12": + version: 2.12.12 + resolution: "wagmi@npm:2.12.12" dependencies: - "@wagmi/connectors": 5.0.26 - "@wagmi/core": 2.12.2 + "@wagmi/connectors": 5.1.11 + "@wagmi/core": 2.13.5 use-sync-external-store: 1.2.0 peerDependencies: "@tanstack/react-query": ">=5.0.0" @@ -26705,7 +26705,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 5fb5d9434261de1760dc0ba5798627e993f1e80f3ec64c351b42ee7b4f0230c6391194f2ba91dca9ed2452dfb8628215dfdbffda73238bef3cbc65c500f8c964 + checksum: b4d2f8ba66f7ff6228400c0759df0f0f0f5b282081af7b22261c5468ef0eaf676427d1319c6bd7efe14ab5c1fefcfcdf8d2ec3cc1bc68e0f23454ba8c1b85c31 languageName: node linkType: hard @@ -26777,6 +26777,16 @@ __metadata: languageName: node linkType: hard +"webauthn-p256@npm:0.0.5": + version: 0.0.5 + resolution: "webauthn-p256@npm:0.0.5" + dependencies: + "@noble/curves": ^1.4.0 + "@noble/hashes": ^1.4.0 + checksum: 2837188d1e6d947c87c5728374fb6aec96387cb766f78e7a04d5903774264feb278d68a639748f09997f591e5278796c662bb5c4e8b8286b0f22254694023584 + languageName: node + linkType: hard + "webextension-polyfill@npm:>=0.10.0 <1.0": version: 0.12.0 resolution: "webextension-polyfill@npm:0.12.0"