@crossmint/client-sdk-smart-wallet 0.1.2 → 0.1.3

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.
Files changed (42) hide show
  1. package/dist/index.cjs +4 -1
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +254 -119
  4. package/dist/index.d.ts +254 -119
  5. package/dist/index.js +4 -1
  6. package/dist/index.js.map +1 -1
  7. package/package.json +4 -12
  8. package/src/SmartWalletSDK.test.ts +12 -13
  9. package/src/SmartWalletSDK.ts +33 -26
  10. package/src/api/APIErrorService.ts +10 -7
  11. package/src/api/BaseCrossmintService.ts +3 -4
  12. package/src/api/CrossmintWalletService.test.ts +9 -9
  13. package/src/api/CrossmintWalletService.ts +39 -16
  14. package/src/blockchain/chains.ts +25 -2
  15. package/src/blockchain/transfer.ts +1 -1
  16. package/src/blockchain/wallets/EVMSmartWallet.ts +49 -42
  17. package/src/blockchain/wallets/account/config.ts +60 -0
  18. package/src/blockchain/wallets/account/creator.ts +36 -0
  19. package/src/blockchain/wallets/account/eoa.ts +50 -0
  20. package/src/blockchain/wallets/{passkey.ts → account/passkey.ts} +32 -32
  21. package/src/blockchain/wallets/account/signer.ts +44 -0
  22. package/src/blockchain/wallets/account/strategy.ts +5 -0
  23. package/src/blockchain/wallets/clientDecorator.ts +6 -6
  24. package/src/blockchain/wallets/paymaster.ts +12 -15
  25. package/src/blockchain/wallets/service.ts +38 -143
  26. package/src/error/index.ts +25 -117
  27. package/src/error/processor.ts +5 -6
  28. package/src/index.ts +16 -12
  29. package/src/services/logging/ConsoleProvider.ts +3 -12
  30. package/src/services/logging/DatadogProvider.ts +1 -1
  31. package/src/types/internal.ts +41 -20
  32. package/src/types/{Config.ts → params.ts} +0 -5
  33. package/src/types/schema.ts +63 -0
  34. package/src/types/service.ts +31 -0
  35. package/src/utils/api.ts +39 -0
  36. package/src/utils/constants.ts +2 -0
  37. package/src/utils/log.ts +1 -109
  38. package/src/utils/signer.ts +14 -16
  39. package/src/blockchain/wallets/eoa.ts +0 -49
  40. package/src/types/API.ts +0 -40
  41. package/src/utils/log.test.ts +0 -76
  42. /package/src/types/{Tokens.ts → token.ts} +0 -0
@@ -1,27 +1,26 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+
1
3
  import { SmartWalletSDK } from "./SmartWalletSDK";
2
- import { SmartWalletSDKInitParams } from "./types/Config";
3
4
 
4
- jest.mock("./services/logging");
5
+ vi.mock("./services/logging");
5
6
 
6
- Object.defineProperty(global, "window", {
7
- value: {
8
- location: {
9
- origin: "http://localhost",
10
- },
7
+ // Mock global window object
8
+ vi.stubGlobal("window", {
9
+ location: {
10
+ origin: "http://localhost",
11
11
  },
12
12
  });
13
13
 
14
14
  describe("SmartWalletSDK", () => {
15
15
  let sdk: SmartWalletSDK;
16
- const mockInitParams: SmartWalletSDKInitParams = {
17
- clientApiKey:
18
- "sk_staging_A4vDwAp4t5az6fVQMpQK6qapBnAqgpxrrD35TaFQnyKgxehNbd959uZeaHjNCadWDXrgLRAK1CxeasZjtYEq4TbFkKMBBvbQ9oinAxQf8LbHsSYW2DMzT8fBko3YGLq9t7ZiXZjmgkTioxGVUUjyLtWLeBKwNUDLgpshWjaoR7pKRnSE9SqhwjQbiK62VKiBTdA3KvHsyG9k8mLMcKrDyfXp",
19
- };
20
16
 
21
17
  beforeEach(() => {
22
18
  // Reset mocks before each test
23
- jest.clearAllMocks();
24
- sdk = SmartWalletSDK.init(mockInitParams);
19
+ vi.clearAllMocks();
20
+ sdk = SmartWalletSDK.init({
21
+ clientApiKey:
22
+ "sk_staging_A4vDwAp4t5az6fVQMpQK6qapBnAqgpxrrD35TaFQnyKgxehNbd959uZeaHjNCadWDXrgLRAK1CxeasZjtYEq4TbFkKMBBvbQ9oinAxQf8LbHsSYW2DMzT8fBko3YGLq9t7ZiXZjmgkTioxGVUUjyLtWLeBKwNUDLgpshWjaoR7pKRnSE9SqhwjQbiK62VKiBTdA3KvHsyG9k8mLMcKrDyfXp",
23
+ });
25
24
  });
26
25
 
27
26
  describe("init", () => {
@@ -3,24 +3,26 @@ import { stringify } from "viem";
3
3
  import { validateAPIKey } from "@crossmint/common-sdk-base";
4
4
 
5
5
  import { CrossmintWalletService } from "./api/CrossmintWalletService";
6
- import { SmartWalletChain } from "./blockchain/chains";
6
+ import type { SmartWalletChain } from "./blockchain/chains";
7
7
  import type { EVMSmartWallet } from "./blockchain/wallets";
8
+ import { AccountConfigFacade } from "./blockchain/wallets/account/config";
9
+ import { AccountCreator } from "./blockchain/wallets/account/creator";
10
+ import { EOACreationStrategy } from "./blockchain/wallets/account/eoa";
11
+ import { PasskeyCreationStrategy } from "./blockchain/wallets/account/passkey";
8
12
  import { ClientDecorator } from "./blockchain/wallets/clientDecorator";
9
13
  import { SmartWalletService } from "./blockchain/wallets/service";
10
- import { SmartWalletSDKError } from "./error";
14
+ import { SmartWalletError } from "./error";
11
15
  import { ErrorProcessor } from "./error/processor";
12
16
  import { DatadogProvider } from "./services/logging/DatadogProvider";
13
- import type { SmartWalletSDKInitParams, UserParams, WalletParams } from "./types/Config";
17
+ import type { SmartWalletSDKInitParams, UserParams, WalletParams } from "./types/params";
14
18
  import { isClient } from "./utils/environment";
15
- import { LoggerWrapper, logPerformance } from "./utils/log";
19
+ import { logPerformance } from "./utils/log";
16
20
 
17
- export class SmartWalletSDK extends LoggerWrapper {
21
+ export class SmartWalletSDK {
18
22
  private constructor(
19
23
  private readonly smartWalletService: SmartWalletService,
20
24
  private readonly errorProcessor: ErrorProcessor
21
- ) {
22
- super("SmartWalletSDK");
23
- }
25
+ ) {}
24
26
 
25
27
  /**
26
28
  * Initializes the SDK with the **client side** API key obtained from the Crossmint console.
@@ -28,7 +30,7 @@ export class SmartWalletSDK extends LoggerWrapper {
28
30
  */
29
31
  static init({ clientApiKey }: SmartWalletSDKInitParams): SmartWalletSDK {
30
32
  if (!isClient()) {
31
- throw new SmartWalletSDKError("Smart Wallet SDK should only be used client side.");
33
+ throw new SmartWalletError("Smart Wallet SDK should only be used client side.");
32
34
  }
33
35
 
34
36
  const validationResult = validateAPIKey(clientApiKey);
@@ -38,10 +40,19 @@ export class SmartWalletSDK extends LoggerWrapper {
38
40
 
39
41
  const crossmintService = new CrossmintWalletService(clientApiKey);
40
42
  const errorProcessor = new ErrorProcessor(new DatadogProvider());
41
- return new SmartWalletSDK(
42
- new SmartWalletService(crossmintService, new ClientDecorator(errorProcessor)),
43
- errorProcessor
43
+ const accountCreator = new AccountCreator(
44
+ new EOACreationStrategy(),
45
+ new PasskeyCreationStrategy(crossmintService.getPasskeyServerUrl(), clientApiKey)
46
+ );
47
+
48
+ const smartWalletService = new SmartWalletService(
49
+ crossmintService,
50
+ new AccountConfigFacade(crossmintService),
51
+ accountCreator,
52
+ new ClientDecorator(errorProcessor)
44
53
  );
54
+
55
+ return new SmartWalletSDK(smartWalletService, errorProcessor);
45
56
  }
46
57
 
47
58
  /**
@@ -58,19 +69,15 @@ export class SmartWalletSDK extends LoggerWrapper {
58
69
  chain: SmartWalletChain,
59
70
  walletParams: WalletParams = { signer: { type: "PASSKEY" } }
60
71
  ): Promise<EVMSmartWallet> {
61
- return logPerformance(
62
- "GET_OR_CREATE_WALLET",
63
- async () => {
64
- try {
65
- return await this.smartWalletService.getOrCreate(user, chain, walletParams);
66
- } catch (error: any) {
67
- throw this.errorProcessor.map(
68
- error,
69
- new SmartWalletSDKError(`Wallet creation failed: ${error.message}.`, stringify(error))
70
- );
71
- }
72
- },
73
- { user, chain }
74
- );
72
+ return logPerformance("GET_OR_CREATE_WALLET", async () => {
73
+ try {
74
+ return await this.smartWalletService.getOrCreate(user, chain, walletParams);
75
+ } catch (error: any) {
76
+ throw this.errorProcessor.map(
77
+ error,
78
+ new SmartWalletError(`Wallet creation failed: ${error.message}.`, stringify(error))
79
+ );
80
+ }
81
+ });
75
82
  }
76
83
  }
@@ -1,15 +1,18 @@
1
1
  import {
2
- AdminAlreadyUsedError,
3
2
  CrossmintServiceError,
4
3
  JWTDecryptionError,
5
4
  JWTExpiredError,
6
5
  JWTIdentifierError,
7
6
  JWTInvalidError,
8
- NonCustodialWalletsNotEnabledError,
9
7
  OutOfCreditsError,
10
- SmartWalletSDKError,
8
+ } from "@crossmint/client-sdk-base";
9
+
10
+ import {
11
+ AdminAlreadyUsedError,
12
+ SmartWalletError,
13
+ SmartWalletsNotEnabledError,
11
14
  UserWalletAlreadyCreatedError,
12
- } from "@/error";
15
+ } from "../error";
13
16
 
14
17
  export type CrossmintAPIErrorCodes =
15
18
  | "ERROR_JWT_INVALID"
@@ -22,7 +25,7 @@ export type CrossmintAPIErrorCodes =
22
25
 
23
26
  export class APIErrorService {
24
27
  constructor(
25
- private errors: Partial<Record<CrossmintAPIErrorCodes, (apiResponse: any) => SmartWalletSDKError>> = {
28
+ private errors: Record<CrossmintAPIErrorCodes, (apiResponse: any) => SmartWalletError> = {
26
29
  ERROR_JWT_INVALID: () => new JWTInvalidError(),
27
30
  ERROR_JWT_DECRYPTION: () => new JWTDecryptionError(),
28
31
  ERROR_JWT_EXPIRED: ({ expiredAt }: { expiredAt: string }) => new JWTExpiredError(new Date(expiredAt)),
@@ -31,7 +34,7 @@ export class APIErrorService {
31
34
  ERROR_USER_WALLET_ALREADY_CREATED: ({ userId }: { userId: string }) =>
32
35
  new UserWalletAlreadyCreatedError(userId),
33
36
  ERROR_ADMIN_SIGNER_ALREADY_USED: () => new AdminAlreadyUsedError(),
34
- ERROR_PROJECT_NONCUSTODIAL_WALLETS_NOT_ENABLED: () => new NonCustodialWalletsNotEnabledError(),
37
+ ERROR_PROJECT_NONCUSTODIAL_WALLETS_NOT_ENABLED: () => new SmartWalletsNotEnabledError(),
35
38
  }
36
39
  ) {}
37
40
 
@@ -64,7 +67,7 @@ export class APIErrorService {
64
67
  throw new CrossmintServiceError(body.message, response.status);
65
68
  }
66
69
  } catch (e) {
67
- if (e instanceof SmartWalletSDKError) {
70
+ if (e instanceof SmartWalletError) {
68
71
  throw e;
69
72
  }
70
73
  console.error("Error parsing response", e);
@@ -1,11 +1,11 @@
1
+ import { CrossmintServiceError } from "@crossmint/client-sdk-base";
1
2
  import { validateAPIKey } from "@crossmint/common-sdk-base";
2
3
 
3
- import { CrossmintServiceError } from "../error";
4
4
  import { CROSSMINT_DEV_URL, CROSSMINT_PROD_URL, CROSSMINT_STG_URL } from "../utils/constants";
5
- import { LoggerWrapper, logPerformance } from "../utils/log";
5
+ import { logPerformance } from "../utils/log";
6
6
  import { APIErrorService } from "./APIErrorService";
7
7
 
8
- export abstract class BaseCrossmintService extends LoggerWrapper {
8
+ export abstract class BaseCrossmintService {
9
9
  public crossmintAPIHeaders: Record<string, string>;
10
10
  protected crossmintBaseUrl: string;
11
11
  protected apiErrorService: APIErrorService;
@@ -16,7 +16,6 @@ export abstract class BaseCrossmintService extends LoggerWrapper {
16
16
  };
17
17
 
18
18
  constructor(apiKey: string) {
19
- super("BaseCrossmintService");
20
19
  const result = validateAPIKey(apiKey);
21
20
  if (!result.isValid) {
22
21
  throw new Error("API key invalid");
@@ -1,16 +1,16 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+
1
3
  import { CROSSMINT_STG_URL } from "../utils/constants";
2
4
  import { CrossmintWalletService } from "./CrossmintWalletService";
3
5
 
4
- jest.mock("../services/logging", () => ({
5
- logError: jest.fn(),
6
- logInfo: jest.fn(),
6
+ vi.mock("../services/logging", () => ({
7
+ logError: vi.fn(),
8
+ logInfo: vi.fn(),
7
9
  }));
8
10
 
9
- jest.mock("../utils/helpers", () => {
10
- return {
11
- isLocalhost: jest.fn().mockReturnValue(true),
12
- };
13
- });
11
+ vi.mock("../utils/helpers", () => ({
12
+ isLocalhost: vi.fn().mockReturnValue(true),
13
+ }));
14
14
 
15
15
  describe("CrossmintService", () => {
16
16
  let crossmintService: CrossmintWalletService;
@@ -37,6 +37,6 @@ describe("CrossmintService", () => {
37
37
  });
38
38
 
39
39
  afterEach(() => {
40
- jest.clearAllMocks();
40
+ vi.clearAllMocks();
41
41
  });
42
42
  });
@@ -1,13 +1,20 @@
1
- import { SmartWalletChain } from "@/blockchain/chains";
2
- import { SignerData, StoreSmartWalletParams } from "@/types/API";
3
- import type { UserParams } from "@/types/Config";
4
- import { API_VERSION } from "@/utils/constants";
1
+ import type { UserOperation } from "permissionless";
2
+ import type { EntryPoint, GetEntryPointVersion } from "permissionless/types/entrypoint";
5
3
 
6
- import { BaseCrossmintService } from "./BaseCrossmintService";
4
+ import { CrossmintServiceError } from "@crossmint/client-sdk-base";
5
+ import { blockchainToChainId } from "@crossmint/common-sdk-base";
6
+
7
+ import { BaseCrossmintService } from "../api/BaseCrossmintService";
8
+ import type { SmartWalletChain } from "../blockchain/chains";
9
+ import type { UserParams } from "../types/params";
10
+ import { SmartWalletConfigSchema } from "../types/schema";
11
+ import type { SmartWalletConfig, StoreSmartWalletParams } from "../types/service";
12
+ import { bigintsToHex, parseBigintAPIResponse } from "../utils/api";
13
+ import { API_VERSION } from "../utils/constants";
7
14
 
8
15
  export class CrossmintWalletService extends BaseCrossmintService {
9
- async idempotentCreateSmartWallet(user: UserParams, input: StoreSmartWalletParams) {
10
- return this.fetchCrossmintAPI(
16
+ async idempotentCreateSmartWallet(user: UserParams, input: StoreSmartWalletParams): Promise<void> {
17
+ await this.fetchCrossmintAPI(
11
18
  `${API_VERSION}/sdk/smart-wallet`,
12
19
  { method: "PUT", body: JSON.stringify(input) },
13
20
  "Error creating abstract wallet. Please contact support",
@@ -15,22 +22,38 @@ export class CrossmintWalletService extends BaseCrossmintService {
15
22
  );
16
23
  }
17
24
 
18
- async getSmartWalletConfig(
25
+ async sponsorUserOperation<E extends EntryPoint>(
19
26
  user: UserParams,
27
+ userOp: UserOperation<GetEntryPointVersion<E>>,
28
+ entryPoint: E,
20
29
  chain: SmartWalletChain
21
- ): Promise<{
22
- kernelVersion: string;
23
- entryPointVersion: string;
24
- userId: string;
25
- signers: { signerData: SignerData }[];
26
- smartContractWalletAddress?: string;
27
- }> {
28
- return this.fetchCrossmintAPI(
30
+ ): Promise<{ sponsorUserOpParams: UserOperation<GetEntryPointVersion<E>> }> {
31
+ const chainId = blockchainToChainId(chain);
32
+ const result = await this.fetchCrossmintAPI(
33
+ `${API_VERSION}/sdk/paymaster`,
34
+ { method: "POST", body: JSON.stringify({ userOp: bigintsToHex(userOp), entryPoint, chainId }) },
35
+ "Error sponsoring user operation. Please contact support",
36
+ user.jwt
37
+ );
38
+ return parseBigintAPIResponse(result);
39
+ }
40
+
41
+ async getSmartWalletConfig(user: UserParams, chain: SmartWalletChain): Promise<SmartWalletConfig> {
42
+ const data: unknown = await this.fetchCrossmintAPI(
29
43
  `${API_VERSION}/sdk/smart-wallet/config?chain=${chain}`,
30
44
  { method: "GET" },
31
45
  "Error getting smart wallet version configuration. Please contact support",
32
46
  user.jwt
33
47
  );
48
+
49
+ const result = SmartWalletConfigSchema.safeParse(data);
50
+ if (result.success) {
51
+ return result.data;
52
+ }
53
+
54
+ throw new CrossmintServiceError(
55
+ `Invalid smart wallet config, please contact support. Details below:\n${result.error.toString()}`
56
+ );
34
57
  }
35
58
 
36
59
  async fetchNFTs(address: string, chain: SmartWalletChain) {
@@ -1,11 +1,24 @@
1
- import { BUNDLER_RPC } from "@/utils/constants";
2
- import { Chain, base, baseSepolia, polygon, polygonAmoy } from "viem/chains";
1
+ import {
2
+ Chain,
3
+ arbitrum,
4
+ arbitrumSepolia,
5
+ base,
6
+ baseSepolia,
7
+ optimism,
8
+ optimismSepolia,
9
+ polygon,
10
+ polygonAmoy,
11
+ } from "viem/chains";
3
12
 
4
13
  import { BlockchainIncludingTestnet as Blockchain, ObjectValues, objectValues } from "@crossmint/common-sdk-base";
5
14
 
15
+ import { BUNDLER_RPC } from "../utils/constants";
16
+
6
17
  export const SmartWalletTestnet = {
7
18
  BASE_SEPOLIA: Blockchain.BASE_SEPOLIA,
8
19
  POLYGON_AMOY: Blockchain.POLYGON_AMOY,
20
+ OPTIMISM_SEPOLIA: Blockchain.OPTIMISM_SEPOLIA,
21
+ ARBITRUM_SEPOLIA: Blockchain.ARBITRUM_SEPOLIA,
9
22
  } as const;
10
23
  export type SmartWalletTestnet = ObjectValues<typeof SmartWalletTestnet>;
11
24
  export const SMART_WALLET_TESTNETS = objectValues(SmartWalletTestnet);
@@ -13,6 +26,8 @@ export const SMART_WALLET_TESTNETS = objectValues(SmartWalletTestnet);
13
26
  export const SmartWalletMainnet = {
14
27
  BASE: Blockchain.BASE,
15
28
  POLYGON: Blockchain.POLYGON,
29
+ OPTIMISM: Blockchain.OPTIMISM,
30
+ ARBITRUM: Blockchain.ARBITRUM,
16
31
  } as const;
17
32
  export type SmartWalletMainnet = ObjectValues<typeof SmartWalletMainnet>;
18
33
  export const SMART_WALLET_MAINNETS = objectValues(SmartWalletMainnet);
@@ -29,6 +44,10 @@ export const zerodevProjects: Record<SmartWalletChain, string> = {
29
44
  "polygon-amoy": "3deef404-ca06-4a5d-9a58-907c99e7ef00",
30
45
  "base-sepolia": "5a127978-6473-4784-9dfb-f74395b220a6",
31
46
  base: "e8b3020f-4dde-4176-8a7d-be8102527a5c",
47
+ "optimism-sepolia": "f8dd488e-eaed-467d-a5de-0184c160f3b1",
48
+ optimism: "505950ab-ee07-4a9c-bd16-320ac71a9350",
49
+ arbitrum: "a965100f-fedf-4e6b-a207-20f5687fcc4f",
50
+ "arbitrum-sepolia": "76c860ca-af77-4fb1-8eac-07825952f6f4",
32
51
  };
33
52
 
34
53
  export const viemNetworks: Record<SmartWalletChain, Chain> = {
@@ -36,6 +55,10 @@ export const viemNetworks: Record<SmartWalletChain, Chain> = {
36
55
  "polygon-amoy": polygonAmoy,
37
56
  base: base,
38
57
  "base-sepolia": baseSepolia,
58
+ optimism: optimism,
59
+ "optimism-sepolia": optimismSepolia,
60
+ arbitrum: arbitrum,
61
+ "arbitrum-sepolia": arbitrumSepolia,
39
62
  };
40
63
 
41
64
  export const getBundlerRPC = (chain: SmartWalletChain) => {
@@ -1,7 +1,7 @@
1
1
  import { Abi, Account, Address, erc20Abi, erc721Abi } from "viem";
2
2
 
3
3
  import erc1155Abi from "../ABI/ERC1155.json";
4
- import type { ERC20TransferType, SFTTransferType, TransferType } from "../types/Tokens";
4
+ import type { ERC20TransferType, SFTTransferType, TransferType } from "../types/token";
5
5
 
6
6
  type TransferInputParams = {
7
7
  from: Account;
@@ -1,12 +1,13 @@
1
- import { logError } from "@/services/logging";
2
1
  import { type HttpTransport, type PublicClient, isAddress, publicActions } from "viem";
3
2
 
3
+ import { TransferError } from "@crossmint/client-sdk-base";
4
+
4
5
  import type { CrossmintWalletService } from "../../api/CrossmintWalletService";
5
- import { TransferError } from "../../error";
6
- import type { TransferType } from "../../types/Tokens";
6
+ import { logError, logInfo } from "../../services/logging";
7
7
  import { SmartWalletClient } from "../../types/internal";
8
+ import type { TransferType } from "../../types/token";
8
9
  import { SCW_SERVICE } from "../../utils/constants";
9
- import { LoggerWrapper, errorToJSON } from "../../utils/log";
10
+ import { errorToJSON, logPerformance } from "../../utils/log";
10
11
  import { SmartWalletChain } from "../chains";
11
12
  import { transferParams } from "../transfer";
12
13
 
@@ -14,7 +15,7 @@ import { transferParams } from "../transfer";
14
15
  * Smart wallet interface for EVM chains enhanced with Crossmint capabilities.
15
16
  * Core functionality is exposed via [viem](https://viem.sh/) clients within the `client` property of the class.
16
17
  */
17
- export class EVMSmartWallet extends LoggerWrapper {
18
+ export class EVMSmartWallet {
18
19
  public readonly chain: SmartWalletChain;
19
20
 
20
21
  /**
@@ -38,7 +39,6 @@ export class EVMSmartWallet extends LoggerWrapper {
38
39
  publicClient: PublicClient<HttpTransport>,
39
40
  chain: SmartWalletChain
40
41
  ) {
41
- super("EVMSmartWallet", { chain, address: accountClient.account.address });
42
42
  this.chain = chain;
43
43
  this.client = {
44
44
  wallet: accountClient,
@@ -57,46 +57,53 @@ export class EVMSmartWallet extends LoggerWrapper {
57
57
  * @returns The transaction hash.
58
58
  */
59
59
  public async transferToken(toAddress: string, config: TransferType): Promise<string> {
60
- return this.logPerformance("TRANSFER", async () => {
61
- if (this.chain !== config.token.chain) {
62
- throw new Error(
63
- `Chain mismatch: Expected ${config.token.chain}, but got ${this.chain}. Ensure you are interacting with the correct blockchain.`
64
- );
65
- }
66
-
67
- if (!isAddress(toAddress)) {
68
- throw new Error(`Invalid recipient address: '${toAddress}' is not a valid EVM address.`);
69
- }
60
+ return logPerformance(
61
+ "TRANSFER",
62
+ async () => {
63
+ if (this.chain !== config.token.chain) {
64
+ throw new Error(
65
+ `Chain mismatch: Expected ${config.token.chain}, but got ${this.chain}. Ensure you are interacting with the correct blockchain.`
66
+ );
67
+ }
70
68
 
71
- if (!isAddress(config.token.contractAddress)) {
72
- throw new Error(
73
- `Invalid contract address: '${config.token.contractAddress}' is not a valid EVM address.`
74
- );
75
- }
69
+ if (!isAddress(toAddress)) {
70
+ throw new Error(`Invalid recipient address: '${toAddress}' is not a valid EVM address.`);
71
+ }
76
72
 
77
- const tx = transferParams({
78
- contract: config.token.contractAddress,
79
- to: toAddress,
80
- from: this.accountClient.account,
81
- config,
82
- });
73
+ if (!isAddress(config.token.contractAddress)) {
74
+ throw new Error(
75
+ `Invalid contract address: '${config.token.contractAddress}' is not a valid EVM address.`
76
+ );
77
+ }
83
78
 
84
- try {
85
- const client = this.accountClient.extend(publicActions);
86
- const { request } = await client.simulateContract(tx);
87
- return await client.writeContract(request);
88
- } catch (error) {
89
- logError("[TRANSFER] - ERROR_TRANSFERRING_TOKEN", {
90
- service: SCW_SERVICE,
91
- error: errorToJSON(error),
92
- tokenId: tx.tokenId,
93
- contractAddress: config.token.contractAddress,
94
- chain: config.token.chain,
79
+ const tx = transferParams({
80
+ contract: config.token.contractAddress,
81
+ to: toAddress,
82
+ from: this.accountClient.account,
83
+ config,
95
84
  });
96
- const tokenIdString = tx.tokenId == null ? "" : `:${tx.tokenId}}`;
97
- throw new TransferError(`Error transferring token ${config.token.contractAddress}${tokenIdString}`);
98
- }
99
- });
85
+
86
+ try {
87
+ const client = this.accountClient.extend(publicActions);
88
+ const { request } = await client.simulateContract(tx);
89
+ const hash = await client.writeContract(request);
90
+ logInfo(`[TRANSFER] - Transaction hash from transfer: ${hash}`);
91
+
92
+ return hash;
93
+ } catch (error) {
94
+ logError("[TRANSFER] - ERROR_TRANSFERRING_TOKEN", {
95
+ service: SCW_SERVICE,
96
+ error: errorToJSON(error),
97
+ tokenId: tx.tokenId,
98
+ contractAddress: config.token.contractAddress,
99
+ chain: config.token.chain,
100
+ });
101
+ const tokenIdString = tx.tokenId == null ? "" : `:${tx.tokenId}}`;
102
+ throw new TransferError(`Error transferring token ${config.token.contractAddress}${tokenIdString}`);
103
+ }
104
+ },
105
+ { toAddress, config }
106
+ );
100
107
  }
101
108
 
102
109
  /**
@@ -0,0 +1,60 @@
1
+ import type { Address } from "viem";
2
+
3
+ import type { CrossmintWalletService } from "../../../api/CrossmintWalletService";
4
+ import type { SmartWalletChain } from "../../../blockchain/chains";
5
+ import { SmartWalletError } from "../../../error";
6
+ import type { SupportedEntryPointVersion, SupportedKernelVersion } from "../../../types/internal";
7
+ import type { UserParams } from "../../../types/params";
8
+ import type { SignerData } from "../../../types/service";
9
+ import { EOASignerConfig, PasskeySignerConfig, type SignerConfig } from "./signer";
10
+
11
+ export class AccountConfigFacade {
12
+ constructor(private readonly crossmintService: CrossmintWalletService) {}
13
+
14
+ public async get(
15
+ user: UserParams,
16
+ chain: SmartWalletChain
17
+ ): Promise<{
18
+ entryPointVersion: SupportedEntryPointVersion;
19
+ kernelVersion: SupportedKernelVersion;
20
+ userId: string;
21
+ existingSignerConfig?: SignerConfig;
22
+ smartContractWalletAddress?: Address;
23
+ }> {
24
+ const { entryPointVersion, kernelVersion, signers, smartContractWalletAddress, userId } =
25
+ await this.crossmintService.getSmartWalletConfig(user, chain);
26
+
27
+ if (
28
+ (entryPointVersion === "v0.7" && kernelVersion.startsWith("0.2")) ||
29
+ (entryPointVersion === "v0.6" && kernelVersion.startsWith("0.3"))
30
+ ) {
31
+ throw new SmartWalletError(
32
+ `Unsupported combination: entryPoint ${entryPointVersion} and kernel version ${kernelVersion}. Please contact support`
33
+ );
34
+ }
35
+
36
+ return {
37
+ entryPointVersion,
38
+ kernelVersion,
39
+ userId,
40
+ existingSignerConfig: this.getSigner(signers.map((x) => x.signerData)),
41
+ smartContractWalletAddress,
42
+ };
43
+ }
44
+
45
+ private getSigner(signers: SignerData[]): SignerConfig | undefined {
46
+ if (signers.length === 0) {
47
+ return undefined;
48
+ }
49
+
50
+ const data = signers[0];
51
+
52
+ if (data.type === "eoa") {
53
+ return new EOASignerConfig(data);
54
+ }
55
+
56
+ if (data.type === "passkeys") {
57
+ return new PasskeySignerConfig(data);
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,36 @@
1
+ import { AdminMismatchError, ConfigError } from "../../../error";
2
+ import {
3
+ AccountAndSigner,
4
+ type WalletCreationParams,
5
+ isEOACreationParams,
6
+ isPasskeyCreationParams,
7
+ } from "../../../types/internal";
8
+ import { EOACreationStrategy } from "./eoa";
9
+ import { PasskeyCreationStrategy } from "./passkey";
10
+
11
+ export class AccountCreator {
12
+ constructor(
13
+ private readonly eoaStrategy: EOACreationStrategy,
14
+ private readonly passkeyStrategy: PasskeyCreationStrategy
15
+ ) {}
16
+
17
+ public get(params: WalletCreationParams): Promise<AccountAndSigner> {
18
+ if (isPasskeyCreationParams(params)) {
19
+ return this.passkeyStrategy.create(params);
20
+ }
21
+
22
+ if (isEOACreationParams(params)) {
23
+ return this.eoaStrategy.create(params);
24
+ }
25
+
26
+ if (params.existingSignerConfig == null) {
27
+ throw new ConfigError(`Unsupported wallet params:\n${params.walletParams}`);
28
+ }
29
+
30
+ const signerDisplay = params.existingSignerConfig.display();
31
+ throw new AdminMismatchError(
32
+ `Cannot create wallet with ${params.existingSignerConfig.type} signer for user ${params.user.id}', they already have a wallet with signer:\n'${signerDisplay}'`,
33
+ signerDisplay
34
+ );
35
+ }
36
+ }
@@ -0,0 +1,50 @@
1
+ import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator";
2
+ import { createKernelAccount } from "@zerodev/sdk";
3
+
4
+ import { AdminMismatchError } from "../../../error";
5
+ import type { AccountAndSigner, EOACreationParams } from "../../../types/internal";
6
+ import { equalsIgnoreCase } from "../../../utils/helpers";
7
+ import { createOwnerSigner } from "../../../utils/signer";
8
+ import { EOASignerConfig } from "./signer";
9
+ import { AccountCreationStrategy } from "./strategy";
10
+
11
+ export class EOACreationStrategy implements AccountCreationStrategy {
12
+ public async create({
13
+ chain,
14
+ publicClient,
15
+ entryPoint,
16
+ walletParams,
17
+ kernelVersion,
18
+ user,
19
+ existingSignerConfig,
20
+ }: EOACreationParams): Promise<AccountAndSigner> {
21
+ const eoa = await createOwnerSigner({
22
+ chain,
23
+ walletParams,
24
+ });
25
+
26
+ if (existingSignerConfig != null && !equalsIgnoreCase(eoa.address, existingSignerConfig.data.eoaAddress)) {
27
+ throw new AdminMismatchError(
28
+ `User '${user.id}' has an existing wallet with an eoa signer '${existingSignerConfig.data.eoaAddress}', this does not match input eoa signer '${eoa.address}'.`,
29
+ existingSignerConfig.display(),
30
+ { type: "eoa", eoaAddress: existingSignerConfig.data.eoaAddress }
31
+ );
32
+ }
33
+
34
+ const ecdsaValidator = await signerToEcdsaValidator(publicClient, {
35
+ signer: eoa,
36
+ entryPoint: entryPoint.address,
37
+ kernelVersion,
38
+ });
39
+ const account = await createKernelAccount(publicClient, {
40
+ plugins: {
41
+ sudo: ecdsaValidator,
42
+ },
43
+ index: 0n,
44
+ entryPoint: entryPoint.address,
45
+ kernelVersion,
46
+ });
47
+
48
+ return { account, signerConfig: new EOASignerConfig({ eoaAddress: eoa.address, type: "eoa" }) };
49
+ }
50
+ }