@crossmint/client-sdk-smart-wallet 0.1.0
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/LICENSE +201 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +289 -0
- package/dist/index.d.ts +289 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
- package/src/ABI/ERC1155.json +325 -0
- package/src/ABI/ERC20.json +222 -0
- package/src/ABI/ERC721.json +320 -0
- package/src/SmartWalletSDK.test.ts +32 -0
- package/src/SmartWalletSDK.ts +75 -0
- package/src/api/APIErrorService.ts +72 -0
- package/src/api/BaseCrossmintService.ts +82 -0
- package/src/api/CrossmintWalletService.test.ts +42 -0
- package/src/api/CrossmintWalletService.ts +50 -0
- package/src/blockchain/BlockchainNetworks.ts +121 -0
- package/src/blockchain/token/index.ts +1 -0
- package/src/blockchain/transfer.ts +54 -0
- package/src/blockchain/wallets/EVMSmartWallet.ts +109 -0
- package/src/blockchain/wallets/clientDecorator.ts +127 -0
- package/src/blockchain/wallets/eoa.ts +49 -0
- package/src/blockchain/wallets/index.ts +1 -0
- package/src/blockchain/wallets/passkey.ts +117 -0
- package/src/blockchain/wallets/paymaster.ts +49 -0
- package/src/blockchain/wallets/service.ts +193 -0
- package/src/error/index.ts +148 -0
- package/src/error/processor.ts +36 -0
- package/src/index.ts +34 -0
- package/src/services/logging/BrowserLoggerInterface.ts +5 -0
- package/src/services/logging/ConsoleProvider.ts +24 -0
- package/src/services/logging/DatadogProvider.ts +39 -0
- package/src/services/logging/index.ts +16 -0
- package/src/types/API.ts +40 -0
- package/src/types/Config.ts +35 -0
- package/src/types/Tokens.ts +27 -0
- package/src/types/internal.ts +50 -0
- package/src/utils/blockchain.ts +15 -0
- package/src/utils/constants.ts +31 -0
- package/src/utils/environment.ts +3 -0
- package/src/utils/helpers.ts +15 -0
- package/src/utils/log.test.ts +76 -0
- package/src/utils/log.ts +157 -0
- package/src/utils/signer.ts +36 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"inputs": [
|
|
4
|
+
{
|
|
5
|
+
"internalType": "string",
|
|
6
|
+
"name": "name_",
|
|
7
|
+
"type": "string"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"internalType": "string",
|
|
11
|
+
"name": "symbol_",
|
|
12
|
+
"type": "string"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"stateMutability": "nonpayable",
|
|
16
|
+
"type": "constructor"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"anonymous": false,
|
|
20
|
+
"inputs": [
|
|
21
|
+
{
|
|
22
|
+
"indexed": true,
|
|
23
|
+
"internalType": "address",
|
|
24
|
+
"name": "owner",
|
|
25
|
+
"type": "address"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"indexed": true,
|
|
29
|
+
"internalType": "address",
|
|
30
|
+
"name": "approved",
|
|
31
|
+
"type": "address"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"indexed": true,
|
|
35
|
+
"internalType": "uint256",
|
|
36
|
+
"name": "tokenId",
|
|
37
|
+
"type": "uint256"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"name": "Approval",
|
|
41
|
+
"type": "event"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"anonymous": false,
|
|
45
|
+
"inputs": [
|
|
46
|
+
{
|
|
47
|
+
"indexed": true,
|
|
48
|
+
"internalType": "address",
|
|
49
|
+
"name": "owner",
|
|
50
|
+
"type": "address"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"indexed": true,
|
|
54
|
+
"internalType": "address",
|
|
55
|
+
"name": "operator",
|
|
56
|
+
"type": "address"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"indexed": false,
|
|
60
|
+
"internalType": "bool",
|
|
61
|
+
"name": "approved",
|
|
62
|
+
"type": "bool"
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"name": "ApprovalForAll",
|
|
66
|
+
"type": "event"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"anonymous": false,
|
|
70
|
+
"inputs": [
|
|
71
|
+
{
|
|
72
|
+
"indexed": true,
|
|
73
|
+
"internalType": "address",
|
|
74
|
+
"name": "from",
|
|
75
|
+
"type": "address"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"indexed": true,
|
|
79
|
+
"internalType": "address",
|
|
80
|
+
"name": "to",
|
|
81
|
+
"type": "address"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"indexed": true,
|
|
85
|
+
"internalType": "uint256",
|
|
86
|
+
"name": "tokenId",
|
|
87
|
+
"type": "uint256"
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
"name": "Transfer",
|
|
91
|
+
"type": "event"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"inputs": [
|
|
95
|
+
{
|
|
96
|
+
"internalType": "address",
|
|
97
|
+
"name": "to",
|
|
98
|
+
"type": "address"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"internalType": "uint256",
|
|
102
|
+
"name": "tokenId",
|
|
103
|
+
"type": "uint256"
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
"name": "approve",
|
|
107
|
+
"outputs": [],
|
|
108
|
+
"stateMutability": "nonpayable",
|
|
109
|
+
"type": "function"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"inputs": [
|
|
113
|
+
{
|
|
114
|
+
"internalType": "address",
|
|
115
|
+
"name": "owner",
|
|
116
|
+
"type": "address"
|
|
117
|
+
}
|
|
118
|
+
],
|
|
119
|
+
"name": "balanceOf",
|
|
120
|
+
"outputs": [
|
|
121
|
+
{
|
|
122
|
+
"internalType": "uint256",
|
|
123
|
+
"name": "",
|
|
124
|
+
"type": "uint256"
|
|
125
|
+
}
|
|
126
|
+
],
|
|
127
|
+
"stateMutability": "view",
|
|
128
|
+
"type": "function"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"inputs": [
|
|
132
|
+
{
|
|
133
|
+
"internalType": "uint256",
|
|
134
|
+
"name": "tokenId",
|
|
135
|
+
"type": "uint256"
|
|
136
|
+
}
|
|
137
|
+
],
|
|
138
|
+
"name": "getApproved",
|
|
139
|
+
"outputs": [
|
|
140
|
+
{
|
|
141
|
+
"internalType": "address",
|
|
142
|
+
"name": "",
|
|
143
|
+
"type": "address"
|
|
144
|
+
}
|
|
145
|
+
],
|
|
146
|
+
"stateMutability": "view",
|
|
147
|
+
"type": "function"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"inputs": [
|
|
151
|
+
{
|
|
152
|
+
"internalType": "address",
|
|
153
|
+
"name": "owner",
|
|
154
|
+
"type": "address"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"internalType": "address",
|
|
158
|
+
"name": "operator",
|
|
159
|
+
"type": "address"
|
|
160
|
+
}
|
|
161
|
+
],
|
|
162
|
+
"name": "isApprovedForAll",
|
|
163
|
+
"outputs": [
|
|
164
|
+
{
|
|
165
|
+
"internalType": "bool",
|
|
166
|
+
"name": "",
|
|
167
|
+
"type": "bool"
|
|
168
|
+
}
|
|
169
|
+
],
|
|
170
|
+
"stateMutability": "view",
|
|
171
|
+
"type": "function"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"inputs": [],
|
|
175
|
+
"name": "name",
|
|
176
|
+
"outputs": [
|
|
177
|
+
{
|
|
178
|
+
"internalType": "string",
|
|
179
|
+
"name": "",
|
|
180
|
+
"type": "string"
|
|
181
|
+
}
|
|
182
|
+
],
|
|
183
|
+
"stateMutability": "view",
|
|
184
|
+
"type": "function"
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"inputs": [
|
|
188
|
+
{
|
|
189
|
+
"internalType": "uint256",
|
|
190
|
+
"name": "tokenId",
|
|
191
|
+
"type": "uint256"
|
|
192
|
+
}
|
|
193
|
+
],
|
|
194
|
+
"name": "ownerOf",
|
|
195
|
+
"outputs": [
|
|
196
|
+
{
|
|
197
|
+
"internalType": "address",
|
|
198
|
+
"name": "",
|
|
199
|
+
"type": "address"
|
|
200
|
+
}
|
|
201
|
+
],
|
|
202
|
+
"stateMutability": "view",
|
|
203
|
+
"type": "function"
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"inputs": [
|
|
207
|
+
{
|
|
208
|
+
"internalType": "address",
|
|
209
|
+
"name": "from",
|
|
210
|
+
"type": "address"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"internalType": "address",
|
|
214
|
+
"name": "to",
|
|
215
|
+
"type": "address"
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"internalType": "uint256",
|
|
219
|
+
"name": "tokenId",
|
|
220
|
+
"type": "uint256"
|
|
221
|
+
}
|
|
222
|
+
],
|
|
223
|
+
"name": "safeTransferFrom",
|
|
224
|
+
"outputs": [],
|
|
225
|
+
"stateMutability": "nonpayable",
|
|
226
|
+
"type": "function"
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"inputs": [
|
|
230
|
+
{
|
|
231
|
+
"internalType": "address",
|
|
232
|
+
"name": "operator",
|
|
233
|
+
"type": "address"
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"internalType": "bool",
|
|
237
|
+
"name": "approved",
|
|
238
|
+
"type": "bool"
|
|
239
|
+
}
|
|
240
|
+
],
|
|
241
|
+
"name": "setApprovalForAll",
|
|
242
|
+
"outputs": [],
|
|
243
|
+
"stateMutability": "nonpayable",
|
|
244
|
+
"type": "function"
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
"inputs": [
|
|
248
|
+
{
|
|
249
|
+
"internalType": "bytes4",
|
|
250
|
+
"name": "interfaceId",
|
|
251
|
+
"type": "bytes4"
|
|
252
|
+
}
|
|
253
|
+
],
|
|
254
|
+
"name": "supportsInterface",
|
|
255
|
+
"outputs": [
|
|
256
|
+
{
|
|
257
|
+
"internalType": "bool",
|
|
258
|
+
"name": "",
|
|
259
|
+
"type": "bool"
|
|
260
|
+
}
|
|
261
|
+
],
|
|
262
|
+
"stateMutability": "view",
|
|
263
|
+
"type": "function"
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
"inputs": [],
|
|
267
|
+
"name": "symbol",
|
|
268
|
+
"outputs": [
|
|
269
|
+
{
|
|
270
|
+
"internalType": "string",
|
|
271
|
+
"name": "",
|
|
272
|
+
"type": "string"
|
|
273
|
+
}
|
|
274
|
+
],
|
|
275
|
+
"stateMutability": "view",
|
|
276
|
+
"type": "function"
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"inputs": [
|
|
280
|
+
{
|
|
281
|
+
"internalType": "uint256",
|
|
282
|
+
"name": "tokenId",
|
|
283
|
+
"type": "uint256"
|
|
284
|
+
}
|
|
285
|
+
],
|
|
286
|
+
"name": "tokenURI",
|
|
287
|
+
"outputs": [
|
|
288
|
+
{
|
|
289
|
+
"internalType": "string",
|
|
290
|
+
"name": "",
|
|
291
|
+
"type": "string"
|
|
292
|
+
}
|
|
293
|
+
],
|
|
294
|
+
"stateMutability": "view",
|
|
295
|
+
"type": "function"
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
"inputs": [
|
|
299
|
+
{
|
|
300
|
+
"internalType": "address",
|
|
301
|
+
"name": "from",
|
|
302
|
+
"type": "address"
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"internalType": "address",
|
|
306
|
+
"name": "to",
|
|
307
|
+
"type": "address"
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
"internalType": "uint256",
|
|
311
|
+
"name": "tokenId",
|
|
312
|
+
"type": "uint256"
|
|
313
|
+
}
|
|
314
|
+
],
|
|
315
|
+
"name": "transferFrom",
|
|
316
|
+
"outputs": [],
|
|
317
|
+
"stateMutability": "nonpayable",
|
|
318
|
+
"type": "function"
|
|
319
|
+
}
|
|
320
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { SmartWalletSDK } from "./SmartWalletSDK";
|
|
2
|
+
import { SmartWalletSDKInitParams } from "./types/Config";
|
|
3
|
+
|
|
4
|
+
jest.mock("./services/logging");
|
|
5
|
+
|
|
6
|
+
Object.defineProperty(global, "window", {
|
|
7
|
+
value: {
|
|
8
|
+
location: {
|
|
9
|
+
origin: "http://localhost",
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe("SmartWalletSDK", () => {
|
|
15
|
+
let sdk: SmartWalletSDK;
|
|
16
|
+
const mockInitParams: SmartWalletSDKInitParams = {
|
|
17
|
+
clientApiKey:
|
|
18
|
+
"sk_staging_A4vDwAp4t5az6fVQMpQK6qapBnAqgpxrrD35TaFQnyKgxehNbd959uZeaHjNCadWDXrgLRAK1CxeasZjtYEq4TbFkKMBBvbQ9oinAxQf8LbHsSYW2DMzT8fBko3YGLq9t7ZiXZjmgkTioxGVUUjyLtWLeBKwNUDLgpshWjaoR7pKRnSE9SqhwjQbiK62VKiBTdA3KvHsyG9k8mLMcKrDyfXp",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
// Reset mocks before each test
|
|
23
|
+
jest.clearAllMocks();
|
|
24
|
+
sdk = SmartWalletSDK.init(mockInitParams);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("init", () => {
|
|
28
|
+
it("should initialize the SDK correctly", () => {
|
|
29
|
+
expect(sdk).toBeInstanceOf(SmartWalletSDK);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { stringify } from "viem";
|
|
2
|
+
|
|
3
|
+
import { EVMBlockchainIncludingTestnet, validateAPIKey } from "@crossmint/common-sdk-base";
|
|
4
|
+
|
|
5
|
+
import { CrossmintWalletService } from "./api/CrossmintWalletService";
|
|
6
|
+
import type { EVMSmartWallet } from "./blockchain/wallets";
|
|
7
|
+
import { ClientDecorator } from "./blockchain/wallets/clientDecorator";
|
|
8
|
+
import { SmartWalletService } from "./blockchain/wallets/service";
|
|
9
|
+
import { SmartWalletSDKError } from "./error";
|
|
10
|
+
import { ErrorProcessor } from "./error/processor";
|
|
11
|
+
import { DatadogProvider } from "./services/logging/DatadogProvider";
|
|
12
|
+
import type { SmartWalletSDKInitParams, UserParams, WalletParams } from "./types/Config";
|
|
13
|
+
import { isClient } from "./utils/environment";
|
|
14
|
+
import { LoggerWrapper, logPerformance } from "./utils/log";
|
|
15
|
+
|
|
16
|
+
export class SmartWalletSDK extends LoggerWrapper {
|
|
17
|
+
private constructor(
|
|
18
|
+
private readonly smartWalletService: SmartWalletService,
|
|
19
|
+
private readonly errorProcessor: ErrorProcessor
|
|
20
|
+
) {
|
|
21
|
+
super("SmartWalletSDK");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Initializes the SDK with the **client side** API key obtained from the Crossmint console.
|
|
26
|
+
* @throws error if the api key is not formatted correctly.
|
|
27
|
+
*/
|
|
28
|
+
static init({ clientApiKey }: SmartWalletSDKInitParams): SmartWalletSDK {
|
|
29
|
+
if (!isClient()) {
|
|
30
|
+
throw new SmartWalletSDKError("Smart Wallet SDK should only be used client side.");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const validationResult = validateAPIKey(clientApiKey);
|
|
34
|
+
if (!validationResult.isValid) {
|
|
35
|
+
throw new Error("API key invalid");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const crossmintService = new CrossmintWalletService(clientApiKey);
|
|
39
|
+
const errorProcessor = new ErrorProcessor(new DatadogProvider());
|
|
40
|
+
return new SmartWalletSDK(
|
|
41
|
+
new SmartWalletService(crossmintService, new ClientDecorator(errorProcessor)),
|
|
42
|
+
errorProcessor
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Retrieves or creates a wallet for the specified user.
|
|
48
|
+
* The default configuration is a `PasskeySigner` with the name, which is displayed to the user during creation or signing prompts, derived from the provided jwt.
|
|
49
|
+
*
|
|
50
|
+
* Example using the default passkey signer:
|
|
51
|
+
* ```ts
|
|
52
|
+
* const wallet = await smartWalletSDK.getOrCreateWallet({ jwt: "xxx" }, "base");
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
async getOrCreateWallet(
|
|
56
|
+
user: UserParams,
|
|
57
|
+
chain: EVMBlockchainIncludingTestnet,
|
|
58
|
+
walletParams: WalletParams = { signer: { type: "PASSKEY" } }
|
|
59
|
+
): Promise<EVMSmartWallet> {
|
|
60
|
+
return logPerformance(
|
|
61
|
+
"GET_OR_CREATE_WALLET",
|
|
62
|
+
async () => {
|
|
63
|
+
try {
|
|
64
|
+
return await this.smartWalletService.getOrCreate(user, chain, walletParams);
|
|
65
|
+
} catch (error: any) {
|
|
66
|
+
throw this.errorProcessor.map(
|
|
67
|
+
error,
|
|
68
|
+
new SmartWalletSDKError(`Wallet creation failed: ${error.message}.`, stringify(error))
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{ user, chain }
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AdminAlreadyUsedError,
|
|
3
|
+
CrossmintServiceError,
|
|
4
|
+
JWTDecryptionError,
|
|
5
|
+
JWTExpiredError,
|
|
6
|
+
JWTIdentifierError,
|
|
7
|
+
JWTInvalidError,
|
|
8
|
+
NonCustodialWalletsNotEnabledError,
|
|
9
|
+
OutOfCreditsError,
|
|
10
|
+
SmartWalletSDKError,
|
|
11
|
+
UserWalletAlreadyCreatedError,
|
|
12
|
+
} from "@/error";
|
|
13
|
+
|
|
14
|
+
export type CrossmintAPIErrorCodes =
|
|
15
|
+
| "ERROR_JWT_INVALID"
|
|
16
|
+
| "ERROR_JWT_DECRYPTION"
|
|
17
|
+
| "ERROR_JWT_IDENTIFIER"
|
|
18
|
+
| "ERROR_JWT_EXPIRED"
|
|
19
|
+
| "ERROR_USER_WALLET_ALREADY_CREATED"
|
|
20
|
+
| "ERROR_ADMIN_SIGNER_ALREADY_USED"
|
|
21
|
+
| "ERROR_PROJECT_NONCUSTODIAL_WALLETS_NOT_ENABLED";
|
|
22
|
+
|
|
23
|
+
export class APIErrorService {
|
|
24
|
+
constructor(
|
|
25
|
+
private errors: Partial<Record<CrossmintAPIErrorCodes, (apiResponse: any) => SmartWalletSDKError>> = {
|
|
26
|
+
ERROR_JWT_INVALID: () => new JWTInvalidError(),
|
|
27
|
+
ERROR_JWT_DECRYPTION: () => new JWTDecryptionError(),
|
|
28
|
+
ERROR_JWT_EXPIRED: ({ expiredAt }: { expiredAt: string }) => new JWTExpiredError(new Date(expiredAt)),
|
|
29
|
+
ERROR_JWT_IDENTIFIER: ({ identifierKey }: { identifierKey: string }) =>
|
|
30
|
+
new JWTIdentifierError(identifierKey),
|
|
31
|
+
ERROR_USER_WALLET_ALREADY_CREATED: ({ userId }: { userId: string }) =>
|
|
32
|
+
new UserWalletAlreadyCreatedError(userId),
|
|
33
|
+
ERROR_ADMIN_SIGNER_ALREADY_USED: () => new AdminAlreadyUsedError(),
|
|
34
|
+
ERROR_PROJECT_NONCUSTODIAL_WALLETS_NOT_ENABLED: () => new NonCustodialWalletsNotEnabledError(),
|
|
35
|
+
}
|
|
36
|
+
) {}
|
|
37
|
+
|
|
38
|
+
async throwErrorFromResponse({
|
|
39
|
+
response,
|
|
40
|
+
onServerErrorMessage,
|
|
41
|
+
}: {
|
|
42
|
+
response: Response;
|
|
43
|
+
onServerErrorMessage: string;
|
|
44
|
+
}) {
|
|
45
|
+
if (response.ok) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (response.status >= 500) {
|
|
50
|
+
throw new CrossmintServiceError(onServerErrorMessage, response.status);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (response.status === 402) {
|
|
54
|
+
throw new OutOfCreditsError();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const body = await response.json();
|
|
59
|
+
const code = body.code as CrossmintAPIErrorCodes | undefined;
|
|
60
|
+
if (code != null && this.errors[code] != null) {
|
|
61
|
+
throw this.errors[code](body);
|
|
62
|
+
}
|
|
63
|
+
if (body.message != null) {
|
|
64
|
+
throw new CrossmintServiceError(body.message, response.status);
|
|
65
|
+
}
|
|
66
|
+
} catch (e) {
|
|
67
|
+
console.error("Error parsing response", e);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
throw new CrossmintServiceError(await response.text(), response.status);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { validateAPIKey } from "@crossmint/common-sdk-base";
|
|
2
|
+
|
|
3
|
+
import { CrossmintServiceError } from "../error";
|
|
4
|
+
import { CROSSMINT_DEV_URL, CROSSMINT_PROD_URL, CROSSMINT_STG_URL } from "../utils/constants";
|
|
5
|
+
import { LoggerWrapper, logPerformance } from "../utils/log";
|
|
6
|
+
import { APIErrorService } from "./APIErrorService";
|
|
7
|
+
|
|
8
|
+
export abstract class BaseCrossmintService extends LoggerWrapper {
|
|
9
|
+
public crossmintAPIHeaders: Record<string, string>;
|
|
10
|
+
protected crossmintBaseUrl: string;
|
|
11
|
+
protected apiErrorService: APIErrorService;
|
|
12
|
+
private static urlMap: Record<string, string> = {
|
|
13
|
+
development: CROSSMINT_DEV_URL,
|
|
14
|
+
staging: CROSSMINT_STG_URL,
|
|
15
|
+
production: CROSSMINT_PROD_URL,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
constructor(apiKey: string) {
|
|
19
|
+
super("BaseCrossmintService");
|
|
20
|
+
const result = validateAPIKey(apiKey);
|
|
21
|
+
if (!result.isValid) {
|
|
22
|
+
throw new Error("API key invalid");
|
|
23
|
+
}
|
|
24
|
+
this.crossmintAPIHeaders = {
|
|
25
|
+
accept: "application/json",
|
|
26
|
+
"content-type": "application/json",
|
|
27
|
+
"x-api-key": apiKey,
|
|
28
|
+
};
|
|
29
|
+
this.crossmintBaseUrl = this.getUrlFromEnv(result.environment);
|
|
30
|
+
this.apiErrorService = new APIErrorService();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
protected async fetchCrossmintAPI(
|
|
34
|
+
endpoint: string,
|
|
35
|
+
options: { body?: string; method: string } = { method: "GET" },
|
|
36
|
+
onServerErrorMessage: string,
|
|
37
|
+
authToken?: string
|
|
38
|
+
) {
|
|
39
|
+
return logPerformance(
|
|
40
|
+
"FETCH_CROSSMINT_API",
|
|
41
|
+
async () => {
|
|
42
|
+
const url = `${this.crossmintBaseUrl}/${endpoint}`;
|
|
43
|
+
const { body, method } = options;
|
|
44
|
+
|
|
45
|
+
let response: Response;
|
|
46
|
+
try {
|
|
47
|
+
response = await fetch(url, {
|
|
48
|
+
body,
|
|
49
|
+
method,
|
|
50
|
+
headers: {
|
|
51
|
+
...this.crossmintAPIHeaders,
|
|
52
|
+
...(authToken != null && {
|
|
53
|
+
Authorization: `Bearer ${authToken}`,
|
|
54
|
+
}),
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
} catch (error) {
|
|
58
|
+
throw new CrossmintServiceError(`Error fetching Crossmint API: ${error}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
await this.apiErrorService.throwErrorFromResponse({
|
|
63
|
+
response,
|
|
64
|
+
onServerErrorMessage,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return await response.json();
|
|
69
|
+
},
|
|
70
|
+
{ endpoint }
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
protected getUrlFromEnv(environment: string) {
|
|
75
|
+
const url = BaseCrossmintService.urlMap[environment];
|
|
76
|
+
if (!url) {
|
|
77
|
+
console.log(" CrossmintService.urlMap: ", BaseCrossmintService.urlMap);
|
|
78
|
+
throw new Error(`URL not found for environment: ${environment}`);
|
|
79
|
+
}
|
|
80
|
+
return url;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { CROSSMINT_STG_URL } from "../utils/constants";
|
|
2
|
+
import { CrossmintWalletService } from "./CrossmintWalletService";
|
|
3
|
+
|
|
4
|
+
jest.mock("../services/logging", () => ({
|
|
5
|
+
logError: jest.fn(),
|
|
6
|
+
logInfo: jest.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
jest.mock("../utils/helpers", () => {
|
|
10
|
+
return {
|
|
11
|
+
isLocalhost: jest.fn().mockReturnValue(true),
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("CrossmintService", () => {
|
|
16
|
+
let crossmintService: CrossmintWalletService;
|
|
17
|
+
const apiKey =
|
|
18
|
+
"sk_staging_A4vDwAp4t5az6fVQMpQK6qapBnAqgpxrrD35TaFQnyKgxehNbd959uZeaHjNCadWDXrgLRAK1CxeasZjtYEq4TbFkKMBBvbQ9oinAxQf8LbHsSYW2DMzT8fBko3YGLq9t7ZiXZjmgkTioxGVUUjyLtWLeBKwNUDLgpshWjaoR7pKRnSE9SqhwjQbiK62VKiBTdA3KvHsyG9k8mLMcKrDyfXp";
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
crossmintService = new CrossmintWalletService(apiKey);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("constructor", () => {
|
|
25
|
+
it("should initialize with correct headers and base URL", () => {
|
|
26
|
+
expect(crossmintService).toBeDefined();
|
|
27
|
+
|
|
28
|
+
expect(crossmintService["crossmintAPIHeaders"]).toEqual({
|
|
29
|
+
accept: "application/json",
|
|
30
|
+
"content-type": "application/json",
|
|
31
|
+
"x-api-key": apiKey,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Check if the base URL is correctly set to the staging URL
|
|
35
|
+
expect(crossmintService["crossmintBaseUrl"]).toBe(CROSSMINT_STG_URL);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
jest.clearAllMocks();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { SignerData, StoreSmartWalletParams } from "@/types/API";
|
|
2
|
+
import type { UserParams } from "@/types/Config";
|
|
3
|
+
import { API_VERSION } from "@/utils/constants";
|
|
4
|
+
|
|
5
|
+
import type { EVMBlockchainIncludingTestnet } from "@crossmint/common-sdk-base";
|
|
6
|
+
|
|
7
|
+
import { BaseCrossmintService } from "./BaseCrossmintService";
|
|
8
|
+
|
|
9
|
+
export { EVMBlockchainIncludingTestnet } from "@crossmint/common-sdk-base";
|
|
10
|
+
|
|
11
|
+
export class CrossmintWalletService extends BaseCrossmintService {
|
|
12
|
+
async idempotentCreateSmartWallet(user: UserParams, input: StoreSmartWalletParams) {
|
|
13
|
+
return this.fetchCrossmintAPI(
|
|
14
|
+
`${API_VERSION}/sdk/smart-wallet`,
|
|
15
|
+
{ method: "PUT", body: JSON.stringify(input) },
|
|
16
|
+
"Error creating abstract wallet. Please contact support",
|
|
17
|
+
user.jwt
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async getSmartWalletConfig(
|
|
22
|
+
user: UserParams,
|
|
23
|
+
chain: EVMBlockchainIncludingTestnet
|
|
24
|
+
): Promise<{
|
|
25
|
+
kernelVersion: string;
|
|
26
|
+
entryPointVersion: string;
|
|
27
|
+
userId: string;
|
|
28
|
+
signers: { signerData: SignerData }[];
|
|
29
|
+
smartContractWalletAddress?: string;
|
|
30
|
+
}> {
|
|
31
|
+
return this.fetchCrossmintAPI(
|
|
32
|
+
`${API_VERSION}/sdk/smart-wallet/config?chain=${chain}`,
|
|
33
|
+
{ method: "GET" },
|
|
34
|
+
"Error getting smart wallet version configuration. Please contact support",
|
|
35
|
+
user.jwt
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async fetchNFTs(address: string, chain: EVMBlockchainIncludingTestnet) {
|
|
40
|
+
return this.fetchCrossmintAPI(
|
|
41
|
+
`v1-alpha1/wallets/${chain}:${address}/nfts`,
|
|
42
|
+
{ method: "GET" },
|
|
43
|
+
`Error fetching NFTs for wallet: ${address}`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public getPasskeyServerUrl(): string {
|
|
48
|
+
return this.crossmintBaseUrl + "/internal/passkeys";
|
|
49
|
+
}
|
|
50
|
+
}
|