From 4a89c33c6fcfe2fcea2ab6b56328f968e09905db Mon Sep 17 00:00:00 2001
From: poocart <7067483+poocart@users.noreply.github.com>
Date: Wed, 18 Oct 2023 13:33:06 +0300
Subject: [PATCH] Feature/prime sdk as default (#57)
* removed via prop, removed etherspot provider type, default provider etherspot prime
* removed gas token, added prime sdk estimations (#58)
---
CHANGELOG.md | 11 ++
.../hooks/useEtherspotTransactions.test.js | 26 +--
__tests__/hooks/useWalletAddress.test.js | 24 ++-
package.json | 2 +-
src/components/EtherspotBatch.tsx | 5 +-
src/components/EtherspotBatches.tsx | 24 ++-
src/hooks/useWalletAddress.ts | 11 +-
...EtherspotTransactionKitContextProvider.tsx | 149 +++++-------------
src/types/EtherspotTransactionKit.ts | 38 ++---
9 files changed, 107 insertions(+), 183 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5e3f2cc..d729aff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,16 @@
# Changelog
+## [0.6.0] - 2023-10-18
+
+### Breaking Changes
+- Removed `etherspot` wallet type from `useWalletAddress` hook
+- Removed `via` prop from `` component
+- Default `useWalletAddress` and `` provider is set to `etherspot-prime`
+- Removed `gasTokenAddress` prop from `` component due being unsupported in default `etherspot-prime` provider
+
+### Added
+- Added `gasCost` to `etherspot-prime` estimations
+
## [0.5.1] - 2023-10-17
### Added
diff --git a/__tests__/hooks/useEtherspotTransactions.test.js b/__tests__/hooks/useEtherspotTransactions.test.js
index 6f79691..82a3eee 100644
--- a/__tests__/hooks/useEtherspotTransactions.test.js
+++ b/__tests__/hooks/useEtherspotTransactions.test.js
@@ -6,7 +6,7 @@ import { useEtherspotTransactions, EtherspotTransactionKit, EtherspotBatches, Et
const TestSingleBatchComponent = () => (
-
+
{
test
-
+
{
test
-
-
-
-
-
-
+
{
const { result: { current } } = renderHook(() => useEtherspotTransactions(), { wrapper });
- expect(current.batches.length).toBe(6);
+ expect(current.batches.length).toBe(5);
expect(current.batches[0].batches.length).toBe(1);
expect(current.batches[0].batches[0].chainId).toBe(123);
- expect(current.batches[0].batches[0].gasTokenAddress).toBe('testGasTokenAddress');
expect(current.batches[0].batches[0].transactions.length).toBe(3);
expect(current.batches[0].batches[0].transactions[1].to).toBe('0x0');
expect(current.batches[0].batches[0].transactions[1].data).toBe('0xFFF');
@@ -93,13 +83,7 @@ describe('useEtherspotTransactions()', () => {
expect(current.batches[0].batches[0].transactions[2].data).toBe('0xa9059cbb0000000000000000000000007f30b1960d5556929b03a0339814fe903c55a347000000000000000000000000000000000000000000000006aaf7c8516d0c0000');
expect(current.batches[0].batches[0].transactions[2].value).toBe(undefined);
expect(current.batches[1].skip).toBe(true);
- expect(current.batches[2].batches.length).toBe(1);
- expect(current.batches[2].batches[0].chainId).toBe(69);
- expect(current.batches[2].batches[0].transactions.length).toBe(1);
- expect(current.batches[2].batches[0].transactions[0].to).toBe('0x420');
- expect(current.batches[2].batches[0].transactions[0].data).toBe('0x69420');
- expect(current.batches[2].batches[0].transactions[0].value.toJSON()).toStrictEqual({ 'hex': '0x03bd913e6c1df40000', 'type': 'BigNumber' });
- expect(current.batches[3].paymaster).toStrictEqual({ url: 'someUrl', api_key: 'someApiKey' });
+ expect(current.batches[2].paymaster).toStrictEqual({ url: 'someUrl', api_key: 'someApiKey' });
});
it('throws an error if within ', () => {
diff --git a/__tests__/hooks/useWalletAddress.test.js b/__tests__/hooks/useWalletAddress.test.js
index d0c221e..31a0669 100644
--- a/__tests__/hooks/useWalletAddress.test.js
+++ b/__tests__/hooks/useWalletAddress.test.js
@@ -7,11 +7,23 @@ import { EtherspotTransactionKit, useWalletAddress } from '../../src';
const ethersProvider = new ethers.providers.JsonRpcProvider('http://localhost:8545', 'goerli'); // replace with your node's RPC URL
const provider = new ethers.Wallet.createRandom().connect(ethersProvider);
-const etherspotAddress = '0x7F30B1960D5556929B03a0339814fE903c55a347';
const etherspotPrimeAddress = '0x07ff85757f5209534EB601E1CA60d72807ECE0bC';
const providerWalletAddress = provider.address;
describe('useWalletAddress()', () => {
+ it('returns default type wallet address if no provided type', async () => {
+ const wrapper = ({ children }) => (
+
+ {children}
+
+ );
+
+ const { result } = renderHook(() => useWalletAddress(), { wrapper });
+
+ await waitFor(() => expect(result.current).not.toBe(undefined));
+ expect(result.current).toEqual(etherspotPrimeAddress);
+ });
+
it('returns wallet address by type', async () => {
const wrapper = ({ children }) => (
@@ -20,15 +32,11 @@ describe('useWalletAddress()', () => {
);
const { result, rerender } = renderHook(({ providerType }) => useWalletAddress(providerType), {
- initialProps: { providerType: 'etherspot' },
+ initialProps: { providerType: 'etherspot-prime' },
wrapper,
});
await waitFor(() => expect(result.current).not.toBe(undefined));
- expect(result.current).toEqual(etherspotAddress);
-
- rerender({ providerType: 'etherspot-prime' });
- await waitFor(() => expect(result.current).not.toBe(etherspotAddress));
expect(result.current).toEqual(etherspotPrimeAddress);
rerender({ providerType: 'provider' });
@@ -44,12 +52,12 @@ describe('useWalletAddress()', () => {
);
const { result, rerender } = renderHook(({ providerType }) => useWalletAddress(providerType), {
- initialProps: { providerType: 'etherspot' },
+ initialProps: { providerType: 'etherspot-prime' },
wrapper,
});
await waitFor(() => expect(result.current).not.toBe(undefined));
- expect(result.current).toEqual(etherspotAddress);
+ expect(result.current).toEqual(etherspotPrimeAddress);
rerender({ providerType: 'whatever' });
expect(result.current).toEqual(undefined);
diff --git a/package.json b/package.json
index a891beb..23464b0 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@etherspot/transaction-kit",
"description": "React Etherspot Transaction Kit",
- "version": "0.5.1",
+ "version": "0.6.0",
"main": "dist/cjs/index.js",
"scripts": {
"rollup:build": "NODE_OPTIONS=--max-old-space-size=8192 rollup -c",
diff --git a/src/components/EtherspotBatch.tsx b/src/components/EtherspotBatch.tsx
index fc2a450..df5e977 100644
--- a/src/components/EtherspotBatch.tsx
+++ b/src/components/EtherspotBatch.tsx
@@ -18,7 +18,7 @@ interface EtherspotBatchProps extends IBatch {
children?: React.ReactNode;
}
-const EtherspotBatch = ({ children, chainId, gasTokenAddress, id: batchId }: EtherspotBatchProps) => {
+const EtherspotBatch = ({ children, chainId, id: batchId }: EtherspotBatchProps) => {
const context = useContext(EtherspotBatchesContext);
const existingBatchContext = useContext(EtherspotBatchContext);
const componentId = useId();
@@ -36,7 +36,6 @@ const EtherspotBatch = ({ children, chainId, gasTokenAddress, id: batchId }: Eth
const batch = {
id: batchId ?? componentId,
chainId,
- gasTokenAddress,
transactions: getObjectSortedByKeys(transactionsPerId),
};
@@ -48,7 +47,7 @@ const EtherspotBatch = ({ children, chainId, gasTokenAddress, id: batchId }: Eth
return current;
});
}
- }, [componentId, transactionsPerId, chainId, batchId, gasTokenAddress]);
+ }, [componentId, transactionsPerId, chainId, batchId]);
return (
diff --git a/src/components/EtherspotBatches.tsx b/src/components/EtherspotBatches.tsx
index a34c647..4113fe2 100644
--- a/src/components/EtherspotBatches.tsx
+++ b/src/components/EtherspotBatches.tsx
@@ -1,5 +1,4 @@
import React, { useContext, useEffect, useState } from 'react';
-import { PaymasterApi } from '@etherspot/prime-sdk';
// contexts
import EtherspotTransactionKitContext from '../contexts/EtherspotTransactionKitContext';
@@ -21,13 +20,14 @@ type EtherspotBatchesProps = IBatches & {
}
const EtherspotBatches = (props: EtherspotBatchesProps) => {
- let paymaster: PaymasterApi | undefined;
-
- const { children, skip, onEstimated, onSent, id: batchesId, via } = props;
-
- if (via === 'etherspot-prime') {
- paymaster = props.paymaster;
- }
+ const {
+ children,
+ skip,
+ onEstimated,
+ onSent,
+ id: batchesId,
+ paymaster,
+ } = props;
const context = useContext(EtherspotTransactionKitContext);
const existingBatchesContext = useContext(EtherspotBatchesContext);
@@ -55,13 +55,9 @@ const EtherspotBatches = (props: EtherspotBatchesProps) => {
batches: getObjectSortedByKeys(batchesPerId),
onEstimated,
onSent,
- via,
+ paymaster,
};
- if (groupedBatch.via === 'etherspot-prime') {
- groupedBatch = { ...groupedBatch, paymaster };
- }
-
context.setGroupedBatchesPerId((current) => ({ ...current, [componentId]: groupedBatch }));
return () => {
@@ -70,7 +66,7 @@ const EtherspotBatches = (props: EtherspotBatchesProps) => {
return current;
});
}
- }, [componentId, batchesPerId, skip, batchesId, via, paymaster]);
+ }, [componentId, batchesPerId, skip, batchesId, paymaster]);
return (
diff --git a/src/hooks/useWalletAddress.ts b/src/hooks/useWalletAddress.ts
index d0728db..af758a8 100644
--- a/src/hooks/useWalletAddress.ts
+++ b/src/hooks/useWalletAddress.ts
@@ -1,6 +1,5 @@
import { useEffect, useMemo, useState } from 'react';
import { useEtherspot } from '@etherspot/react-etherspot';
-import { AccountTypes } from 'etherspot';
// types
import { IWalletType } from '../types/EtherspotTransactionKit';
@@ -14,7 +13,7 @@ import useEtherspotTransactions from './useEtherspotTransactions';
* @param chainId {number} - Chain ID
* @returns {string | undefined} - wallet address by its type
*/
-const useWalletAddress = (walletType: IWalletType = 'etherspot', chainId?: number): string | undefined => {
+const useWalletAddress = (walletType: IWalletType = 'etherspot-prime', chainId?: number): string | undefined => {
const [walletAddress, setWalletAddress] = useState<(string | undefined)>(undefined);
const { connect, getSdkForChainId, providerWalletAddress, chainId: defaultChainId } = useEtherspot();
const { getEtherspotPrimeSdkForChainId } = useEtherspotTransactions();
@@ -35,13 +34,7 @@ const useWalletAddress = (walletType: IWalletType = 'etherspot', chainId?: numbe
console.warn(`Unable to get SDK for chain ID ${walletAddressChainId}`);
}
- if (walletType === 'etherspot') {
- if (sdkForChainId?.state?.account?.type !== AccountTypes.Contract) {
- await connect(walletAddressChainId);
- }
-
- updatedWalletAddress = sdkForChainId?.state?.account?.address;
- } else if (walletType === 'etherspot-prime') {
+ if (walletType === 'etherspot-prime') {
const etherspotPrimeSdk = await getEtherspotPrimeSdkForChainId(walletAddressChainId);
if (!etherspotPrimeSdk) {
console.warn(`Unable to get Etherspot Prime SDK for chain ID ${walletAddressChainId}`);
diff --git a/src/providers/EtherspotTransactionKitContextProvider.tsx b/src/providers/EtherspotTransactionKitContextProvider.tsx
index 7f76653..39c4108 100644
--- a/src/providers/EtherspotTransactionKitContextProvider.tsx
+++ b/src/providers/EtherspotTransactionKitContextProvider.tsx
@@ -1,14 +1,12 @@
import { useEtherspot } from '@etherspot/react-etherspot';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { PrimeSdk, isWalletProvider, Web3WalletProvider } from '@etherspot/prime-sdk';
-import { AccountStates } from 'etherspot';
-import { ethers } from 'ethers';
// contexts
import EtherspotTransactionKitContext from '../contexts/EtherspotTransactionKitContext';
// utils
-import { getObjectSortedByKeys, isTestnetChainId, parseEtherspotErrorMessageIfAvailable } from '../utils/common';
+import { getObjectSortedByKeys } from '../utils/common';
// types
import { EstimatedBatch, IBatch, IBatches, IEstimatedBatches, ISentBatches, SentBatch } from '../types/EtherspotTransactionKit';
@@ -23,15 +21,9 @@ let isSdkConnecting: Promise | undefined;
let etherspotPrimeSdkPerChain: { [chainId: number]: PrimeSdk } = {};
const EtherspotTransactionKitContextProvider = ({ children }: EtherspotTransactionKitContextProviderProps) => {
- const { getSdkForChainId, connect, provider, chainId } = useEtherspot();
+ const { provider, chainId } = useEtherspot();
const [groupedBatchesPerId, setGroupedBatchesPerId] = useState>({});
- const connectToSdkForChainIfNeeded = async (chainId: number) => {
- isSdkConnecting = connect(chainId);
- await isSdkConnecting;
- isSdkConnecting = undefined;
- }
-
const estimate = async (
batchesIds?: string[],
forSending: boolean = false,
@@ -61,58 +53,24 @@ const EtherspotTransactionKitContextProvider = ({ children }: EtherspotTransacti
continue;
}
- if (groupedBatch.via === 'etherspot-prime') {
- const etherspotPrimeSdkForChainId = await getEtherspotPrimeSdkForChainId(batchChainId);
- if (!etherspotPrimeSdkForChainId) {
- estimatedBatches.push({ ...batch, errorMessage: 'Failed to get Etherspot Prime SDK for chain!' });
- continue;
- }
-
- try {
- if (!forSending) await etherspotPrimeSdkForChainId.clearUserOpsFromBatch();
-
- await Promise.all(batch.transactions.map(async ({ to, value, data }) => {
- await etherspotPrimeSdkForChainId.addUserOpsToBatch(({ to, value, data }));
- }));
-
- // TODO: add actual estimation when available on Etherspot Prime
- estimatedBatches.push({ ...batch, cost: ethers.BigNumber.from(0) });
- } catch (e) {
- estimatedBatches.push({ ...batch, errorMessage: (e instanceof Error && e.message) || 'Failed to estimate!' });
- }
- } else {
- const sdkForChainId = getSdkForChainId(batchChainId);
-
- if (!sdkForChainId) {
- estimatedBatches.push({ ...batch, errorMessage: 'Failed to get Etherspot SDK for chain!' });
- continue;
- }
-
- await connectToSdkForChainIfNeeded(batchChainId);
-
- try {
- if (!forSending) sdkForChainId.clearGatewayBatch();
-
- if (sdkForChainId.state.account.state === AccountStates.UnDeployed) {
- await sdkForChainId.batchDeployAccount();
- }
-
- await Promise.all(batch.transactions.map(async ({ to, value, data }) => {
- await sdkForChainId.batchExecuteAccountTransaction(({ to, value, data }));
- }));
-
- const { estimation } = await sdkForChainId.estimateGatewayBatch({ feeToken: batch.gasTokenAddress });
-
- const cost = batch.gasTokenAddress
- ? estimation.feeAmount
- : estimation.estimatedGasPrice.mul(estimation.estimatedGas)
-
- estimatedBatches.push({ ...batch, cost });
- } catch (e) {
- const rawErrorMessage = e instanceof Error && e.message;
- const errorMessage = parseEtherspotErrorMessageIfAvailable(rawErrorMessage || 'Failed to estimate!');
- estimatedBatches.push({ ...batch, errorMessage });
- }
+ const etherspotPrimeSdkForChainId = await getEtherspotPrimeSdkForChainId(batchChainId);
+ if (!etherspotPrimeSdkForChainId) {
+ estimatedBatches.push({ ...batch, errorMessage: 'Failed to get Etherspot Prime SDK for chain!' });
+ continue;
+ }
+
+ try {
+ if (!forSending) await etherspotPrimeSdkForChainId.clearUserOpsFromBatch();
+
+ await Promise.all(batch.transactions.map(async ({ to, value, data }) => {
+ await etherspotPrimeSdkForChainId.addUserOpsToBatch(({ to, value, data }));
+ }));
+
+ const userOp = await etherspotPrimeSdkForChainId.estimate(groupedBatch.paymaster);
+ const totalGas = await etherspotPrimeSdkForChainId.totalGasEstimated(userOp);
+ estimatedBatches.push({ ...batch, cost: totalGas, userOp });
+ } catch (e) {
+ estimatedBatches.push({ ...batch, errorMessage: (e instanceof Error && e.message) || 'Failed to estimate!' });
}
}
@@ -181,21 +139,13 @@ const EtherspotTransactionKitContextProvider = ({ children }: EtherspotTransacti
// clear any existing batches before new estimate & send
await Promise.all(groupedBatchesToClean.map(async ({
batches = [],
- via,
}) => Promise.all(batches.map(async (batch) => {
const batchChainId = batch.chainId ?? chainId;
- if (via === 'etherspot-prime') {
- const etherspotPrimeSdkForChainId = await getEtherspotPrimeSdkForChainId(batchChainId);
- if (!etherspotPrimeSdkForChainId) return;
- await etherspotPrimeSdkForChainId.clearUserOpsFromBatch();
- return;
- }
+ const etherspotPrimeSdkForChainId = await getEtherspotPrimeSdkForChainId(batchChainId);
+ if (!etherspotPrimeSdkForChainId) return;
- const sdkForChainId = getSdkForChainId(batchChainId);
- if (!sdkForChainId) return;
-
- sdkForChainId.clearGatewayBatch();
+ await etherspotPrimeSdkForChainId.clearUserOpsFromBatch();
}))));
const estimated = await estimate(batchesIds, true);
@@ -206,51 +156,32 @@ const EtherspotTransactionKitContextProvider = ({ children }: EtherspotTransacti
const sentBatches: SentBatch[] = [];
- const via = estimatedBatches.via ?? 'etherspot';
-
// send in same order as estimated
for (const estimatedBatch of estimatedBatches.estimatedBatches) {
const batchChainId = estimatedBatch.chainId ?? chainId
// return error message as provided by estimate
if (estimatedBatch.errorMessage) {
- sentBatches.push({ ...estimatedBatch, via, errorMessage: estimatedBatch.errorMessage })
+ sentBatches.push({ ...estimatedBatch, errorMessage: estimatedBatch.errorMessage })
+ continue;
+ }
+
+ const etherspotPrimeSdkForChainId = await getEtherspotPrimeSdkForChainId(batchChainId);
+ if (!etherspotPrimeSdkForChainId) {
+ sentBatches.push({ ...estimatedBatch, errorMessage: 'Failed to get Etherspot Prime SDK for chain!' });
+ continue;
+ }
+
+ if (!estimatedBatch.userOp) {
+ sentBatches.push({ ...estimatedBatch, errorMessage: 'Failed to get estimated UserOp!' });
continue;
}
- if (estimatedBatches.via === 'etherspot-prime') {
- const etherspotPrimeSdkForChainId = await getEtherspotPrimeSdkForChainId(batchChainId);
- if (!etherspotPrimeSdkForChainId) {
- sentBatches.push({ ...estimatedBatch, via, errorMessage: 'Failed to get Etherspot Prime SDK for chain!' });
- continue;
- }
-
- try {
- const estimated = await etherspotPrimeSdkForChainId.estimate(estimatedBatches.paymaster);
- const userOpHash = await etherspotPrimeSdkForChainId.send(estimated);
- sentBatches.push({ ...estimatedBatch, via, userOpHash });
- } catch (e) {
- sentBatches.push({ ...estimatedBatch, via, errorMessage: (e instanceof Error && e.message) || 'Failed to estimate!' });
- }
- } else {
- const sdkForChainId = getSdkForChainId(batchChainId);
- if (!sdkForChainId) {
- sentBatches.push({ ...estimatedBatch, via, errorMessage: 'Failed to get Etherspot SDK for chain!' });
- continue;
- }
-
- await connectToSdkForChainIfNeeded(batchChainId);
-
- try {
- // testnets does not have guards
- const guarded = !isTestnetChainId(batchChainId);
- const { hash: batchHash } = await sdkForChainId.submitGatewayBatch({ guarded });
- sentBatches.push({ ...estimatedBatch, via, batchHash });
- } catch (e) {
- const rawErrorMessage = e instanceof Error && e.message;
- const errorMessage = parseEtherspotErrorMessageIfAvailable(rawErrorMessage || 'Failed to send!');
- sentBatches.push({ ...estimatedBatch, via, errorMessage });
- }
+ try {
+ const userOpHash = await etherspotPrimeSdkForChainId.send(estimatedBatch.userOp);
+ sentBatches.push({ ...estimatedBatch, userOpHash });
+ } catch (e) {
+ sentBatches.push({ ...estimatedBatch, errorMessage: (e instanceof Error && e.message) || 'Failed to estimate!' });
}
}
diff --git a/src/types/EtherspotTransactionKit.ts b/src/types/EtherspotTransactionKit.ts
index ff38566..330d98f 100644
--- a/src/types/EtherspotTransactionKit.ts
+++ b/src/types/EtherspotTransactionKit.ts
@@ -1,5 +1,5 @@
import { Fragment, JsonFragment } from '@ethersproject/abi/src.ts/fragments';
-import { BigNumber, BigNumberish } from 'ethers';
+import { BigNumber, BigNumberish, BytesLike } from 'ethers';
import { ExchangeOffer } from 'etherspot';
import { Route } from '@lifi/sdk';
import { PaymasterApi } from '@etherspot/prime-sdk';
@@ -22,41 +22,29 @@ export interface IProviderWalletTransaction {
export interface IBatch {
id?: string;
chainId?: number;
- gasTokenAddress?: string;
transactions?: ITransaction[];
}
export interface EstimatedBatch extends IBatch {
errorMessage?: string;
cost?: BigNumber;
+ userOp?: UserOp;
}
-export interface EtherspotSentBatch extends EstimatedBatch {
- via: 'etherspot';
- batchHash?: string;
-}
export interface EtherspotPrimeSentBatch extends EstimatedBatch {
- via: 'etherspot-prime';
userOpHash?: string;
}
export type SentBatch = {
errorMessage?: string;
-} & (EtherspotSentBatch | EtherspotPrimeSentBatch);
-
-export type IBatches = IDefaultBatches
- | (IDefaultBatches<'etherspot-prime'> & IEtherspotPrimeBatchesExtra);
+} & (EtherspotPrimeSentBatch);
-interface IDefaultBatches {
+export interface IBatches {
id?: string;
batches?: IBatch[];
onEstimated?: (estimated: EstimatedBatch[]) => void;
onSent?: (sent: SentBatch[]) => void;
skip?: boolean;
- via?: T;
-}
-
-interface IEtherspotPrimeBatchesExtra {
paymaster?: PaymasterApi,
}
@@ -117,6 +105,20 @@ export interface IProviderWalletTransactionSent {
errorMessage?: string;
}
-export type IBatchesWalletType = 'etherspot' | 'etherspot-prime';
+export type IWalletType = 'provider' | 'etherspot-prime';
+
+type EtherspotPromiseOrValue = T | Promise;
-export type IWalletType = 'provider' | IBatchesWalletType;
+interface UserOp {
+ sender: EtherspotPromiseOrValue;
+ nonce: EtherspotPromiseOrValue;
+ initCode: EtherspotPromiseOrValue;
+ callData: EtherspotPromiseOrValue;
+ callGasLimit: EtherspotPromiseOrValue;
+ verificationGasLimit: EtherspotPromiseOrValue;
+ preVerificationGas: EtherspotPromiseOrValue;
+ maxFeePerGas: EtherspotPromiseOrValue;
+ maxPriorityFeePerGas: EtherspotPromiseOrValue;
+ paymasterAndData: EtherspotPromiseOrValue;
+ signature: EtherspotPromiseOrValue;
+}