@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.
- package/dist/index.cjs +4 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +254 -119
- package/dist/index.d.ts +254 -119
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -12
- package/src/SmartWalletSDK.test.ts +12 -13
- package/src/SmartWalletSDK.ts +33 -26
- package/src/api/APIErrorService.ts +10 -7
- package/src/api/BaseCrossmintService.ts +3 -4
- package/src/api/CrossmintWalletService.test.ts +9 -9
- package/src/api/CrossmintWalletService.ts +39 -16
- package/src/blockchain/chains.ts +25 -2
- package/src/blockchain/transfer.ts +1 -1
- package/src/blockchain/wallets/EVMSmartWallet.ts +49 -42
- package/src/blockchain/wallets/account/config.ts +60 -0
- package/src/blockchain/wallets/account/creator.ts +36 -0
- package/src/blockchain/wallets/account/eoa.ts +50 -0
- package/src/blockchain/wallets/{passkey.ts → account/passkey.ts} +32 -32
- package/src/blockchain/wallets/account/signer.ts +44 -0
- package/src/blockchain/wallets/account/strategy.ts +5 -0
- package/src/blockchain/wallets/clientDecorator.ts +6 -6
- package/src/blockchain/wallets/paymaster.ts +12 -15
- package/src/blockchain/wallets/service.ts +38 -143
- package/src/error/index.ts +25 -117
- package/src/error/processor.ts +5 -6
- package/src/index.ts +16 -12
- package/src/services/logging/ConsoleProvider.ts +3 -12
- package/src/services/logging/DatadogProvider.ts +1 -1
- package/src/types/internal.ts +41 -20
- package/src/types/{Config.ts → params.ts} +0 -5
- package/src/types/schema.ts +63 -0
- package/src/types/service.ts +31 -0
- package/src/utils/api.ts +39 -0
- package/src/utils/constants.ts +2 -0
- package/src/utils/log.ts +1 -109
- package/src/utils/signer.ts +14 -16
- package/src/blockchain/wallets/eoa.ts +0 -49
- package/src/types/API.ts +0 -40
- package/src/utils/log.test.ts +0 -76
- /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
|
-
|
|
5
|
+
vi.mock("./services/logging");
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
24
|
-
sdk = SmartWalletSDK.init(
|
|
19
|
+
vi.clearAllMocks();
|
|
20
|
+
sdk = SmartWalletSDK.init({
|
|
21
|
+
clientApiKey:
|
|
22
|
+
"sk_staging_A4vDwAp4t5az6fVQMpQK6qapBnAqgpxrrD35TaFQnyKgxehNbd959uZeaHjNCadWDXrgLRAK1CxeasZjtYEq4TbFkKMBBvbQ9oinAxQf8LbHsSYW2DMzT8fBko3YGLq9t7ZiXZjmgkTioxGVUUjyLtWLeBKwNUDLgpshWjaoR7pKRnSE9SqhwjQbiK62VKiBTdA3KvHsyG9k8mLMcKrDyfXp",
|
|
23
|
+
});
|
|
25
24
|
});
|
|
26
25
|
|
|
27
26
|
describe("init", () => {
|
package/src/SmartWalletSDK.ts
CHANGED
|
@@ -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 {
|
|
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/
|
|
17
|
+
import type { SmartWalletSDKInitParams, UserParams, WalletParams } from "./types/params";
|
|
14
18
|
import { isClient } from "./utils/environment";
|
|
15
|
-
import {
|
|
19
|
+
import { logPerformance } from "./utils/log";
|
|
16
20
|
|
|
17
|
-
export class SmartWalletSDK
|
|
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
|
|
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
|
-
|
|
42
|
-
new
|
|
43
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
8
|
+
} from "@crossmint/client-sdk-base";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
AdminAlreadyUsedError,
|
|
12
|
+
SmartWalletError,
|
|
13
|
+
SmartWalletsNotEnabledError,
|
|
11
14
|
UserWalletAlreadyCreatedError,
|
|
12
|
-
} from "
|
|
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:
|
|
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
|
|
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
|
|
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 {
|
|
5
|
+
import { logPerformance } from "../utils/log";
|
|
6
6
|
import { APIErrorService } from "./APIErrorService";
|
|
7
7
|
|
|
8
|
-
export abstract class BaseCrossmintService
|
|
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
|
-
|
|
5
|
-
logError:
|
|
6
|
-
logInfo:
|
|
6
|
+
vi.mock("../services/logging", () => ({
|
|
7
|
+
logError: vi.fn(),
|
|
8
|
+
logInfo: vi.fn(),
|
|
7
9
|
}));
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
40
|
+
vi.clearAllMocks();
|
|
41
41
|
});
|
|
42
42
|
});
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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) {
|
package/src/blockchain/chains.ts
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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/
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
);
|
|
75
|
-
}
|
|
69
|
+
if (!isAddress(toAddress)) {
|
|
70
|
+
throw new Error(`Invalid recipient address: '${toAddress}' is not a valid EVM address.`);
|
|
71
|
+
}
|
|
76
72
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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
|
+
}
|