@crossmint/client-sdk-smart-wallet 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +4 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -3
- package/dist/index.d.ts +51 -3
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/blockchain/chains.ts +0 -17
- package/src/blockchain/rpc.ts +7 -0
- package/src/blockchain/wallets/EVMSmartWallet.ts +81 -1
- package/src/blockchain/wallets/SendTransactionService.test.ts +163 -0
- package/src/blockchain/wallets/SendTransactionService.ts +186 -0
- package/src/blockchain/wallets/paymaster.ts +4 -0
- package/src/blockchain/wallets/service.ts +29 -13
- package/src/utils/constants.ts +0 -2
package/src/blockchain/rpc.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { blockchainToChainId } from "@crossmint/common-sdk-base";
|
|
2
|
+
|
|
1
3
|
import { SmartWalletChain } from "./chains";
|
|
2
4
|
|
|
3
5
|
const ALCHEMY_API_KEY = "-7M6vRDBDknwvMxnqah_jbcieWg0qad9";
|
|
6
|
+
const PIMLICO_API_KEY = "pim_9dKmQPxiTCvtbUNF7XFBbA";
|
|
4
7
|
|
|
5
8
|
export const ALCHEMY_RPC_SUBDOMAIN: Record<SmartWalletChain, string> = {
|
|
6
9
|
polygon: "polygon-mainnet",
|
|
@@ -16,3 +19,7 @@ export const ALCHEMY_RPC_SUBDOMAIN: Record<SmartWalletChain, string> = {
|
|
|
16
19
|
export function getAlchemyRPC(chain: SmartWalletChain): string {
|
|
17
20
|
return `https://${ALCHEMY_RPC_SUBDOMAIN[chain]}.g.alchemy.com/v2/${ALCHEMY_API_KEY}`;
|
|
18
21
|
}
|
|
22
|
+
|
|
23
|
+
export function getPimlicoBundlerRPC(chain: SmartWalletChain): string {
|
|
24
|
+
return `https://api.pimlico.io/v2/${blockchainToChainId(chain)}/rpc?apikey=${PIMLICO_API_KEY}`;
|
|
25
|
+
}
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Abi,
|
|
3
|
+
ContractFunctionArgs,
|
|
4
|
+
ContractFunctionName,
|
|
5
|
+
Hex,
|
|
6
|
+
type HttpTransport,
|
|
7
|
+
type PublicClient,
|
|
8
|
+
WriteContractParameters,
|
|
9
|
+
isAddress,
|
|
10
|
+
publicActions,
|
|
11
|
+
} from "viem";
|
|
2
12
|
|
|
3
13
|
import { TransferError } from "@crossmint/client-sdk-base";
|
|
4
14
|
|
|
@@ -8,6 +18,7 @@ import { SmartWalletClient } from "../../types/internal";
|
|
|
8
18
|
import type { TransferType } from "../../types/token";
|
|
9
19
|
import { SmartWalletChain } from "../chains";
|
|
10
20
|
import { transferParams } from "../transfer";
|
|
21
|
+
import { SendTransactionOptions, SendTransactionService } from "./SendTransactionService";
|
|
11
22
|
|
|
12
23
|
/**
|
|
13
24
|
* Smart wallet interface for EVM chains enhanced with Crossmint capabilities.
|
|
@@ -30,6 +41,7 @@ export class EVMSmartWallet {
|
|
|
30
41
|
*/
|
|
31
42
|
public: PublicClient;
|
|
32
43
|
};
|
|
44
|
+
private readonly sendTransactionService: SendTransactionService;
|
|
33
45
|
|
|
34
46
|
constructor(
|
|
35
47
|
private readonly crossmintService: CrossmintWalletService,
|
|
@@ -43,6 +55,7 @@ export class EVMSmartWallet {
|
|
|
43
55
|
wallet: accountClient,
|
|
44
56
|
public: publicClient,
|
|
45
57
|
};
|
|
58
|
+
this.sendTransactionService = new SendTransactionService(publicClient);
|
|
46
59
|
}
|
|
47
60
|
|
|
48
61
|
/**
|
|
@@ -110,4 +123,71 @@ export class EVMSmartWallet {
|
|
|
110
123
|
public async nfts() {
|
|
111
124
|
return this.crossmintService.fetchNFTs(this.address, this.chain);
|
|
112
125
|
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Sends a contract call transaction and returns the hash of a confirmed transaction.
|
|
129
|
+
* @param address the address of the contract to be called
|
|
130
|
+
* @param abi the ABI of the contract - ***should be defined as a typed variable*** to enable type checking of the contract arguments, see https://viem.sh/docs/typescript#type-inference for guidance
|
|
131
|
+
* @param functionName the name of the smart contract function to be called
|
|
132
|
+
* @param args the arguments to be passed to the function
|
|
133
|
+
* @returns The transaction hash.
|
|
134
|
+
* @throws `SendTransactionError` if the transaction fails to send. Contains the error thrown by the viem client.
|
|
135
|
+
* @throws `SendTransactionExecutionRevertedError`, a subclass of `SendTransactionError` if the transaction fails due to a contract execution error.
|
|
136
|
+
*
|
|
137
|
+
* **Passing a typed ABI:**
|
|
138
|
+
* @example
|
|
139
|
+
* const abi = [{
|
|
140
|
+
* "inputs": [
|
|
141
|
+
* {
|
|
142
|
+
* "internalType": "address",
|
|
143
|
+
* "name": "recipient",
|
|
144
|
+
* "type": "address"
|
|
145
|
+
* },
|
|
146
|
+
* ],
|
|
147
|
+
* "name": "mintNFT",
|
|
148
|
+
* "outputs": [],
|
|
149
|
+
* "stateMutability": "nonpayable",
|
|
150
|
+
* "type": "function"
|
|
151
|
+
* }] as const;
|
|
152
|
+
*
|
|
153
|
+
* await wallet.executeContract({
|
|
154
|
+
* address: contractAddress,
|
|
155
|
+
* abi,
|
|
156
|
+
* functionName: "mintNFT",
|
|
157
|
+
* args: [recipientAddress],
|
|
158
|
+
* });
|
|
159
|
+
*/
|
|
160
|
+
public async executeContract<
|
|
161
|
+
const TAbi extends Abi | readonly unknown[],
|
|
162
|
+
TFunctionName extends ContractFunctionName<TAbi, "nonpayable" | "payable"> = ContractFunctionName<
|
|
163
|
+
TAbi,
|
|
164
|
+
"nonpayable" | "payable"
|
|
165
|
+
>,
|
|
166
|
+
TArgs extends ContractFunctionArgs<TAbi, "nonpayable" | "payable", TFunctionName> = ContractFunctionArgs<
|
|
167
|
+
TAbi,
|
|
168
|
+
"nonpayable" | "payable",
|
|
169
|
+
TFunctionName
|
|
170
|
+
>
|
|
171
|
+
>({
|
|
172
|
+
address,
|
|
173
|
+
abi,
|
|
174
|
+
functionName,
|
|
175
|
+
args,
|
|
176
|
+
value,
|
|
177
|
+
config,
|
|
178
|
+
}: Omit<WriteContractParameters<TAbi, TFunctionName, TArgs>, "chain" | "account"> & {
|
|
179
|
+
config?: Partial<SendTransactionOptions>;
|
|
180
|
+
}): Promise<Hex> {
|
|
181
|
+
return this.sendTransactionService.sendTransaction(
|
|
182
|
+
{
|
|
183
|
+
address,
|
|
184
|
+
abi: abi as Abi,
|
|
185
|
+
functionName,
|
|
186
|
+
args,
|
|
187
|
+
value,
|
|
188
|
+
},
|
|
189
|
+
this.accountClient,
|
|
190
|
+
config
|
|
191
|
+
);
|
|
192
|
+
}
|
|
113
193
|
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { SmartAccountClient } from "permissionless";
|
|
2
|
+
import { SmartAccount } from "permissionless/accounts";
|
|
3
|
+
import { EntryPoint } from "permissionless/types";
|
|
4
|
+
import {
|
|
5
|
+
BaseError,
|
|
6
|
+
Chain,
|
|
7
|
+
ContractFunctionRevertedError,
|
|
8
|
+
PublicClient,
|
|
9
|
+
SimulateContractReturnType,
|
|
10
|
+
TransactionReceipt,
|
|
11
|
+
Transport,
|
|
12
|
+
zeroAddress,
|
|
13
|
+
} from "viem";
|
|
14
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
15
|
+
import { mock } from "vitest-mock-extended";
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
EVMSendTransactionError,
|
|
19
|
+
EVMSendTransactionExecutionRevertedError,
|
|
20
|
+
SendTransactionService,
|
|
21
|
+
} from "./SendTransactionService";
|
|
22
|
+
|
|
23
|
+
function makeMockError<E extends Error, F extends object>(error: E, fields?: F): E & F {
|
|
24
|
+
const mockError = Object.create(error);
|
|
25
|
+
Object.assign(mockError, fields);
|
|
26
|
+
return mockError;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("SendTransactionService", () => {
|
|
30
|
+
const mockPublicClient = mock<PublicClient>();
|
|
31
|
+
const mockAccountClient = mock<SmartAccountClient<EntryPoint, Transport, Chain, SmartAccount<EntryPoint>>>();
|
|
32
|
+
const sendTransactionService = new SendTransactionService(mockPublicClient);
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
vi.resetAllMocks();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("Throws EVMSendTransactionExecutionRevertedError when a transaction simulation fails", async () => {
|
|
39
|
+
const mockError = makeMockError(BaseError.prototype, { walk: vi.fn() });
|
|
40
|
+
const mockRevertError = new ContractFunctionRevertedError({
|
|
41
|
+
abi: [],
|
|
42
|
+
functionName: "mockFunction",
|
|
43
|
+
message: "mockMessage",
|
|
44
|
+
});
|
|
45
|
+
mockRevertError.reason = "mockReason";
|
|
46
|
+
const mockRevertData = mock<ContractFunctionRevertedError["data"]>();
|
|
47
|
+
mockRevertError.data = mockRevertData;
|
|
48
|
+
mockError.walk.mockReturnValue(mockRevertError);
|
|
49
|
+
mockPublicClient.simulateContract.mockRejectedValue(mockError);
|
|
50
|
+
let rejected = false;
|
|
51
|
+
try {
|
|
52
|
+
await sendTransactionService.sendTransaction(
|
|
53
|
+
{
|
|
54
|
+
address: zeroAddress,
|
|
55
|
+
abi: [],
|
|
56
|
+
functionName: "mockFunction",
|
|
57
|
+
args: [],
|
|
58
|
+
},
|
|
59
|
+
mockAccountClient
|
|
60
|
+
);
|
|
61
|
+
} catch (e) {
|
|
62
|
+
rejected = true;
|
|
63
|
+
expect(e).toBeInstanceOf(EVMSendTransactionExecutionRevertedError);
|
|
64
|
+
expect((e as EVMSendTransactionExecutionRevertedError).stage).toBe("simulation");
|
|
65
|
+
expect((e as EVMSendTransactionExecutionRevertedError).reason).toBe("mockReason");
|
|
66
|
+
expect((e as EVMSendTransactionExecutionRevertedError).data == mockRevertData).toBe(true);
|
|
67
|
+
}
|
|
68
|
+
expect(rejected).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("Throws EVMSendTransactionExecutionRevertedError when a transaction reverts on chain", async () => {
|
|
72
|
+
const mockReceipt = mock<TransactionReceipt>();
|
|
73
|
+
mockReceipt.status = "reverted";
|
|
74
|
+
mockPublicClient.waitForTransactionReceipt.mockResolvedValueOnce(mockReceipt);
|
|
75
|
+
let rejected = false;
|
|
76
|
+
try {
|
|
77
|
+
await sendTransactionService.sendTransaction(
|
|
78
|
+
{
|
|
79
|
+
address: zeroAddress,
|
|
80
|
+
abi: [],
|
|
81
|
+
functionName: "mockFunction",
|
|
82
|
+
args: [],
|
|
83
|
+
},
|
|
84
|
+
mockAccountClient
|
|
85
|
+
);
|
|
86
|
+
} catch (e) {
|
|
87
|
+
rejected = true;
|
|
88
|
+
expect(e).toBeInstanceOf(EVMSendTransactionExecutionRevertedError);
|
|
89
|
+
expect((e as EVMSendTransactionExecutionRevertedError).stage).toBe("execution");
|
|
90
|
+
}
|
|
91
|
+
expect(rejected).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("Throws a confirmation error when a transaction confirmation fails", async () => {
|
|
95
|
+
const mockError = makeMockError(BaseError.prototype);
|
|
96
|
+
mockPublicClient.waitForTransactionReceipt.mockRejectedValue(mockError);
|
|
97
|
+
let rejected = false;
|
|
98
|
+
try {
|
|
99
|
+
await sendTransactionService.sendTransaction(
|
|
100
|
+
{
|
|
101
|
+
address: zeroAddress,
|
|
102
|
+
abi: [],
|
|
103
|
+
functionName: "mockFunction",
|
|
104
|
+
args: [],
|
|
105
|
+
},
|
|
106
|
+
mockAccountClient
|
|
107
|
+
);
|
|
108
|
+
} catch (e) {
|
|
109
|
+
rejected = true;
|
|
110
|
+
expect(e).toBeInstanceOf(EVMSendTransactionError);
|
|
111
|
+
expect((e as EVMSendTransactionError).stage).toBe("confirmation");
|
|
112
|
+
}
|
|
113
|
+
expect(rejected).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("Throws EVMSendTransactionError when a transaction fails to send", async () => {
|
|
117
|
+
const mockError = makeMockError(BaseError.prototype);
|
|
118
|
+
mockAccountClient.writeContract.mockRejectedValue(mockError);
|
|
119
|
+
let rejected = false;
|
|
120
|
+
try {
|
|
121
|
+
await sendTransactionService.sendTransaction(
|
|
122
|
+
{
|
|
123
|
+
address: zeroAddress,
|
|
124
|
+
abi: [],
|
|
125
|
+
functionName: "mockFunction",
|
|
126
|
+
args: [],
|
|
127
|
+
},
|
|
128
|
+
mockAccountClient
|
|
129
|
+
);
|
|
130
|
+
} catch (e) {
|
|
131
|
+
rejected = true;
|
|
132
|
+
expect(e).toBeInstanceOf(EVMSendTransactionError);
|
|
133
|
+
expect((e as EVMSendTransactionError).stage).toBe("send");
|
|
134
|
+
}
|
|
135
|
+
expect(rejected).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("Simulates before sending a transaction", async () => {
|
|
139
|
+
const mockReceipt = mock<TransactionReceipt>();
|
|
140
|
+
mockPublicClient.waitForTransactionReceipt.mockResolvedValueOnce(mockReceipt);
|
|
141
|
+
const callOrder: string[] = [];
|
|
142
|
+
mockPublicClient.simulateContract.mockImplementation(async () => {
|
|
143
|
+
callOrder.push("simulateContract");
|
|
144
|
+
return mock<SimulateContractReturnType>();
|
|
145
|
+
});
|
|
146
|
+
mockAccountClient.writeContract.mockImplementation(async () => {
|
|
147
|
+
callOrder.push("sendTransaction");
|
|
148
|
+
return "0xmockTxHash";
|
|
149
|
+
});
|
|
150
|
+
await expect(
|
|
151
|
+
sendTransactionService.sendTransaction(
|
|
152
|
+
{
|
|
153
|
+
address: zeroAddress,
|
|
154
|
+
abi: [],
|
|
155
|
+
functionName: "mockFunction",
|
|
156
|
+
args: [],
|
|
157
|
+
},
|
|
158
|
+
mockAccountClient
|
|
159
|
+
)
|
|
160
|
+
).resolves.toBeDefined();
|
|
161
|
+
expect(callOrder).toEqual(["simulateContract", "sendTransaction"]);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import type { SmartAccountClient } from "permissionless";
|
|
2
|
+
import type { EntryPoint } from "permissionless/_types/types";
|
|
3
|
+
import type { SmartAccount } from "permissionless/accounts";
|
|
4
|
+
import {
|
|
5
|
+
type Abi,
|
|
6
|
+
type Address,
|
|
7
|
+
BaseError,
|
|
8
|
+
type Chain,
|
|
9
|
+
ContractFunctionRevertedError,
|
|
10
|
+
type Hex,
|
|
11
|
+
type PublicClient,
|
|
12
|
+
type TransactionReceipt,
|
|
13
|
+
type Transport,
|
|
14
|
+
} from "viem";
|
|
15
|
+
|
|
16
|
+
import { CrossmintSDKError, WalletErrorCode } from "@crossmint/client-sdk-base";
|
|
17
|
+
|
|
18
|
+
export type TransactionServiceTransactionRequest = {
|
|
19
|
+
address: Address;
|
|
20
|
+
abi: Abi;
|
|
21
|
+
functionName: string;
|
|
22
|
+
args: any;
|
|
23
|
+
value?: bigint;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type SendTransactionFailureStage = "simulation" | "send" | "confirmation" | "execution";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Error thrown when a transaction fails to send.
|
|
30
|
+
* @param viemError The error thrown by the viem client. See https://viem.sh/docs/glossary/errors.html
|
|
31
|
+
*/
|
|
32
|
+
export class EVMSendTransactionError extends CrossmintSDKError {
|
|
33
|
+
constructor(
|
|
34
|
+
message: string,
|
|
35
|
+
public readonly viemError: BaseError,
|
|
36
|
+
public readonly stage: SendTransactionFailureStage,
|
|
37
|
+
code = WalletErrorCode.SEND_TRANSACTION_FAILED
|
|
38
|
+
) {
|
|
39
|
+
super(message, code);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Error thrown when a transaction fails due to a contract execution error.
|
|
45
|
+
* @param viemError The error thrown by the viem client. See https://viem.sh/docs/glossary/errors.html
|
|
46
|
+
* @param data The name and arguments of the revert error from the provided ABI
|
|
47
|
+
* @param reason The revert error if it is a string and not an ABI error
|
|
48
|
+
* @example
|
|
49
|
+
* try {
|
|
50
|
+
* await wallet.executeContract({
|
|
51
|
+
* address: contractAddress,
|
|
52
|
+
* abi,
|
|
53
|
+
* functionName: "mintNFT",
|
|
54
|
+
* args: [recipientAddress],
|
|
55
|
+
* });
|
|
56
|
+
* } catch (e) {
|
|
57
|
+
* if (e instanceof SendTransactionExecutionRevertedError) {
|
|
58
|
+
* alert(`Transaction reverted: ${e.message}`);
|
|
59
|
+
* }
|
|
60
|
+
* throw e;
|
|
61
|
+
* }
|
|
62
|
+
*/
|
|
63
|
+
export class EVMSendTransactionExecutionRevertedError extends EVMSendTransactionError {
|
|
64
|
+
constructor(
|
|
65
|
+
message: string,
|
|
66
|
+
public readonly viemError: BaseError,
|
|
67
|
+
revertError: ContractFunctionRevertedError,
|
|
68
|
+
public readonly txId: string | undefined,
|
|
69
|
+
public readonly stage: SendTransactionFailureStage,
|
|
70
|
+
public readonly data = revertError.data,
|
|
71
|
+
public readonly reason = revertError.reason
|
|
72
|
+
) {
|
|
73
|
+
super(message, viemError, stage, WalletErrorCode.SEND_TRANSACTION_EXECUTION_REVERTED);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface SendTransactionOptions {
|
|
78
|
+
/**
|
|
79
|
+
* The number of confirmations to wait for before yielding the transaction hash.
|
|
80
|
+
*/
|
|
81
|
+
confirmations: number;
|
|
82
|
+
/**
|
|
83
|
+
* The timeout in milliseconds to wait for a transaction to confirm before throwing an error.
|
|
84
|
+
*/
|
|
85
|
+
transactionConfirmationTimeout: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export class SendTransactionService {
|
|
89
|
+
constructor(
|
|
90
|
+
private publicClient: PublicClient,
|
|
91
|
+
private defaultSendTransactionOptions: SendTransactionOptions = {
|
|
92
|
+
confirmations: 2,
|
|
93
|
+
transactionConfirmationTimeout: 30_000,
|
|
94
|
+
}
|
|
95
|
+
) {}
|
|
96
|
+
|
|
97
|
+
async sendTransaction(
|
|
98
|
+
request: TransactionServiceTransactionRequest,
|
|
99
|
+
client: SmartAccountClient<EntryPoint, Transport, Chain, SmartAccount<EntryPoint>>,
|
|
100
|
+
config: Partial<SendTransactionOptions> = {}
|
|
101
|
+
): Promise<Hex> {
|
|
102
|
+
const { confirmations, transactionConfirmationTimeout } = this.getConfig(config);
|
|
103
|
+
|
|
104
|
+
await this.simulateCall(request, undefined, "simulation");
|
|
105
|
+
|
|
106
|
+
let hash;
|
|
107
|
+
try {
|
|
108
|
+
hash = await client.writeContract({
|
|
109
|
+
...request,
|
|
110
|
+
account: client.account,
|
|
111
|
+
chain: client.chain,
|
|
112
|
+
});
|
|
113
|
+
} catch (e) {
|
|
114
|
+
if (e instanceof BaseError) {
|
|
115
|
+
throw new EVMSendTransactionError(e.message, e, "send");
|
|
116
|
+
}
|
|
117
|
+
throw e;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const receipt = await this.publicClient.waitForTransactionReceipt({
|
|
122
|
+
hash,
|
|
123
|
+
confirmations,
|
|
124
|
+
timeout: transactionConfirmationTimeout,
|
|
125
|
+
});
|
|
126
|
+
return await this.handleReceipt(receipt, request);
|
|
127
|
+
} catch (e) {
|
|
128
|
+
if (e instanceof BaseError) {
|
|
129
|
+
throw new EVMSendTransactionError(e.message, e, "confirmation");
|
|
130
|
+
}
|
|
131
|
+
throw e;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private getConfig(config: Partial<SendTransactionOptions>): SendTransactionOptions {
|
|
136
|
+
return {
|
|
137
|
+
...this.defaultSendTransactionOptions,
|
|
138
|
+
...config,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private async handleReceipt(receipt: TransactionReceipt, request: TransactionServiceTransactionRequest) {
|
|
143
|
+
if (receipt.status === "reverted") {
|
|
144
|
+
// This should revert and throw the full reason
|
|
145
|
+
await this.simulateCall(request, receipt.transactionHash, "execution");
|
|
146
|
+
// Otherwise, throw a generic error (this should practically never happen)
|
|
147
|
+
throw new EVMSendTransactionExecutionRevertedError(
|
|
148
|
+
"Transaction reverted but unable to detect the reason",
|
|
149
|
+
new ContractFunctionRevertedError({ abi: request.abi as Abi, functionName: request.functionName }),
|
|
150
|
+
new ContractFunctionRevertedError({ abi: request.abi as Abi, functionName: request.functionName }),
|
|
151
|
+
receipt.transactionHash,
|
|
152
|
+
"execution"
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return receipt.transactionHash;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private async simulateCall(
|
|
159
|
+
request: TransactionServiceTransactionRequest,
|
|
160
|
+
passthroughTxId: string | undefined,
|
|
161
|
+
stage: SendTransactionFailureStage
|
|
162
|
+
) {
|
|
163
|
+
try {
|
|
164
|
+
await this.publicClient.simulateContract({
|
|
165
|
+
...request,
|
|
166
|
+
account: request.address,
|
|
167
|
+
chain: this.publicClient.chain,
|
|
168
|
+
});
|
|
169
|
+
} catch (e) {
|
|
170
|
+
if (e instanceof BaseError) {
|
|
171
|
+
const revertError = e.walk((err) => err instanceof ContractFunctionRevertedError);
|
|
172
|
+
if (revertError instanceof ContractFunctionRevertedError) {
|
|
173
|
+
throw new EVMSendTransactionExecutionRevertedError(
|
|
174
|
+
revertError.message,
|
|
175
|
+
e,
|
|
176
|
+
revertError,
|
|
177
|
+
passthroughTxId,
|
|
178
|
+
stage
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
throw new EVMSendTransactionError(e.message, e, stage);
|
|
182
|
+
}
|
|
183
|
+
throw e;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CrossmintWalletService } from "@/api/CrossmintWalletService";
|
|
2
2
|
import { Middleware } from "permissionless/actions/smartAccount";
|
|
3
|
+
import { PimlicoBundlerClient } from "permissionless/clients/pimlico";
|
|
3
4
|
import { EntryPoint } from "permissionless/types/entrypoint";
|
|
4
5
|
|
|
5
6
|
import { UserParams } from "../../types/params";
|
|
@@ -11,11 +12,13 @@ export function usePaymaster(chain: SmartWalletChain) {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export function paymasterMiddleware({
|
|
15
|
+
bundlerClient,
|
|
14
16
|
entryPoint,
|
|
15
17
|
chain,
|
|
16
18
|
walletService,
|
|
17
19
|
user,
|
|
18
20
|
}: {
|
|
21
|
+
bundlerClient: PimlicoBundlerClient<EntryPoint>;
|
|
19
22
|
entryPoint: EntryPoint;
|
|
20
23
|
chain: SmartWalletChain;
|
|
21
24
|
walletService: CrossmintWalletService;
|
|
@@ -23,6 +26,7 @@ export function paymasterMiddleware({
|
|
|
23
26
|
}): Middleware<EntryPoint> {
|
|
24
27
|
return {
|
|
25
28
|
middleware: {
|
|
29
|
+
gasPrice: async () => (await bundlerClient.getUserOperationGasPrice()).fast,
|
|
26
30
|
sponsorUserOperation: async ({ userOperation }) => {
|
|
27
31
|
const { sponsorUserOpParams } = await walletService.sponsorUserOperation(
|
|
28
32
|
user,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ENTRYPOINT_ADDRESS_V06, ENTRYPOINT_ADDRESS_V07 } from "permissionless";
|
|
3
|
-
import {
|
|
1
|
+
import { KernelSmartAccount } from "@zerodev/sdk";
|
|
2
|
+
import { ENTRYPOINT_ADDRESS_V06, ENTRYPOINT_ADDRESS_V07, createSmartAccountClient } from "permissionless";
|
|
3
|
+
import { createPimlicoBundlerClient } from "permissionless/clients/pimlico";
|
|
4
|
+
import { EntryPoint } from "permissionless/types/entrypoint";
|
|
5
|
+
import { HttpTransport, createPublicClient, http } from "viem";
|
|
4
6
|
|
|
5
7
|
import { blockchainToChainId } from "@crossmint/common-sdk-base";
|
|
6
8
|
|
|
@@ -8,8 +10,8 @@ import type { CrossmintWalletService } from "../../api/CrossmintWalletService";
|
|
|
8
10
|
import type { SmartWalletClient } from "../../types/internal";
|
|
9
11
|
import type { UserParams, WalletParams } from "../../types/params";
|
|
10
12
|
import { CURRENT_VERSION, ZERO_DEV_TYPE } from "../../utils/constants";
|
|
11
|
-
import { type SmartWalletChain,
|
|
12
|
-
import { getAlchemyRPC } from "../rpc";
|
|
13
|
+
import { type SmartWalletChain, viemNetworks } from "../chains";
|
|
14
|
+
import { getAlchemyRPC, getPimlicoBundlerRPC } from "../rpc";
|
|
13
15
|
import { EVMSmartWallet } from "./EVMSmartWallet";
|
|
14
16
|
import type { AccountConfigService } from "./account/config";
|
|
15
17
|
import type { AccountCreator } from "./account/creator";
|
|
@@ -68,13 +70,29 @@ export class SmartWalletService {
|
|
|
68
70
|
});
|
|
69
71
|
}
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
return new EVMSmartWallet(
|
|
74
|
+
this.crossmintService,
|
|
75
|
+
this.smartAccountClient(chain, account, user),
|
|
76
|
+
publicClient,
|
|
77
|
+
chain
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private smartAccountClient(
|
|
82
|
+
chain: SmartWalletChain,
|
|
83
|
+
account: KernelSmartAccount<EntryPoint, HttpTransport>,
|
|
84
|
+
user: UserParams
|
|
85
|
+
): SmartWalletClient {
|
|
86
|
+
const transport = http(getPimlicoBundlerRPC(chain));
|
|
87
|
+
const bundlerConfig = { chain: viemNetworks[chain], entryPoint: account.entryPoint };
|
|
88
|
+
const bundlerClient = createPimlicoBundlerClient({ ...bundlerConfig, transport });
|
|
89
|
+
const smartAccountClient: SmartWalletClient = createSmartAccountClient({
|
|
72
90
|
account,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
bundlerTransport: http(getBundlerRPC(chain)),
|
|
91
|
+
bundlerTransport: transport,
|
|
92
|
+
...bundlerConfig,
|
|
76
93
|
...(usePaymaster(chain) &&
|
|
77
94
|
paymasterMiddleware({
|
|
95
|
+
bundlerClient,
|
|
78
96
|
entryPoint: account.entryPoint,
|
|
79
97
|
chain,
|
|
80
98
|
walletService: this.crossmintService,
|
|
@@ -82,11 +100,9 @@ export class SmartWalletService {
|
|
|
82
100
|
})),
|
|
83
101
|
});
|
|
84
102
|
|
|
85
|
-
|
|
103
|
+
return this.clientDecorator.decorate({
|
|
86
104
|
crossmintChain: chain,
|
|
87
|
-
smartAccountClient
|
|
105
|
+
smartAccountClient,
|
|
88
106
|
});
|
|
89
|
-
|
|
90
|
-
return new EVMSmartWallet(this.crossmintService, smartAccountClient, publicClient, chain);
|
|
91
107
|
}
|
|
92
108
|
}
|
package/src/utils/constants.ts
CHANGED
|
@@ -3,7 +3,5 @@ export const CURRENT_VERSION = 0;
|
|
|
3
3
|
export const SCW_SERVICE = "SCW_SDK";
|
|
4
4
|
export const SDK_VERSION = "0.1.0";
|
|
5
5
|
export const API_VERSION = "2024-06-09";
|
|
6
|
-
export const BUNDLER_RPC = "https://rpc.zerodev.app/api/v2/bundler/";
|
|
7
|
-
export const PAYMASTER_RPC = "https://rpc.zerodev.app/api/v2/paymaster/";
|
|
8
6
|
export const SUPPORTED_KERNEL_VERSIONS = ["0.3.1", "0.3.0", "0.2.4"] as const;
|
|
9
7
|
export const SUPPORTED_ENTRYPOINT_VERSIONS = ["v0.6", "v0.7"] as const;
|