@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.
@@ -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 { type HttpTransport, type PublicClient, isAddress, publicActions } from "viem";
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 { createKernelAccountClient } from "@zerodev/sdk";
2
- import { ENTRYPOINT_ADDRESS_V06, ENTRYPOINT_ADDRESS_V07 } from "permissionless";
3
- import { createPublicClient, http } from "viem";
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, getBundlerRPC, viemNetworks } from "../chains";
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
- const kernelAccountClient: SmartWalletClient = createKernelAccountClient({
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
- chain: viemNetworks[chain],
74
- entryPoint: account.entryPoint,
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
- const smartAccountClient = this.clientDecorator.decorate({
103
+ return this.clientDecorator.decorate({
86
104
  crossmintChain: chain,
87
- smartAccountClient: kernelAccountClient,
105
+ smartAccountClient,
88
106
  });
89
-
90
- return new EVMSmartWallet(this.crossmintService, smartAccountClient, publicClient, chain);
91
107
  }
92
108
  }
@@ -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;