diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e6e70537..869bbadf 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -18,6 +18,7 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 + persist-credentials: false - name: Setup Node uses: actions/setup-node@v3 diff --git a/package.json b/package.json index 51d14589..d822d973 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "scripts": { "dev": "yarn playground:dev", "w-info": "yarn workspaces info", - "build": "yarn workspaces foreach -pt run build", - "build:packages": "yarn workspaces foreach --no-private run build", + "build": "yarn workspaces foreach -pt run build", + "build:packages": "yarn workspaces foreach --no-private run build", "types": "yarn workspaces foreach -pt run types", "test": "yarn workspaces foreach -pt run test", "prepare": "husky install" diff --git a/packages/sdk/README.md b/packages/sdk/README.md index 6d60360f..e5bfa490 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -444,7 +444,7 @@ const callback: RequestStageCallback = ({ stage, payload }) => { }; try { - const requestResult = await lidoSDK.withdrawals.request({ + const requestResult = await lidoSDK.withdrawals.request.requestByToken({ amount, requests, token, // 'stETH' | 'wstETH' @@ -510,7 +510,7 @@ const callback: RequestStageCallback = ({ stage, payload }) => { }; try { - const requestResult = await lidoSDK.withdrawals.requestWithoutPermit({ + const requestResult = await lidoSDK.withdrawals.request.requestWithoutPermit({ requests, token, // 'stETH' | 'wstETH' callback, diff --git a/packages/sdk/src/core/types.ts b/packages/sdk/src/core/types.ts index db514f37..d0661c6c 100644 --- a/packages/sdk/src/core/types.ts +++ b/packages/sdk/src/core/types.ts @@ -27,6 +27,7 @@ export type LidoSDKCoreProps = | LidoSDKCorePropsRpcProvider; export enum TransactionCallbackStage { + 'PERMIT' = 'permit', 'GAS_LIMIT' = 'gas_limit', 'SIGN' = 'sign', 'RECEIPT' = 'receipt', @@ -37,8 +38,9 @@ export enum TransactionCallbackStage { } export type TransactionCallbackProps = + | { stage: TransactionCallbackStage.PERMIT; payload?: undefined } | { stage: TransactionCallbackStage.GAS_LIMIT; payload?: undefined } - | { stage: TransactionCallbackStage.SIGN; payload?: undefined } + | { stage: TransactionCallbackStage.SIGN; payload?: bigint } | { stage: TransactionCallbackStage.RECEIPT; payload: Hash } | { stage: TransactionCallbackStage.CONFIRMATION; diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index cbefb092..5ff8f6cf 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -1,13 +1,17 @@ export { LidoSDK } from './sdk.js'; export { type SDKError } from './common/utils/SDKError.js'; export * from './common/decorators/index.js'; -export { StakeProps } from './staking/index.js'; export { - RequestCallbackStages, - RequestStageCallback, - RequestProps, - RequestWithPermitProps, + TransactionCallbackStage, + type TransactionCallback, +} from './core/index.js'; +export { type StakeProps } from './staking/index.js'; +export { + type ClaimRequestsProps, + type RequestProps, + type RequestWithPermitProps, ApproveCallbackStages, - ApproveStageCallback, + type ApproveStageCallback, } from './withdrawals/index.js'; export { LIDO_CONTRACT_NAMES } from './common/constants.js'; +export { type WrapProps } from './wrap/index.js'; diff --git a/packages/sdk/src/staking/staking.ts b/packages/sdk/src/staking/staking.ts index 36cd7399..71ec7d79 100644 --- a/packages/sdk/src/staking/staking.ts +++ b/packages/sdk/src/staking/staking.ts @@ -11,7 +11,7 @@ import { } from 'viem'; import invariant from 'tiny-invariant'; import { LidoSDKCore } from '../core/index.js'; -import { Logger, Cache } from '../common/decorators/index.js'; +import { Logger, Cache, ErrorHandler } from '../common/decorators/index.js'; import { SUBMIT_EXTRA_GAS_TRANSACTION_RATIO, @@ -76,26 +76,15 @@ export class LidoSDKStaking { // Calls @Logger('Call:') + @ErrorHandler('Error:') public async stake(props: StakeProps): Promise { invariant(this.core.web3Provider, 'Web3 provider is not defined'); + const { account } = props; - const { callback, account } = props; - try { - const isContract = await this.core.isContract(account); - - if (isContract) return await this.stakeMultisig(props); - else return await this.stakeEOA(props); - } catch (error) { - const { message, code } = this.core.getErrorMessage(error); - const txError = this.core.error({ - message, - error, - code, - }); - callback?.({ stage: TransactionCallbackStage.ERROR, payload: txError }); + const isContract = await this.core.isContract(account); - throw txError; - } + if (isContract) return await this.stakeMultisig(props); + else return await this.stakeEOA(props); } @Logger('LOG:') @@ -119,7 +108,7 @@ export class LidoSDKStaking { referralAddress, ); - callback({ stage: TransactionCallbackStage.SIGN }); + callback({ stage: TransactionCallbackStage.SIGN, payload: gasLimit }); const contract = await this.getContractStETH(); const transaction = await contract.write.submit([referralAddress], { @@ -177,6 +166,7 @@ export class LidoSDKStaking { } @Logger('Call:') + @ErrorHandler('Error:') public async stakeSimulateTx( props: StakeProps, ): Promise { @@ -199,6 +189,7 @@ export class LidoSDKStaking { @Logger('Views:') @Cache(30 * 1000, ['core.chain.id']) + @ErrorHandler('Error:') public async getStakeLimitInfo() { const contract = await this.getContractStETH(); diff --git a/packages/sdk/src/withdrawals/bus.ts b/packages/sdk/src/withdrawals/bus.ts index f931ff83..ec7c0ed2 100644 --- a/packages/sdk/src/withdrawals/bus.ts +++ b/packages/sdk/src/withdrawals/bus.ts @@ -3,7 +3,9 @@ import { LidoSDKCore } from '../core/index.js'; import { LidoSDKWithdrawalsContract } from './withdrawalsContract.js'; import { LidoSDKWithdrawalsViews } from './withdrawalsViews.js'; import { LidoSDKWithdrawalsRequestsInfo } from './withdrawalsRequestsInfo.js'; -import { LidoSDKWithdrawalsApprove } from './withdrawalsApprove.js'; +import { LidoSDKWithdrawalsApprove } from './request/approve.js'; +import { LidoSDKWithdrawalsClaim } from './claim/claim.js'; +import { LidoSDKWithdrawalsRequest } from './request/request.js'; import { type LidoSDKWithdrawalsProps } from './types.js'; export class Bus { @@ -15,6 +17,8 @@ export class Bus { private viewsInstance: LidoSDKWithdrawalsViews | undefined; private requestsInfoInstance: LidoSDKWithdrawalsRequestsInfo | undefined; private approvalInstance: LidoSDKWithdrawalsApprove | undefined; + private claimInstance: LidoSDKWithdrawalsClaim | undefined; + private requestInstance: LidoSDKWithdrawalsRequest | undefined; constructor(props: LidoSDKWithdrawalsProps, version?: string) { this.props = props; @@ -104,4 +108,38 @@ export class Bus { set approval(approve: LidoSDKWithdrawalsApprove) { this.approvalInstance = approve; } + + // Claim + + get claim(): LidoSDKWithdrawalsClaim { + if (!this.claimInstance) { + this.claimInstance = new LidoSDKWithdrawalsClaim({ + ...this.props, + bus: this, + }); + return this.claimInstance; + } + return this.claimInstance; + } + + set claim(approve: LidoSDKWithdrawalsClaim) { + this.claimInstance = approve; + } + + // Request + + get request(): LidoSDKWithdrawalsRequest { + if (!this.requestInstance) { + this.requestInstance = new LidoSDKWithdrawalsRequest({ + ...this.props, + bus: this, + }); + return this.requestInstance; + } + return this.requestInstance; + } + + set request(approve: LidoSDKWithdrawalsRequest) { + this.requestInstance = approve; + } } diff --git a/packages/sdk/src/withdrawals/claim/claim.ts b/packages/sdk/src/withdrawals/claim/claim.ts new file mode 100644 index 00000000..696e62f4 --- /dev/null +++ b/packages/sdk/src/withdrawals/claim/claim.ts @@ -0,0 +1,124 @@ +import invariant from 'tiny-invariant'; + +import { type LidoSDKCoreProps } from '../../core/index.js'; +import { Logger, Cache, ErrorHandler } from '../../common/decorators/index.js'; +import { TransactionCallbackStage } from '../../core/types.js'; +import { version } from '../../version.js'; + +import { Bus } from '../bus.js'; + +import { ClaimRequestsProps } from './types.js'; + +export class LidoSDKWithdrawalsClaim { + private readonly bus: Bus; + + constructor(props: LidoSDKCoreProps & { bus?: Bus }) { + if (props.bus) this.bus = props.bus; + else this.bus = new Bus(props, version); + } + + // Calls + + @Logger('Call:') + @ErrorHandler('Error:') + public async claimRequests(props: ClaimRequestsProps) { + const { account } = props; + invariant(this.bus.core.web3Provider, 'Web3 provider is not defined'); + invariant(this.bus.core.rpcProvider, 'RPC provider is not defined'); + + const isContract = await this.bus.core.isContract(account); + + if (isContract) return this.claimRequestsMultisig(props); + else return this.claimRequestsEOA(props); + } + + @Logger('Call:') + @ErrorHandler('Error:') + public async claimRequestsEOA(props: ClaimRequestsProps) { + const { account, requestsIds, hints, callback } = props; + + const contract = await this.bus.contract.getContractWithdrawalsQueue(); + + callback?.({ stage: TransactionCallbackStage.GAS_LIMIT }); + + const feeData = await this.bus.core.getFeeData(); + const gasLimit = await this.claimGasLimit(props); + const overrides = { + account, + maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ?? undefined, + maxFeePerGas: feeData.maxFeePerGas ?? undefined, + }; + + callback?.({ stage: TransactionCallbackStage.SIGN, payload: gasLimit }); + + const params = [requestsIds, hints] as const; + const transaction = await contract.write.claimWithdrawals(params, { + ...overrides, + gas: gasLimit, + chain: this.bus.core.chain, + }); + + callback?.({ + stage: TransactionCallbackStage.RECEIPT, + payload: transaction, + }); + + const transactionReceipt = + await this.bus.core.rpcProvider.waitForTransactionReceipt({ + hash: transaction, + }); + + callback?.({ + stage: TransactionCallbackStage.CONFIRMATION, + payload: transactionReceipt, + }); + + const confirmations = + await this.bus.core.rpcProvider.getTransactionConfirmations({ + hash: transactionReceipt.transactionHash, + }); + + callback?.({ + stage: TransactionCallbackStage.DONE, + payload: confirmations, + }); + + return { hash: transaction, receipt: transactionReceipt, confirmations }; + } + + @Logger('Call:') + @ErrorHandler('Error:') + public async claimRequestsMultisig(props: ClaimRequestsProps) { + const { account, requestsIds, hints, callback } = props; + + const contract = await this.bus.contract.getContractWithdrawalsQueue(); + + callback?.({ stage: TransactionCallbackStage.SIGN }); + + const params = [requestsIds, hints] as const; + const transaction = await contract.write.claimWithdrawals(params, { + account, + chain: this.bus.core.chain, + }); + + callback?.({ stage: TransactionCallbackStage.MULTISIG_DONE }); + + return { hash: transaction }; + } + + @Logger('Utils:') + @Cache(30 * 1000, ['core.chain.id']) + private async claimGasLimit(props: ClaimRequestsProps): Promise { + const { account, requestsIds, hints } = props; + invariant(this.bus.core.rpcProvider, 'RPC provider is not defined'); + + const contract = await this.bus.contract.getContractWithdrawalsQueue(); + + const params = [requestsIds, hints] as const; + const gasLimit = await contract.estimateGas.claimWithdrawals(params, { + account, + }); + + return gasLimit; + } +} diff --git a/packages/sdk/src/withdrawals/claim/index.ts b/packages/sdk/src/withdrawals/claim/index.ts new file mode 100644 index 00000000..68b6b6ed --- /dev/null +++ b/packages/sdk/src/withdrawals/claim/index.ts @@ -0,0 +1 @@ +export { LidoSDKWithdrawalsClaim } from './claim.js'; diff --git a/packages/sdk/src/withdrawals/claim/types.ts b/packages/sdk/src/withdrawals/claim/types.ts new file mode 100644 index 00000000..771d4fdb --- /dev/null +++ b/packages/sdk/src/withdrawals/claim/types.ts @@ -0,0 +1,10 @@ +import { type Address } from 'viem'; + +import { TransactionCallback } from '../../core/index.js'; + +export type ClaimRequestsProps = { + account: Address; + requestsIds: bigint[]; + hints: bigint[]; + callback?: TransactionCallback; +}; diff --git a/packages/sdk/src/withdrawals/index.ts b/packages/sdk/src/withdrawals/index.ts index f4f789fd..26c5cc9f 100644 --- a/packages/sdk/src/withdrawals/index.ts +++ b/packages/sdk/src/withdrawals/index.ts @@ -1,9 +1,8 @@ export { LidoSDKWithdrawals } from './withdrawals.js'; +export { ClaimRequestsProps } from './claim/types.js'; export { - type RequestCallbackStages, - type RequestStageCallback, type RequestProps, type RequestWithPermitProps, type ApproveCallbackStages, type ApproveStageCallback, -} from './types.js'; +} from './request/types.js'; diff --git a/packages/sdk/src/withdrawals/withdrawalsApprove.ts b/packages/sdk/src/withdrawals/request/approve.ts similarity index 97% rename from packages/sdk/src/withdrawals/withdrawalsApprove.ts rename to packages/sdk/src/withdrawals/request/approve.ts index f5217a6a..a8795458 100644 --- a/packages/sdk/src/withdrawals/withdrawalsApprove.ts +++ b/packages/sdk/src/withdrawals/request/approve.ts @@ -1,11 +1,11 @@ import { parseEther, type Address } from 'viem'; import invariant from 'tiny-invariant'; -import { type LidoSDKCoreProps } from '../core/index.js'; -import { Logger, Cache, ErrorHandler } from '../common/decorators/index.js'; -import { version } from '../version.js'; +import { type LidoSDKCoreProps } from '../../core/index.js'; +import { Logger, Cache, ErrorHandler } from '../../common/decorators/index.js'; +import { version } from '../../version.js'; -import { Bus } from './bus.js'; +import { Bus } from '../bus.js'; import { type ApproveProps, ApproveCallbackStages } from './types.js'; export class LidoSDKWithdrawalsApprove { diff --git a/packages/sdk/src/withdrawals/request/index.ts b/packages/sdk/src/withdrawals/request/index.ts new file mode 100644 index 00000000..4af6b5e4 --- /dev/null +++ b/packages/sdk/src/withdrawals/request/index.ts @@ -0,0 +1 @@ +export { LidoSDKWithdrawalsRequest } from './request.js'; diff --git a/packages/sdk/src/withdrawals/request/request.ts b/packages/sdk/src/withdrawals/request/request.ts new file mode 100644 index 00000000..afd9f14f --- /dev/null +++ b/packages/sdk/src/withdrawals/request/request.ts @@ -0,0 +1,329 @@ +import { parseEther, type Address, Hash } from 'viem'; +import invariant from 'tiny-invariant'; + +import { Logger, Cache, ErrorHandler } from '../../common/decorators/index.js'; +import { LIDO_TOKENS } from '../../common/constants.js'; +import { type LidoSDKCoreProps } from '../../core/index.js'; +import { + type PermitSignature, + TransactionCallbackStage, + TransactionCallback, +} from '../../core/types.js'; +import { version } from '../../version.js'; + +import { Bus } from '../bus.js'; + +import { type RequestWithPermitProps, type RequestProps } from './types.js'; + +export class LidoSDKWithdrawalsRequest { + private readonly bus: Bus; + + constructor(props: LidoSDKCoreProps & { bus?: Bus }) { + if (props.bus) this.bus = props.bus; + else this.bus = new Bus(props, version); + } + + // Calls + + @Logger('Call:') + public async requestByToken(props: RequestWithPermitProps) { + const { account } = props; + invariant(this.bus.core.web3Provider, 'Web3 provider is not defined'); + invariant(this.bus.core.rpcProvider, 'RPC provider is not defined'); + + const isContract = await this.bus.core.isContract(account); + + if (isContract) return this.requestMultisigByToken(props); + else return this.requestWithPermitByToken(props); + } + + @Logger('Call:') + public async requestWithoutPermit(props: RequestProps) { + const { account } = props; + invariant(this.bus.core.web3Provider, 'Web3 provider is not defined'); + invariant(this.bus.core.rpcProvider, 'RPC provider is not defined'); + + const isContract = await this.bus.core.isContract(account); + + if (isContract) return this.requestMultisigByToken(props); + else return this.requestWithoutPermitByToken(props); + } + + @Logger('Call:') + @ErrorHandler('Error:') + public async requestStethWithPermit( + props: Omit, + ) { + this.requestWithPermitByToken({ ...props, token: LIDO_TOKENS.steth }); + } + + @Logger('Call:') + @ErrorHandler('Error:') + public async requestWstethWithPermit( + props: Omit, + ) { + this.requestWithPermitByToken({ ...props, token: LIDO_TOKENS.steth }); + } + + @Logger('Call:') + @ErrorHandler('Error:') + public async requestStethWithoutPermit(props: Omit) { + this.requestWithoutPermitByToken({ ...props, token: LIDO_TOKENS.steth }); + } + + @Logger('Call:') + @ErrorHandler('Error:') + public async requestWstethWithoutPermit(props: Omit) { + this.requestWithoutPermitByToken({ ...props, token: LIDO_TOKENS.steth }); + } + + @Logger('Call:') + @ErrorHandler('Error:') + public async requestStethMultisig(props: Omit) { + return this.requestMultisigByToken({ ...props, token: LIDO_TOKENS.steth }); + } + + @Logger('Call:') + @ErrorHandler('Error:') + public async requestWstethMultisig(props: Omit) { + return this.requestMultisigByToken({ ...props, token: LIDO_TOKENS.wsteth }); + } + + @Logger('Call:') + @ErrorHandler('Error:') + public async requestWithoutPermitByToken(props: RequestProps) { + const { account, requests, callback, token } = props; + invariant(this.bus.core.web3Provider, 'Web3 provider is not defined'); + invariant(this.bus.core.rpcProvider, 'RPC provider is not defined'); + + const contract = await this.bus.contract.getContractWithdrawalsQueue(); + + const isSteth = token === 'stETH'; + let tokenRequestMethod; + + if (isSteth) { + tokenRequestMethod = contract.write.requestWithdrawals; + } else { + tokenRequestMethod = contract.write.requestWithdrawalsWstETH; + } + + const params = [requests, account] as const; + + callback?.({ stage: TransactionCallbackStage.GAS_LIMIT }); + + const gasLimit = await this.requestGasLimitByToken( + account, + requests, + token, + ); + const feeData = await this.bus.core.getFeeData(); + const overrides = { + account, + maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ?? undefined, + maxFeePerGas: feeData.maxFeePerGas ?? undefined, + }; + + callback?.({ stage: TransactionCallbackStage.SIGN, payload: gasLimit }); + + const transaction = await tokenRequestMethod.call(this, params, { + ...overrides, + gas: gasLimit, + chain: this.bus.core.chain, + }); + + return this.waitTransactionLifecycle(transaction, callback); + } + + @Logger('Call:') + @ErrorHandler('Error:') + public async requestWithPermitByToken(props: RequestWithPermitProps) { + const { account, amount, requests, callback, token } = props; + + invariant(this.bus.core.web3Provider, 'Web3 provider is not defined'); + invariant(this.bus.core.rpcProvider, 'RPC provider is not defined'); + + const contract = await this.bus.contract.getContractWithdrawalsQueue(); + + const isSteth = token === LIDO_TOKENS.steth; + let tokenRequestMethod; + + if (isSteth) { + tokenRequestMethod = contract.write.requestWithdrawalsWithPermit; + } else { + tokenRequestMethod = contract.write.requestWithdrawalsWstETHWithPermit; + } + + callback?.({ stage: TransactionCallbackStage.PERMIT }); + + const signature = await this.bus.core.signPermit({ + account, + spender: contract.address, + amount: parseEther(amount), + token, + }); + + const params = [ + requests, + signature.owner, + { + value: signature.value, + deadline: signature.deadline, + v: signature.v, + r: signature.r, + s: signature.s, + }, + ] as const; + + callback?.({ stage: TransactionCallbackStage.GAS_LIMIT }); + + const gasLimit = await this.requestWithPermitGasLimitByToken( + account, + signature, + requests, + token, + ); + const feeData = await this.bus.core.getFeeData(); + const overrides = { + account, + maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ?? undefined, + maxFeePerGas: feeData.maxFeePerGas ?? undefined, + }; + + callback?.({ stage: TransactionCallbackStage.SIGN, payload: gasLimit }); + + const transaction = await tokenRequestMethod.call(this, params, { + chain: this.bus.core.chain, + gas: gasLimit, + ...overrides, + }); + + return this.waitTransactionLifecycle(transaction, callback); + } + + @Logger('Call:') + @ErrorHandler('Error:') + public async requestMultisigByToken(props: RequestProps) { + const { account, requests, callback, token } = props; + invariant(this.bus.core.web3Provider, 'Web3 provider is not defined'); + invariant(this.bus.core.rpcProvider, 'RPC provider is not defined'); + + const contract = await this.bus.contract.getContractWithdrawalsQueue(); + + const isSteth = token === 'stETH'; + let tokenRequestMethod; + + if (isSteth) tokenRequestMethod = contract.write.requestWithdrawals; + else tokenRequestMethod = contract.write.requestWithdrawalsWstETH; + + callback?.({ stage: TransactionCallbackStage.SIGN }); + + const params = [requests, account] as const; + const transaction = await tokenRequestMethod.call(this, params, { + account, + chain: this.bus.core.chain, + }); + + callback?.({ stage: TransactionCallbackStage.MULTISIG_DONE }); + + return { hash: transaction }; + } + + // Utils + + @Logger('Utils:') + @Cache(30 * 1000, ['bus.core.chain.id']) + private async requestWithPermitGasLimitByToken( + account: Address, + signature: PermitSignature, + requests: readonly bigint[], + token: 'stETH' | 'wstETH', + ): Promise { + invariant(this.bus.core.rpcProvider, 'RPC provider is not defined'); + + const contract = await this.bus.contract.getContractWithdrawalsQueue(); + + const isSteth = token === 'stETH'; + let tokenRequestMethod; + + if (isSteth) + tokenRequestMethod = contract.estimateGas.requestWithdrawalsWithPermit; + else + tokenRequestMethod = + contract.estimateGas.requestWithdrawalsWstETHWithPermit; + + const params = [ + requests, + signature.owner, + { + value: signature.value, + deadline: signature.deadline, + v: signature.v, + r: signature.r, + s: signature.s, + }, + ] as const; + + const gasLimit = await tokenRequestMethod.call(this, params, { + account, + }); + + return gasLimit; + } + + @Logger('Utils:') + @Cache(30 * 1000, ['bus.core.chain.id']) + private async requestGasLimitByToken( + account: Address, + requests: readonly bigint[], + token: 'stETH' | 'wstETH', + ): Promise { + invariant(this.bus.core.rpcProvider, 'RPC provider is not defined'); + + const contract = await this.bus.contract.getContractWithdrawalsQueue(); + + const isSteth = token === 'stETH'; + let tokenRequestMethod; + if (isSteth) tokenRequestMethod = contract.estimateGas.requestWithdrawals; + else tokenRequestMethod = contract.estimateGas.requestWithdrawalsWstETH; + + const params = [requests, account] as const; + const gasLimit = await tokenRequestMethod.call(this, params, { + account, + }); + + return gasLimit; + } + + @Logger('Utils:') + private async waitTransactionLifecycle( + transaction: Hash, + callback?: TransactionCallback, + ) { + callback?.({ + stage: TransactionCallbackStage.RECEIPT, + payload: transaction, + }); + + const transactionReceipt = + await this.bus.core.rpcProvider.waitForTransactionReceipt({ + hash: transaction, + }); + + callback?.({ + stage: TransactionCallbackStage.CONFIRMATION, + payload: transactionReceipt, + }); + + const confirmations = + await this.bus.core.rpcProvider.getTransactionConfirmations({ + hash: transactionReceipt.transactionHash, + }); + + callback?.({ + stage: TransactionCallbackStage.DONE, + payload: confirmations, + }); + + return { hash: transaction, receipt: transactionReceipt, confirmations }; + } +} diff --git a/packages/sdk/src/withdrawals/request/types.ts b/packages/sdk/src/withdrawals/request/types.ts new file mode 100644 index 00000000..7ab979a5 --- /dev/null +++ b/packages/sdk/src/withdrawals/request/types.ts @@ -0,0 +1,67 @@ +import { type Address, type Hash, type TransactionReceipt } from 'viem'; + +import { TransactionCallback } from '../../core/index.js'; +import { type SDKError } from '../../common/utils/index.js'; +import { LIDO_TOKENS } from '../../common/constants.js'; + +export type WithdrawableTokens = + | (typeof LIDO_TOKENS)['steth'] + | (typeof LIDO_TOKENS)['wsteth']; + +export type PermitWstETHStETHProps = { + amount: bigint; + account: Address; + spender: Address; + deadline: bigint; +}; + +export type PermitProps = PermitWstETHStETHProps & { + token: WithdrawableTokens; +}; + +export type RequestWithPermitProps = { + account: Address; + amount: string; + requests: readonly bigint[]; + token: WithdrawableTokens; + callback?: TransactionCallback; +}; + +export type RequestProps = { + account: Address; + requests: readonly bigint[]; + token: WithdrawableTokens; + callback?: TransactionCallback; +}; + +export enum ApproveCallbackStages { + 'GAS_LIMIT' = 'gas_limit', + 'SIGN' = 'sign', + 'RECEIPT' = 'receipt', + 'CONFIRMATION' = 'confirmation', + 'DONE' = 'done', + 'MULTISIG_DONE' = 'multisig_done', + 'ERROR' = 'error', +} + +export type ApproveCallbackProps = + | { stage: ApproveCallbackStages.GAS_LIMIT; payload?: undefined } + | { stage: ApproveCallbackStages.SIGN; payload?: bigint } + | { stage: ApproveCallbackStages.SIGN; payload: Hash } + | { stage: ApproveCallbackStages.RECEIPT; payload: Hash } + | { + stage: ApproveCallbackStages.CONFIRMATION; + payload: TransactionReceipt; + } + | { stage: ApproveCallbackStages.DONE; payload: bigint } + | { stage: ApproveCallbackStages.MULTISIG_DONE; payload?: undefined } + | { stage: ApproveCallbackStages.ERROR; payload: SDKError }; + +export type ApproveStageCallback = (props: ApproveCallbackProps) => void; + +export type ApproveProps = { + account: Address; + amount: string; + token: 'stETH' | 'wstETH'; + callback?: ApproveStageCallback; +}; diff --git a/packages/sdk/src/withdrawals/types.ts b/packages/sdk/src/withdrawals/types.ts index 7d2edd85..384ae300 100644 --- a/packages/sdk/src/withdrawals/types.ts +++ b/packages/sdk/src/withdrawals/types.ts @@ -1,17 +1,11 @@ -import { type Address, type Hash, type TransactionReceipt } from 'viem'; +import { type Address } from 'viem'; import { type LidoSDKCoreProps, type LidoSDKCore } from '../core/index.js'; -import { type SDKError } from '../common/utils/index.js'; -import { LIDO_TOKENS } from '../common/constants.js'; export type LidoSDKWithdrawalsProps = LidoSDKCoreProps & { core?: LidoSDKCore; }; -export type WithdrawableTokens = - | (typeof LIDO_TOKENS)['steth'] - | (typeof LIDO_TOKENS)['wsteth']; - export type RequestStatus = { amountOfStETH: bigint; amountOfShares: bigint; @@ -31,86 +25,3 @@ export type RequestStatusWithId = { id: bigint; stringId: string; }; - -export type PermitWstETHStETHProps = { - amount: bigint; - account: Address; - spender: Address; - deadline: bigint; -}; - -export type PermitProps = PermitWstETHStETHProps & { - token: WithdrawableTokens; -}; - -export type RequestWithPermitProps = { - account: Address; - amount: string; - requests: readonly bigint[]; - token: WithdrawableTokens; - callback?: RequestStageCallback; -}; - -export type RequestProps = { - account: Address; - requests: readonly bigint[]; - token: WithdrawableTokens; - callback?: RequestStageCallback; -}; - -export enum RequestCallbackStages { - 'BUNKER' = 'bunker', - 'PERMIT' = 'permit', - 'GAS_LIMIT' = 'gas_limit', - 'SIGN' = 'sign', - 'RECEIPT' = 'receipt', - 'CONFIRMATION' = 'confirmation', - 'DONE' = 'done', - 'MULTISIG_DONE' = 'multisig_done', - 'ERROR' = 'error', -} - -export type RequestCallbackProps = - | { stage: RequestCallbackStages.BUNKER; payload?: undefined } - | { stage: RequestCallbackStages.PERMIT; payload?: undefined } - | { stage: RequestCallbackStages.GAS_LIMIT; payload?: undefined } - | { stage: RequestCallbackStages.SIGN; payload?: bigint } - | { stage: RequestCallbackStages.RECEIPT; payload: Hash } - | { stage: RequestCallbackStages.CONFIRMATION; payload: TransactionReceipt } - | { stage: RequestCallbackStages.DONE; payload: bigint } - | { stage: RequestCallbackStages.MULTISIG_DONE; payload?: undefined } - | { stage: RequestCallbackStages.ERROR; payload: SDKError }; - -export type RequestStageCallback = (props: RequestCallbackProps) => void; - -export enum ApproveCallbackStages { - 'GAS_LIMIT' = 'gas_limit', - 'SIGN' = 'sign', - 'RECEIPT' = 'receipt', - 'CONFIRMATION' = 'confirmation', - 'DONE' = 'done', - 'MULTISIG_DONE' = 'multisig_done', - 'ERROR' = 'error', -} - -export type ApproveCallbackProps = - | { stage: ApproveCallbackStages.GAS_LIMIT; payload?: undefined } - | { stage: ApproveCallbackStages.SIGN; payload?: bigint } - | { stage: ApproveCallbackStages.SIGN; payload: Hash } - | { stage: ApproveCallbackStages.RECEIPT; payload: Hash } - | { - stage: ApproveCallbackStages.CONFIRMATION; - payload: TransactionReceipt; - } - | { stage: ApproveCallbackStages.DONE; payload: bigint } - | { stage: ApproveCallbackStages.MULTISIG_DONE; payload?: undefined } - | { stage: ApproveCallbackStages.ERROR; payload: SDKError }; - -export type ApproveStageCallback = (props: ApproveCallbackProps) => void; - -export type ApproveProps = { - account: Address; - amount: string; - token: 'stETH' | 'wstETH'; - callback?: ApproveStageCallback; -}; diff --git a/packages/sdk/src/withdrawals/withdrawals.ts b/packages/sdk/src/withdrawals/withdrawals.ts index 27979290..a6101954 100644 --- a/packages/sdk/src/withdrawals/withdrawals.ts +++ b/packages/sdk/src/withdrawals/withdrawals.ts @@ -1,330 +1,10 @@ -import { parseEther, type Address } from 'viem'; -import invariant from 'tiny-invariant'; - -import { Logger, Cache, ErrorHandler } from '../common/decorators/index.js'; import { version } from '../version.js'; import { Bus } from './bus.js'; -import { - type LidoSDKWithdrawalsProps, - type RequestWithPermitProps, - type RequestProps, - RequestCallbackStages, -} from './types.js'; -import { PermitSignature } from '../core/types.js'; -import { LIDO_TOKENS } from '../common/constants.js'; +import { LidoSDKWithdrawalsProps } from './types.js'; export class LidoSDKWithdrawals extends Bus { constructor(props: LidoSDKWithdrawalsProps) { super(props, version); } - - // Calls - - @Logger('Call:') - public async request(props: RequestWithPermitProps) { - const { account } = props; - invariant(this.core.web3Provider, 'Web3 provider is not defined'); - invariant(this.core.rpcProvider, 'RPC provider is not defined'); - - const isContract = await this.core.isContract(account); - - if (isContract) return this.requestMultisigByToken(props); - else return this.requestWithPermitByToken(props); - } - - @Logger('Call:') - public async requestWithoutPermit(props: RequestProps) { - const { account } = props; - invariant(this.core.web3Provider, 'Web3 provider is not defined'); - invariant(this.core.rpcProvider, 'RPC provider is not defined'); - - const isContract = await this.core.isContract(account); - - if (isContract) return this.requestMultisigByToken(props); - else return this.requestWithoutPermitByToken(props); - } - - @Logger('Call:') - @ErrorHandler('Error:') - public async requestStethWithPermit( - props: Omit, - ) { - this.requestWithPermitByToken({ ...props, token: LIDO_TOKENS.steth }); - } - - @Logger('Call:') - @ErrorHandler('Error:') - public async requestWstethWithPermit( - props: Omit, - ) { - this.requestWithPermitByToken({ ...props, token: LIDO_TOKENS.steth }); - } - - @Logger('Call:') - @ErrorHandler('Error:') - public async requestStethWithoutPermit(props: Omit) { - this.requestWithoutPermitByToken({ ...props, token: LIDO_TOKENS.steth }); - } - - @Logger('Call:') - @ErrorHandler('Error:') - public async requestWstethWithoutPermit(props: Omit) { - this.requestWithoutPermitByToken({ ...props, token: LIDO_TOKENS.steth }); - } - - @Logger('Call:') - @ErrorHandler('Error:') - public async requestStethMultisig(props: Omit) { - return this.requestMultisigByToken({ ...props, token: LIDO_TOKENS.steth }); - } - - @Logger('Call:') - @ErrorHandler('Error:') - public async requestWstethMultisig(props: Omit) { - return this.requestMultisigByToken({ ...props, token: LIDO_TOKENS.wsteth }); - } - - @Logger('Call:') - @ErrorHandler('Error:') - public async requestWithoutPermitByToken(props: RequestProps) { - const { account, requests, callback, token } = props; - invariant(this.core.web3Provider, 'Web3 provider is not defined'); - invariant(this.core.rpcProvider, 'RPC provider is not defined'); - - const contract = await this.contract.getContractWithdrawalsQueue(); - - const isSteth = token === 'stETH'; - let tokenRequestMethod; - - if (isSteth) { - tokenRequestMethod = contract.write.requestWithdrawals; - } else { - tokenRequestMethod = contract.write.requestWithdrawalsWstETH; - } - - const params = [requests, account] as const; - - callback?.({ stage: RequestCallbackStages.GAS_LIMIT }); - - const gasLimit = await this.requestGasLimitByToken( - account, - requests, - token, - ); - const feeData = await this.core.getFeeData(); - const overrides = { - account, - maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ?? undefined, - maxFeePerGas: feeData.maxFeePerGas ?? undefined, - }; - - callback?.({ stage: RequestCallbackStages.SIGN, payload: gasLimit }); - - const transaction = await tokenRequestMethod.call(this, [...params], { - ...overrides, - chain: this.core.chain, - }); - - callback?.({ stage: RequestCallbackStages.RECEIPT, payload: transaction }); - - const transactionReceipt = - await this.core.rpcProvider.waitForTransactionReceipt({ - hash: transaction, - }); - - callback?.({ - stage: RequestCallbackStages.CONFIRMATION, - payload: transactionReceipt, - }); - - const confirmations = - await this.core.rpcProvider.getTransactionConfirmations({ - hash: transactionReceipt.transactionHash, - }); - - callback?.({ stage: RequestCallbackStages.DONE, payload: confirmations }); - - return { hash: transaction, receipt: transactionReceipt, confirmations }; - } - - @Logger('Call:') - @ErrorHandler('Error:') - public async requestWithPermitByToken(props: RequestWithPermitProps) { - const { account, amount, requests, callback, token } = props; - - invariant(this.core.web3Provider, 'Web3 provider is not defined'); - invariant(this.core.rpcProvider, 'RPC provider is not defined'); - - const contract = await this.contract.getContractWithdrawalsQueue(); - - const isSteth = token === LIDO_TOKENS.steth; - let tokenRequestMethod; - - if (isSteth) { - tokenRequestMethod = contract.write.requestWithdrawalsWithPermit; - } else { - tokenRequestMethod = contract.write.requestWithdrawalsWstETHWithPermit; - } - - callback?.({ stage: RequestCallbackStages.PERMIT }); - - const signature = await this.core.signPermit({ - account, - spender: contract.address, - amount: parseEther(amount), - token, - }); - - const params = [ - requests, - signature.owner, - { - value: signature.value, - deadline: signature.deadline, - v: signature.v, - r: signature.r, - s: signature.s, - }, - ] as const; - - callback?.({ stage: RequestCallbackStages.GAS_LIMIT }); - - const gasLimit = await this.requestWithPermitGasLimitByToken( - account, - signature, - requests, - token, - ); - const feeData = await this.core.getFeeData(); - const overrides = { - account, - maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ?? undefined, - maxFeePerGas: feeData.maxFeePerGas ?? undefined, - }; - - callback?.({ stage: RequestCallbackStages.SIGN, payload: gasLimit }); - - const transaction = await tokenRequestMethod.call(this, [...params], { - chain: this.core.chain, - gas: gasLimit, - ...overrides, - }); - - callback?.({ stage: RequestCallbackStages.RECEIPT, payload: transaction }); - - const transactionReceipt = - await this.core.rpcProvider.waitForTransactionReceipt({ - hash: transaction, - }); - - callback?.({ - stage: RequestCallbackStages.CONFIRMATION, - payload: transactionReceipt, - }); - - const confirmations = - await this.core.rpcProvider.getTransactionConfirmations({ - hash: transactionReceipt.transactionHash, - }); - - callback?.({ stage: RequestCallbackStages.DONE, payload: confirmations }); - - return { hash: transaction, receipt: transactionReceipt, confirmations }; - } - - @Logger('Call:') - @ErrorHandler('Error:') - public async requestMultisigByToken(props: RequestProps) { - const { account, requests, callback, token } = props; - invariant(this.core.web3Provider, 'Web3 provider is not defined'); - invariant(this.core.rpcProvider, 'RPC provider is not defined'); - - const contract = await this.contract.getContractWithdrawalsQueue(); - - const isSteth = token === 'stETH'; - let tokenRequestMethod; - - if (isSteth) tokenRequestMethod = contract.write.requestWithdrawals; - else tokenRequestMethod = contract.write.requestWithdrawalsWstETH; - - const params = [requests, account] as const; - - callback?.({ stage: RequestCallbackStages.SIGN }); - - const transaction = await tokenRequestMethod.call(this, [...params], { - account, - chain: this.core.chain, - }); - - callback?.({ stage: RequestCallbackStages.MULTISIG_DONE }); - - return { hash: transaction }; - } - - // Utils - - @Logger('Utils:') - @Cache(30 * 1000, ['core.chain.id']) - private async requestWithPermitGasLimitByToken( - account: Address, - signature: PermitSignature, - requests: readonly bigint[], - token: 'stETH' | 'wstETH', - ): Promise { - invariant(this.core.rpcProvider, 'RPC provider is not defined'); - - const contract = await this.contract.getContractWithdrawalsQueue(); - - const isSteth = token === 'stETH'; - let tokenRequestMethod; - - if (isSteth) - tokenRequestMethod = contract.estimateGas.requestWithdrawalsWithPermit; - else - tokenRequestMethod = - contract.estimateGas.requestWithdrawalsWstETHWithPermit; - - const params = [ - requests, - signature.owner, - { - value: signature.value, - deadline: signature.deadline, - v: signature.v, - r: signature.r, - s: signature.s, - }, - ] as const; - - const gasLimit = await tokenRequestMethod.call(this, [...params], { - account, - }); - - return gasLimit; - } - - @Logger('Utils:') - @Cache(30 * 1000, ['core.chain.id']) - private async requestGasLimitByToken( - account: Address, - requests: readonly bigint[], - token: 'stETH' | 'wstETH', - ): Promise { - invariant(this.core.rpcProvider, 'RPC provider is not defined'); - - const contract = await this.contract.getContractWithdrawalsQueue(); - - const isSteth = token === 'stETH'; - let tokenRequestMethod; - if (isSteth) tokenRequestMethod = contract.estimateGas.requestWithdrawals; - else tokenRequestMethod = contract.estimateGas.requestWithdrawalsWstETH; - - const params = [requests, account] as const; - const gasLimit = await tokenRequestMethod.call(this, [...params], { - account, - }); - - return gasLimit; - } } diff --git a/packages/sdk/src/withdrawals/withdrawalsRequestsInfo.ts b/packages/sdk/src/withdrawals/withdrawalsRequestsInfo.ts index bd29c5c7..95b096b1 100644 --- a/packages/sdk/src/withdrawals/withdrawalsRequestsInfo.ts +++ b/packages/sdk/src/withdrawals/withdrawalsRequestsInfo.ts @@ -71,7 +71,11 @@ export class LidoSDKWithdrawalsRequestsInfo { @Logger('Utils:') public async getClaimableRequestsETH(props: { claimableRequestsIds: (bigint | RequestStatusWithId)[]; - }): Promise<{ ethByRequests: readonly bigint[]; ethSum: bigint }> { + }): Promise<{ + ethByRequests: readonly bigint[]; + ethSum: bigint; + hints: readonly bigint[]; + }> { const sortedIds = props.claimableRequestsIds .sort((aReq, bReq) => { if (isBigint(aReq) && isBigint(bReq)) { @@ -96,7 +100,7 @@ export class LidoSDKWithdrawalsRequestsInfo { }); const ethSum = ethByRequests.reduce((acc, eth) => acc + eth, BigInt(0)); - return { ethByRequests, ethSum }; + return { ethByRequests, ethSum, hints }; } @Logger('Utils:') diff --git a/packages/sdk/src/wrap/index.ts b/packages/sdk/src/wrap/index.ts index 1a6a8431..15fd0565 100644 --- a/packages/sdk/src/wrap/index.ts +++ b/packages/sdk/src/wrap/index.ts @@ -1,2 +1,2 @@ export { LidoSDKWrap } from './wrap.js'; -export { type CommonWrapProps, type LidoSDKWrapProps } from './types.js'; +export { type WrapProps, type LidoSDKWrapProps } from './types.js'; diff --git a/packages/sdk/src/wrap/types.ts b/packages/sdk/src/wrap/types.ts index 54659fb2..2b0fbf30 100644 --- a/packages/sdk/src/wrap/types.ts +++ b/packages/sdk/src/wrap/types.ts @@ -16,7 +16,7 @@ export type LidoSDKWrapProps = LidoSDKCoreProps & { core?: LidoSDKCore; }; -export type CommonWrapProps = { +export type WrapProps = { value: string; account: Address; callback?: TransactionCallback; @@ -30,7 +30,7 @@ export type TxResult = { export type PopulatedTx = Omit; -export interface IMethodProps { +export interface TxMethodProps { account: Address; callback?: (props: { stage: TransactionCallbackStage.ERROR; diff --git a/packages/sdk/src/wrap/wrap.ts b/packages/sdk/src/wrap/wrap.ts index 1e619507..fd940700 100644 --- a/packages/sdk/src/wrap/wrap.ts +++ b/packages/sdk/src/wrap/wrap.ts @@ -8,16 +8,17 @@ import { type WalletClient, type FormattedTransactionRequest, type WriteContractParameters, + type Hash, } from 'viem'; import invariant from 'tiny-invariant'; import { LidoSDKCore } from '../core/index.js'; import { Logger, Cache, ErrorHandler } from '../common/decorators/index.js'; import { LidoSDKWrapProps, - CommonWrapProps, + WrapProps, TxResult, PopulatedTx, - IMethodProps as ITxMethodProps, + TxMethodProps, } from './types.js'; import { version } from '../version.js'; @@ -93,18 +94,16 @@ export class LidoSDKWrap { // Calls - //// WRAP ETH - @Logger('Call:') @ErrorHandler('Error:') - public async wrapEth(props: CommonWrapProps): Promise { + public async wrapEth(props: WrapProps): Promise { return this.executeTxMethod(props, this.wrapEthEOA, this.wrapEthMultisig); } @Logger('LOG:') - private async wrapEthEOA(props: CommonWrapProps): Promise { + private async wrapEthEOA(props: WrapProps): Promise { invariant(this.core.web3Provider, 'Web3 provider is not defined'); - const { value: stringValue, callback = () => {}, account } = props; + const { value: stringValue, callback, account } = props; const value = parseEther(stringValue); // Checking the daily protocol staking limit @@ -112,7 +111,7 @@ export class LidoSDKWrap { this.validateStakeLimit(value); const contract = await this.getContractWstETH(); - callback({ stage: TransactionCallbackStage.GAS_LIMIT }); + callback?.({ stage: TransactionCallbackStage.GAS_LIMIT }); const gasLimit = await this.core.rpcProvider.estimateGas({ account, to: contract.address, @@ -121,7 +120,7 @@ export class LidoSDKWrap { const { maxFeePerGas, maxPriorityFeePerGas } = await this.core.getFeeData(); - callback({ stage: TransactionCallbackStage.SIGN }); + callback?.({ stage: TransactionCallbackStage.SIGN, payload: gasLimit }); const transaction = await this.core.web3Provider.sendTransaction({ value, account, @@ -136,14 +135,14 @@ export class LidoSDKWrap { } @Logger('LOG:') - private async wrapEthMultisig(props: CommonWrapProps): Promise { + private async wrapEthMultisig(props: WrapProps): Promise { invariant(this.core.web3Provider, 'Web3 provider is not defined'); - const { value: stringValue, callback = () => {}, account } = props; + const { value: stringValue, callback, account } = props; const value = parseEther(stringValue); const contract = await this.getContractWstETH(); - callback({ stage: TransactionCallbackStage.SIGN }); + callback?.({ stage: TransactionCallbackStage.SIGN }); const hash = await this.core.web3Provider.sendTransaction({ value, chain: this.core.chain, @@ -151,13 +150,13 @@ export class LidoSDKWrap { to: contract.address, }); - callback({ stage: TransactionCallbackStage.MULTISIG_DONE }); + callback?.({ stage: TransactionCallbackStage.MULTISIG_DONE }); return { hash }; } @Logger('Utils:') - public async wrapEthPopulateTx(props: CommonWrapProps): Promise { + public async wrapEthPopulateTx(props: WrapProps): Promise { const { value, account } = props; const address = await this.contractAddressWstETH(); @@ -172,7 +171,7 @@ export class LidoSDKWrap { @Logger('Call:') @ErrorHandler('Error:') public async wrapEthEstimateGas( - props: Omit, + props: Omit, ): Promise { const { value, account } = props; @@ -184,11 +183,11 @@ export class LidoSDKWrap { }); } - /// WRAP STETH + /// Wrap stETH @Logger('Call:') @ErrorHandler('Error:') - public async wrapSteth(props: CommonWrapProps): Promise { + public async wrapSteth(props: WrapProps): Promise { return this.executeTxMethod( props, this.wrapStethEOA, @@ -197,20 +196,20 @@ export class LidoSDKWrap { } @Logger('LOG:') - private async wrapStethEOA(props: CommonWrapProps): Promise { + private async wrapStethEOA(props: WrapProps): Promise { invariant(this.core.web3Provider, 'Web3 provider is not defined'); - const { value: stringValue, callback = () => {}, account } = props; + const { value: stringValue, callback, account } = props; const value = parseEther(stringValue); const contract = await this.getContractWstETH(); - callback({ stage: TransactionCallbackStage.GAS_LIMIT }); + callback?.({ stage: TransactionCallbackStage.GAS_LIMIT }); const gasLimit = await contract.estimateGas.wrap([value], { account, }); const { maxFeePerGas, maxPriorityFeePerGas } = await this.core.getFeeData(); - callback({ stage: TransactionCallbackStage.SIGN }); + callback?.({ stage: TransactionCallbackStage.SIGN, payload: gasLimit }); const transaction = await contract.write.wrap([value], { chain: this.core.chain, gas: gasLimit, @@ -223,14 +222,14 @@ export class LidoSDKWrap { } @Logger('LOG:') - private async wrapStethMultisig(props: CommonWrapProps): Promise { + private async wrapStethMultisig(props: WrapProps): Promise { invariant(this.core.web3Provider, 'Web3 provider is not defined'); - const { value: stringValue, callback = () => {}, account } = props; + const { value: stringValue, callback, account } = props; const value = parseEther(stringValue); const contract = await this.getContractWstETH(); - callback({ stage: TransactionCallbackStage.SIGN }); + callback?.({ stage: TransactionCallbackStage.SIGN }); const transaction = await contract.write.wrap([value], { chain: this.core.chain, account, @@ -242,9 +241,7 @@ export class LidoSDKWrap { } @Logger('Utils:') - public async wrapStethPopulateTx( - props: CommonWrapProps, - ): Promise { + public async wrapStethPopulateTx(props: WrapProps): Promise { const { value: stringValue, account } = props; const value = parseEther(stringValue); const address = await this.contractAddressWstETH(); @@ -263,7 +260,7 @@ export class LidoSDKWrap { @Logger('Call:') @ErrorHandler('Error:') public async wrapStethSimulateTx( - props: Omit, + props: Omit, ): Promise { const { value, account } = props; @@ -280,11 +277,11 @@ export class LidoSDKWrap { return request; } - /// APPROVE + /// Approve @Logger('Call:') @ErrorHandler('Error:') - public async approveStethForWrap(props: CommonWrapProps): Promise { + public async approveStethForWrap(props: WrapProps): Promise { return this.executeTxMethod( props, this.approveStethForWrapEOA, @@ -301,16 +298,14 @@ export class LidoSDKWrap { } @Logger('LOG:') - private async approveStethForWrapEOA( - props: CommonWrapProps, - ): Promise { - const { account, value: stringValue, callback = () => {} } = props; + private async approveStethForWrapEOA(props: WrapProps): Promise { + const { account, value: stringValue, callback } = props; const value = parseEther(stringValue); const stethContract = await this.getPartialContractSteth(); const wstethContractAddress = await this.contractAddressWstETH(); - callback({ stage: TransactionCallbackStage.GAS_LIMIT }); + callback?.({ stage: TransactionCallbackStage.GAS_LIMIT }); const gasLimit = await stethContract.estimateGas.approve( [wstethContractAddress, value], { @@ -320,7 +315,7 @@ export class LidoSDKWrap { const { maxFeePerGas, maxPriorityFeePerGas } = await this.core.getFeeData(); - callback({ stage: TransactionCallbackStage.SIGN }); + callback?.({ stage: TransactionCallbackStage.SIGN, payload: gasLimit }); const transaction = await stethContract.write.approve( [wstethContractAddress, value], { @@ -337,15 +332,15 @@ export class LidoSDKWrap { @Logger('LOG:') private async approveStethForWrapMultisig( - props: CommonWrapProps, + props: WrapProps, ): Promise { - const { account, value: stringValue, callback = () => {} } = props; + const { account, value: stringValue, callback } = props; const value = parseEther(stringValue); const stethContract = await this.getPartialContractSteth(); const wstethContractAddress = await this.contractAddressWstETH(); - callback({ stage: TransactionCallbackStage.SIGN }); + callback?.({ stage: TransactionCallbackStage.SIGN }); const transaction = await stethContract.write.approve( [wstethContractAddress, value], @@ -355,14 +350,14 @@ export class LidoSDKWrap { }, ); - callback({ stage: TransactionCallbackStage.MULTISIG_DONE }); + callback?.({ stage: TransactionCallbackStage.MULTISIG_DONE }); return { hash: transaction }; } @Logger('Utils:') public async approveStethForWrapPopulateTx( - props: CommonWrapProps, + props: WrapProps, ): Promise> { const { value: stringValue, account } = props; const value = parseEther(stringValue); @@ -384,7 +379,7 @@ export class LidoSDKWrap { @Logger('Call:') @ErrorHandler('Error:') public async approveStethForWrapSimulateTx( - props: Omit, + props: Omit, ): Promise { const { value: stringValue, account } = props; const value = parseEther(stringValue); @@ -403,30 +398,30 @@ export class LidoSDKWrap { return request; } - /// UNWRAP + /// Unwrap @Logger('Call:') @ErrorHandler('Error:') - public async unwrap(props: CommonWrapProps): Promise { + public async unwrap(props: WrapProps): Promise { return this.executeTxMethod(props, this.unwrapEOA, this.unwrapMultisig); } @Logger('LOG:') - private async unwrapEOA(props: CommonWrapProps): Promise { + private async unwrapEOA(props: WrapProps): Promise { invariant(this.core.web3Provider, 'Web3 provider is not defined'); - const { value: stringValue, callback = () => {}, account } = props; + const { value: stringValue, callback, account } = props; const value = parseEther(stringValue); const contract = await this.getContractWstETH(); - callback({ stage: TransactionCallbackStage.GAS_LIMIT }); + callback?.({ stage: TransactionCallbackStage.GAS_LIMIT }); const gasLimit = await contract.estimateGas.unwrap([value], { account, }); const { maxFeePerGas, maxPriorityFeePerGas } = await this.core.getFeeData(); - callback({ stage: TransactionCallbackStage.SIGN }); + callback?.({ stage: TransactionCallbackStage.SIGN, payload: gasLimit }); const transaction = await contract.write.unwrap([value], { chain: this.core.chain, gas: gasLimit, @@ -439,27 +434,27 @@ export class LidoSDKWrap { } @Logger('LOG:') - private async unwrapMultisig(props: CommonWrapProps): Promise { + private async unwrapMultisig(props: WrapProps): Promise { invariant(this.core.web3Provider, 'Web3 provider is not defined'); - const { value: stringValue, callback = () => {}, account } = props; + const { value: stringValue, callback, account } = props; const value = parseEther(stringValue); const contract = await this.getContractWstETH(); - callback({ stage: TransactionCallbackStage.SIGN }); + callback?.({ stage: TransactionCallbackStage.SIGN }); const transaction = await contract.write.unwrap([value], { chain: this.core.chain, account, }); - callback({ stage: TransactionCallbackStage.MULTISIG_DONE }); + callback?.({ stage: TransactionCallbackStage.MULTISIG_DONE }); return { hash: transaction }; } @Logger('Utils:') - public async unwrapPopulateTx(props: CommonWrapProps): Promise { + public async unwrapPopulateTx(props: WrapProps): Promise { const { value: stringValue, account } = props; const value = parseEther(stringValue); const to = await this.contractAddressWstETH(); @@ -478,7 +473,7 @@ export class LidoSDKWrap { @Logger('Call:') @ErrorHandler('Error:') public async unwrapSimulateTx( - props: Omit, + props: Omit, ): Promise { const { value: stringValue, account } = props; const value = parseEther(stringValue); @@ -496,7 +491,7 @@ export class LidoSDKWrap { return request; } - /// UTILS + /// Utils @Logger('Utils:') private async validateStakeLimit(value: bigint): Promise { @@ -512,7 +507,7 @@ export class LidoSDKWrap { } } - private async executeTxMethod( + private async executeTxMethod( props: TProps, EOAMethod: (props: TProps) => Promise, multisigMethod: (props: TProps) => Promise, @@ -527,17 +522,20 @@ export class LidoSDKWrap { @Logger('Utils:') private async waitTransactionLifecycle( - transaction: `0x${string}`, - callback: TransactionCallback, + transaction: Hash, + callback?: TransactionCallback, ) { - callback({ stage: TransactionCallbackStage.RECEIPT, payload: transaction }); + callback?.({ + stage: TransactionCallbackStage.RECEIPT, + payload: transaction, + }); const transactionReceipt = await this.core.rpcProvider.waitForTransactionReceipt({ hash: transaction, }); - callback({ + callback?.({ stage: TransactionCallbackStage.CONFIRMATION, payload: transactionReceipt, }); @@ -547,12 +545,15 @@ export class LidoSDKWrap { hash: transactionReceipt.transactionHash, }); - callback({ stage: TransactionCallbackStage.DONE, payload: confirmations }); + callback?.({ + stage: TransactionCallbackStage.DONE, + payload: confirmations, + }); return { hash: transaction, receipt: transactionReceipt, confirmations }; } - /// VIEWS + /// Views @Logger('Views:') @ErrorHandler('Error:') diff --git a/playground/demo/withdrawals/request.tsx b/playground/demo/withdrawals/request.tsx index e3502d69..a90d612b 100644 --- a/playground/demo/withdrawals/request.tsx +++ b/playground/demo/withdrawals/request.tsx @@ -5,6 +5,7 @@ import TokenInput from 'components/tokenInput/tokenInput'; import { useLidoSDK } from 'providers/sdk'; import { useState } from 'react'; import { parseEther } from '@ethersproject/units'; +import { transactionToast } from 'utils/transaction-toast'; export const WithdrawalsRequestDemo = () => { const { account: web3account = '0x0' } = useWeb3(); @@ -15,31 +16,55 @@ export const WithdrawalsRequestDemo = () => { return ( + setRequestValue(e.currentTarget.value)} + /> - withdrawals.request({ + withdrawals.request.requestByToken({ account, amount: requestValue, requests: [BigInt(parseEther(requestValue).toString())], token: 'stETH', + callback: transactionToast, }) } - > - setRequestValue(e.currentTarget.value)} - /> - + /> - withdrawals.requestWithoutPermit({ + withdrawals.request.requestByToken({ + account, + amount: requestValue, + requests: [BigInt(parseEther(requestValue).toString())], + token: 'wstETH', + callback: transactionToast, + }) + } + /> + + withdrawals.request.requestWithoutPermit({ account, requests: [BigInt(parseEther(requestValue).toString())], token: 'stETH', + callback: transactionToast, + }) + } + /> + + withdrawals.request.requestWithoutPermit({ + account, + requests: [BigInt(parseEther(requestValue).toString())], + token: 'wstETH', + callback: transactionToast, }) } /> diff --git a/playground/utils/transaction-toast.ts b/playground/utils/transaction-toast.ts index e3dc64eb..d4d3410a 100644 --- a/playground/utils/transaction-toast.ts +++ b/playground/utils/transaction-toast.ts @@ -6,6 +6,10 @@ import { toast } from '@lidofinance/lido-ui'; export const transactionToast: TransactionCallback = ({ stage, payload }) => { switch (stage) { + case TransactionCallbackStage.PERMIT: + return toast('Permit', { type: 'info' }); + case TransactionCallbackStage.GAS_LIMIT: + return toast('Gas limit', { type: 'info' }); case TransactionCallbackStage.SIGN: return toast('Signing', { type: 'info' }); case TransactionCallbackStage.RECEIPT: