pwc-sdk-wallet 0.6.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 (46) hide show
  1. package/README.md +2062 -0
  2. package/dist/Vault.d.ts +493 -0
  3. package/dist/Vault.js +1090 -0
  4. package/dist/chain/ChainService.d.ts +84 -0
  5. package/dist/chain/ChainService.js +136 -0
  6. package/dist/chain/SolanaChainService.d.ts +94 -0
  7. package/dist/chain/SolanaChainService.js +167 -0
  8. package/dist/config/chains.d.ts +233 -0
  9. package/dist/config/chains.js +285 -0
  10. package/dist/config/constants.d.ts +102 -0
  11. package/dist/config/constants.js +109 -0
  12. package/dist/config/environment.d.ts +46 -0
  13. package/dist/config/environment.js +114 -0
  14. package/dist/config/gas.d.ts +107 -0
  15. package/dist/config/gas.js +123 -0
  16. package/dist/crypto/EncryptionService.d.ts +74 -0
  17. package/dist/crypto/EncryptionService.js +178 -0
  18. package/dist/index.d.ts +22 -0
  19. package/dist/index.js +96 -0
  20. package/dist/keyrings/HDKeyring.d.ts +72 -0
  21. package/dist/keyrings/HDKeyring.js +156 -0
  22. package/dist/keyrings/SimpleKeyring.d.ts +31 -0
  23. package/dist/keyrings/SimpleKeyring.js +49 -0
  24. package/dist/keyrings/SolanaKeyring.d.ts +71 -0
  25. package/dist/keyrings/SolanaKeyring.js +159 -0
  26. package/dist/services/BatchProcessor.d.ts +42 -0
  27. package/dist/services/BatchProcessor.js +188 -0
  28. package/dist/services/MultiTransferService.d.ts +78 -0
  29. package/dist/services/MultiTransferService.js +252 -0
  30. package/dist/services/QRCodeService.d.ts +193 -0
  31. package/dist/services/QRCodeService.js +299 -0
  32. package/dist/services/TokenUtils.d.ts +307 -0
  33. package/dist/services/TokenUtils.js +429 -0
  34. package/dist/services/nft/MetadataResolver.d.ts +57 -0
  35. package/dist/services/nft/MetadataResolver.js +162 -0
  36. package/dist/services/nft/NFTAPIService.d.ts +53 -0
  37. package/dist/services/nft/NFTAPIService.js +122 -0
  38. package/dist/services/nft/NFTService.d.ts +241 -0
  39. package/dist/services/nft/NFTService.js +910 -0
  40. package/dist/types/multiTransfer.d.ts +68 -0
  41. package/dist/types/multiTransfer.js +2 -0
  42. package/dist/types/nft/index.d.ts +68 -0
  43. package/dist/types/nft/index.js +2 -0
  44. package/dist/types/nft.d.ts +265 -0
  45. package/dist/types/nft.js +6 -0
  46. package/package.json +70 -0
@@ -0,0 +1,910 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NFTService = void 0;
4
+ const ethers_1 = require("ethers");
5
+ const ChainService_1 = require("../../chain/ChainService");
6
+ const SolanaChainService_1 = require("../../chain/SolanaChainService");
7
+ const chains_1 = require("../../config/chains");
8
+ const TokenUtils_1 = require("../TokenUtils");
9
+ /**
10
+ * Standard ERC-721 NFT interface ABI
11
+ */
12
+ const ERC721_ABI = [
13
+ "function name() view returns (string)",
14
+ "function symbol() view returns (string)",
15
+ "function ownerOf(uint256 tokenId) view returns (address)",
16
+ "function tokenURI(uint256 tokenId) view returns (string)",
17
+ "function balanceOf(address owner) view returns (uint256)",
18
+ "function totalSupply() view returns (uint256)",
19
+ "function transferFrom(address from, address to, uint256 tokenId)",
20
+ "function approve(address to, uint256 tokenId)",
21
+ "function setApprovalForAll(address operator, bool approved)",
22
+ "function getApproved(uint256 tokenId) view returns (address)",
23
+ "function isApprovedForAll(address owner, address operator) view returns (bool)",
24
+ "event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)"
25
+ ];
26
+ /**
27
+ * Standard ERC-1155 NFT interface ABI
28
+ */
29
+ const ERC1155_ABI = [
30
+ "function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data)",
31
+ "function safeBatchTransferFrom(address from, address to, uint256[] ids, uint256[] amounts, bytes data)",
32
+ "function balanceOf(address account, uint256 id) view returns (uint256)",
33
+ "function balanceOfBatch(address[] accounts, uint256[] ids) view returns (uint256[])",
34
+ "function setApprovalForAll(address operator, bool approved)",
35
+ "function isApprovedForAll(address account, address operator) view returns (bool)",
36
+ "function uri(uint256 id) view returns (string)",
37
+ "function name() view returns (string)",
38
+ "function symbol() view returns (string)",
39
+ "function exists(uint256 id) view returns (bool)",
40
+ "event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value)",
41
+ "event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values)",
42
+ "event ApprovalForAll(address indexed account, address indexed operator, bool approved)"
43
+ ];
44
+ /**
45
+ * Pure blockchain NFT service
46
+ * Reads all data directly from smart contracts without third-party APIs
47
+ */
48
+ class NFTService {
49
+ /**
50
+ * Creates a new NFTService instance.
51
+ * @param chainId - The chain ID to operate on
52
+ * @param vault - Optional vault instance for write operations
53
+ * @param fromAddress - Optional sender address for write operations
54
+ */
55
+ constructor(chainId, vault, fromAddress) {
56
+ this.chainId = chainId;
57
+ this.vault = vault;
58
+ // Note: Chain service will be initialized lazily when needed
59
+ // or explicitly via initializeForWrite() method
60
+ }
61
+ /**
62
+ * Initialize chain service for write operations
63
+ * @param vault - The vault instance
64
+ * @param fromAddress - The sender's address
65
+ */
66
+ async initializeChainService(vault, fromAddress) {
67
+ const chainConfig = (0, chains_1.getChainConfig)(this.chainId);
68
+ const privateKey = await vault.getPrivateKeyFor(fromAddress);
69
+ if (chainConfig.type === 'solana') {
70
+ this.chainService = new SolanaChainService_1.SolanaChainService(privateKey, chainConfig);
71
+ }
72
+ else {
73
+ this.chainService = new ChainService_1.ChainService(privateKey, chainConfig);
74
+ }
75
+ }
76
+ /**
77
+ * Initialize chain service for write operations after construction
78
+ * @param vault - The vault instance
79
+ * @param fromAddress - The sender's address
80
+ */
81
+ async initializeForWrite(vault, fromAddress) {
82
+ this.vault = vault;
83
+ await this.initializeChainService(vault, fromAddress);
84
+ }
85
+ /**
86
+ * Gets comprehensive NFT detail by reading directly from blockchain.
87
+ * @param contractAddress - NFT contract address
88
+ * @param tokenId - Token ID (string)
89
+ * @param options - Optional parameters for additional data
90
+ * @param options.includeMetadata - Whether to fetch and include metadata (default: false)
91
+ * @param options.includeHistory - Whether to fetch transaction history (default: false)
92
+ * @param options.includeCollection - Whether to fetch collection information (default: false)
93
+ * @returns Promise resolving to extended NFT detail
94
+ * @example
95
+ * ```typescript
96
+ * const nftDetail = await nftService.getNFTDetail(
97
+ * '0xContract...',
98
+ * '123',
99
+ * { includeMetadata: true, includeHistory: true }
100
+ * );
101
+ * console.log('NFT:', nftDetail.name, 'Owner:', nftDetail.owner);
102
+ * ```
103
+ */
104
+ async getNFTDetail(contractAddress, tokenId, options = {}) {
105
+ try {
106
+ // 1. Get on-chain data
107
+ const onChainData = await this.getOnChainData(contractAddress, tokenId);
108
+ // 2. Resolve metadata from tokenURI
109
+ let metadata;
110
+ if (options.includeMetadata && onChainData.tokenURI) {
111
+ metadata = await this.resolveMetadata(onChainData.tokenURI);
112
+ }
113
+ // 3. Get transaction history (on-chain events)
114
+ let transactionHistory = [];
115
+ if (options.includeHistory) {
116
+ transactionHistory = await this.getTransactionHistory(contractAddress, tokenId);
117
+ }
118
+ // 4. Get collection info
119
+ let collection;
120
+ if (options.includeCollection) {
121
+ collection = await this.getCollectionInfo(contractAddress);
122
+ }
123
+ const nftDetail = {
124
+ contractAddress,
125
+ tokenId,
126
+ name: metadata?.name || `NFT #${tokenId}`,
127
+ description: metadata?.description,
128
+ image: metadata?.image,
129
+ attributes: metadata?.attributes,
130
+ owner: onChainData.owner,
131
+ tokenType: onChainData.tokenType,
132
+ chainId: this.chainId,
133
+ tokenUri: onChainData.tokenURI,
134
+ createdAt: onChainData.createdAt,
135
+ lastTransferAt: onChainData.lastTransferAt,
136
+ metadata,
137
+ collection,
138
+ transactionHistory,
139
+ metadataSource: onChainData.tokenURI?.startsWith('ipfs://') ? 'ipfs' : 'http',
140
+ lastUpdated: Date.now()
141
+ };
142
+ return nftDetail;
143
+ }
144
+ catch (error) {
145
+ throw new Error(`Failed to get NFT detail: ${error instanceof Error ? error.message : 'Unknown error'}`);
146
+ }
147
+ }
148
+ /**
149
+ * Gets on-chain NFT data directly from smart contract
150
+ * @param contractAddress - NFT contract address
151
+ * @param tokenId - Token ID
152
+ * @returns Promise resolving to on-chain data
153
+ */
154
+ async getOnChainData(contractAddress, tokenId) {
155
+ const chainConfig = (0, chains_1.getChainConfig)(this.chainId);
156
+ if (chainConfig.type === 'solana') {
157
+ return this.getSolanaOnChainData(contractAddress, tokenId);
158
+ }
159
+ else {
160
+ return this.getEVMOnChainData(contractAddress, tokenId);
161
+ }
162
+ }
163
+ /**
164
+ * Gets EVM NFT on-chain data
165
+ * @param contractAddress - NFT contract address
166
+ * @param tokenId - Token ID
167
+ * @returns Promise resolving to on-chain data
168
+ */
169
+ async getEVMOnChainData(contractAddress, tokenId) {
170
+ const provider = (0, TokenUtils_1.getProviderAuto)(this.chainId);
171
+ if (!provider) {
172
+ throw new Error('Provider not available');
173
+ }
174
+ // Try ERC-721 first
175
+ try {
176
+ const contract = new ethers_1.Contract(contractAddress, ERC721_ABI, provider);
177
+ const [owner, tokenURI, name, symbol] = await Promise.all([
178
+ contract.ownerOf(tokenId),
179
+ contract.tokenURI(tokenId),
180
+ contract.name().catch(() => 'Unknown'),
181
+ contract.symbol().catch(() => 'UNKNOWN')
182
+ ]);
183
+ return {
184
+ owner,
185
+ tokenURI,
186
+ name,
187
+ symbol,
188
+ tokenType: 'ERC-721',
189
+ createdAt: await this.getMintTimestamp(contractAddress, tokenId),
190
+ lastTransferAt: await this.getLastTransferTimestamp(contractAddress, tokenId)
191
+ };
192
+ }
193
+ catch (error) {
194
+ // Try ERC-1155 if ERC-721 fails
195
+ try {
196
+ const contract = new ethers_1.Contract(contractAddress, ERC1155_ABI, provider);
197
+ const [uri, name, symbol] = await Promise.all([
198
+ contract.uri(tokenId),
199
+ contract.name().catch(() => 'Unknown'),
200
+ contract.symbol().catch(() => 'UNKNOWN')
201
+ ]);
202
+ // For ERC-1155, we need to check if token exists
203
+ const exists = await contract.exists(tokenId);
204
+ if (!exists) {
205
+ throw new Error('Token does not exist');
206
+ }
207
+ return {
208
+ owner: 'Multiple Owners', // ERC-1155 can have multiple owners for same token
209
+ tokenURI: uri,
210
+ name,
211
+ symbol,
212
+ tokenType: 'ERC-1155',
213
+ createdAt: await this.getMintTimestamp(contractAddress, tokenId),
214
+ lastTransferAt: await this.getLastTransferTimestamp(contractAddress, tokenId)
215
+ };
216
+ }
217
+ catch (erc1155Error) {
218
+ throw new Error('Contract is not a valid ERC-721 or ERC-1155 NFT');
219
+ }
220
+ }
221
+ }
222
+ /**
223
+ * Gets Solana NFT on-chain data
224
+ * @param contractAddress - NFT mint address
225
+ * @param tokenId - Token ID (not used for Solana)
226
+ * @returns Promise resolving to on-chain data
227
+ */
228
+ async getSolanaOnChainData(contractAddress, tokenId) {
229
+ // For Solana, contractAddress is the mint address
230
+ const mintAddress = contractAddress;
231
+ // This would need Solana-specific implementation
232
+ // For now, return basic structure
233
+ return {
234
+ owner: 'Unknown', // Would get from Solana program
235
+ tokenURI: '', // Would get from metadata account
236
+ name: 'Solana NFT',
237
+ symbol: 'SOL-NFT',
238
+ tokenType: 'SPL-NFT',
239
+ createdAt: Date.now(),
240
+ lastTransferAt: Date.now()
241
+ };
242
+ }
243
+ /**
244
+ * Resolves metadata from tokenURI (IPFS or HTTP)
245
+ * @param tokenURI - Token URI
246
+ * @returns Promise resolving to metadata
247
+ */
248
+ async resolveMetadata(tokenURI) {
249
+ try {
250
+ let url = tokenURI;
251
+ // Handle IPFS URLs
252
+ if (tokenURI.startsWith('ipfs://')) {
253
+ url = tokenURI.replace('ipfs://', 'https://ipfs.io/ipfs/');
254
+ }
255
+ const response = await fetch(url);
256
+ if (!response.ok) {
257
+ throw new Error(`Failed to fetch metadata: ${response.statusText}`);
258
+ }
259
+ const metadata = await response.json();
260
+ // Validate required fields
261
+ if (!metadata.name) {
262
+ throw new Error('Metadata missing required field: name');
263
+ }
264
+ return metadata;
265
+ }
266
+ catch (error) {
267
+ throw new Error(`Failed to resolve metadata: ${error instanceof Error ? error.message : 'Unknown error'}`);
268
+ }
269
+ }
270
+ /**
271
+ * Gets transaction history from blockchain events
272
+ * @param contractAddress - NFT contract address
273
+ * @param tokenId - Token ID
274
+ * @returns Promise resolving to transaction history
275
+ */
276
+ async getTransactionHistory(contractAddress, tokenId) {
277
+ const chainConfig = (0, chains_1.getChainConfig)(this.chainId);
278
+ if (chainConfig.type === 'evm') {
279
+ const provider = (0, TokenUtils_1.getProviderAuto)(this.chainId);
280
+ if (!provider) {
281
+ return [];
282
+ }
283
+ // Try ERC-721 first
284
+ try {
285
+ const contract721 = new ethers_1.Contract(contractAddress, ERC721_ABI, provider);
286
+ // Get Transfer events for this token
287
+ const filter = contract721.filters.Transfer(null, null, tokenId);
288
+ const events = await contract721.queryFilter(filter, 0, 'latest');
289
+ return events.map(event => {
290
+ const eventLog = event;
291
+ return {
292
+ hash: event.transactionHash,
293
+ from: eventLog.args?.[0] || '',
294
+ to: eventLog.args?.[1] || '',
295
+ blockNumber: event.blockNumber,
296
+ timestamp: Date.now(), // Would need to get from block
297
+ type: eventLog.args?.[0] === '0x0000000000000000000000000000000000000000' ? 'mint' : 'transfer'
298
+ };
299
+ });
300
+ }
301
+ catch (error) {
302
+ // Try ERC-1155 if ERC-721 fails
303
+ try {
304
+ const contract1155 = new ethers_1.Contract(contractAddress, [
305
+ ...ERC1155_ABI,
306
+ "event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value)",
307
+ "event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values)"
308
+ ], provider);
309
+ // Get TransferSingle events for this token
310
+ const singleFilter = contract1155.filters.TransferSingle(null, null, null, tokenId);
311
+ const singleEvents = await contract1155.queryFilter(singleFilter, 0, 'latest');
312
+ // Get TransferBatch events that include this token
313
+ const batchFilter = contract1155.filters.TransferBatch(null, null, null);
314
+ const batchEvents = await contract1155.queryFilter(batchFilter, 0, 'latest');
315
+ const events = [];
316
+ // Process TransferSingle events
317
+ singleEvents.forEach(event => {
318
+ const eventLog = event;
319
+ events.push({
320
+ hash: event.transactionHash,
321
+ from: eventLog.args?.[1] || '', // from
322
+ to: eventLog.args?.[2] || '', // to
323
+ blockNumber: event.blockNumber,
324
+ timestamp: Date.now(),
325
+ type: eventLog.args?.[1] === '0x0000000000000000000000000000000000000000' ? 'mint' : 'transfer'
326
+ });
327
+ });
328
+ // Process TransferBatch events that include this token
329
+ batchEvents.forEach(event => {
330
+ const eventLog = event;
331
+ const ids = eventLog.args?.[3] || []; // token IDs
332
+ const values = eventLog.args?.[4] || []; // amounts
333
+ const tokenIndex = ids.findIndex((id) => id.toString() === tokenId);
334
+ if (tokenIndex !== -1) {
335
+ events.push({
336
+ hash: event.transactionHash,
337
+ from: eventLog.args?.[1] || '', // from
338
+ to: eventLog.args?.[2] || '', // to
339
+ blockNumber: event.blockNumber,
340
+ timestamp: Date.now(),
341
+ type: eventLog.args?.[1] === '0x0000000000000000000000000000000000000000' ? 'mint' : 'transfer'
342
+ });
343
+ }
344
+ });
345
+ return events;
346
+ }
347
+ catch (erc1155Error) {
348
+ // If both fail, return empty array
349
+ return [];
350
+ }
351
+ }
352
+ }
353
+ return [];
354
+ }
355
+ /**
356
+ * Gets collection information from contract
357
+ * @param contractAddress - NFT contract address
358
+ * @returns Promise resolving to collection info
359
+ */
360
+ async getCollectionInfo(contractAddress) {
361
+ const chainConfig = (0, chains_1.getChainConfig)(this.chainId);
362
+ if (chainConfig.type === 'evm') {
363
+ const provider = (0, TokenUtils_1.getProviderAuto)(this.chainId);
364
+ if (!provider) {
365
+ throw new Error('Provider not available');
366
+ }
367
+ // Try ERC-721 first
368
+ try {
369
+ const contract721 = new ethers_1.Contract(contractAddress, ERC721_ABI, provider);
370
+ const [name, symbol, totalSupply] = await Promise.all([
371
+ contract721.name().catch(() => 'Unknown Collection'),
372
+ contract721.symbol().catch(() => 'UNKNOWN'),
373
+ contract721.totalSupply().catch(() => 0)
374
+ ]);
375
+ return {
376
+ contractAddress,
377
+ name,
378
+ symbol,
379
+ tokenType: 'ERC-721',
380
+ chainId: this.chainId,
381
+ totalSupply: Number(totalSupply)
382
+ };
383
+ }
384
+ catch (error) {
385
+ // Try ERC-1155 if ERC-721 fails
386
+ try {
387
+ const contract1155 = new ethers_1.Contract(contractAddress, ERC1155_ABI, provider);
388
+ const [name, symbol] = await Promise.all([
389
+ contract1155.name().catch(() => 'Unknown Collection'),
390
+ contract1155.symbol().catch(() => 'UNKNOWN')
391
+ ]);
392
+ // ERC-1155 doesn't have totalSupply, so we can't get it easily
393
+ return {
394
+ contractAddress,
395
+ name,
396
+ symbol,
397
+ tokenType: 'ERC-1155',
398
+ chainId: this.chainId
399
+ // totalSupply not available for ERC-1155
400
+ };
401
+ }
402
+ catch (erc1155Error) {
403
+ // If both fail, return basic info
404
+ return {
405
+ contractAddress,
406
+ name: 'Unknown Collection',
407
+ symbol: 'UNKNOWN',
408
+ tokenType: 'ERC-721', // Default fallback
409
+ chainId: this.chainId
410
+ };
411
+ }
412
+ }
413
+ }
414
+ return {
415
+ contractAddress,
416
+ name: 'Solana Collection',
417
+ tokenType: 'SPL-NFT',
418
+ chainId: this.chainId
419
+ };
420
+ }
421
+ /**
422
+ * Gets NFT balance for an address from a specific contract.
423
+ * Supports both ERC-721 and ERC-1155 contracts.
424
+ * @param address - Wallet address to check balance for
425
+ * @param contractAddress - NFT contract address
426
+ * @param tokenId - (Optional) Token ID for ERC-1155 contracts
427
+ * @returns Promise resolving to NFT balance information
428
+ * @example
429
+ * ```typescript
430
+ * // For ERC-721
431
+ * const balance = await nftService.getNFTBalance('0xAccount...', '0xContract...');
432
+ * console.log('ERC-721 Count:', balance.count);
433
+ *
434
+ * // For ERC-1155
435
+ * const balance = await nftService.getNFTBalance('0xAccount...', '0xContract...', '123');
436
+ * console.log('ERC-1155 Balance:', balance.count);
437
+ * ```
438
+ */
439
+ async getNFTBalance(address, contractAddress, tokenId) {
440
+ const chainConfig = (0, chains_1.getChainConfig)(this.chainId);
441
+ if (chainConfig.type === 'evm') {
442
+ const provider = (0, TokenUtils_1.getProviderAuto)(this.chainId);
443
+ if (!provider) {
444
+ throw new Error('Provider not available');
445
+ }
446
+ // Try ERC-721 first
447
+ try {
448
+ const contract721 = new ethers_1.Contract(contractAddress, ERC721_ABI, provider);
449
+ const balance = await contract721.balanceOf(address);
450
+ // Get token IDs owned by this address (simplified for now)
451
+ const tokenIds = [];
452
+ // This would require additional logic to get all token IDs
453
+ // For now, return basic balance info
454
+ return {
455
+ contractAddress,
456
+ tokenIds,
457
+ count: Number(balance),
458
+ tokenType: 'ERC-721',
459
+ chainId: this.chainId
460
+ };
461
+ }
462
+ catch (error) {
463
+ // Try ERC-1155 if ERC-721 fails
464
+ try {
465
+ const contract1155 = new ethers_1.Contract(contractAddress, ERC1155_ABI, provider);
466
+ if (tokenId) {
467
+ // Get balance for specific token ID
468
+ const exists = await contract1155.exists(tokenId).catch(() => true);
469
+ if (!exists) {
470
+ return {
471
+ contractAddress,
472
+ tokenIds: [],
473
+ count: 0,
474
+ tokenType: 'ERC-1155',
475
+ chainId: this.chainId
476
+ };
477
+ }
478
+ const balance = await contract1155.balanceOf(address, tokenId);
479
+ return {
480
+ contractAddress,
481
+ tokenIds: [tokenId],
482
+ count: Number(balance),
483
+ tokenType: 'ERC-1155',
484
+ chainId: this.chainId
485
+ };
486
+ }
487
+ else {
488
+ // For ERC-1155 without tokenId, we can't get total count easily
489
+ // Return basic info indicating it's ERC-1155
490
+ return {
491
+ contractAddress,
492
+ tokenIds: [],
493
+ count: 0,
494
+ tokenType: 'ERC-1155',
495
+ chainId: this.chainId
496
+ };
497
+ }
498
+ }
499
+ catch (erc1155Error) {
500
+ throw new Error('Contract is not a valid ERC-721 or ERC-1155 NFT');
501
+ }
502
+ }
503
+ }
504
+ return {
505
+ contractAddress,
506
+ tokenIds: [],
507
+ count: 0,
508
+ tokenType: 'SPL-NFT',
509
+ chainId: this.chainId
510
+ };
511
+ }
512
+ /**
513
+ * Gets ERC-1155 token balance for a specific address and token ID.
514
+ * @param address - Wallet address to check balance for
515
+ * @param contractAddress - ERC-1155 contract address
516
+ * @param tokenId - Token ID to check balance for
517
+ * @returns Promise resolving to the balance amount as bigint
518
+ * @example
519
+ * ```typescript
520
+ * const balance = await nftService.getERC1155Balance('0xAccount...', '0xContract...', '123');
521
+ * console.log('ERC-1155 Balance:', balance.toString());
522
+ * ```
523
+ */
524
+ async getERC1155Balance(address, contractAddress, tokenId) {
525
+ const chainConfig = (0, chains_1.getChainConfig)(this.chainId);
526
+ if (chainConfig.type === 'solana') {
527
+ throw new Error('ERC-1155 is not supported on Solana');
528
+ }
529
+ const provider = (0, TokenUtils_1.getProviderAuto)(this.chainId);
530
+ if (!provider) {
531
+ throw new Error('Provider not available');
532
+ }
533
+ try {
534
+ const contract = new ethers_1.Contract(contractAddress, ERC1155_ABI, provider);
535
+ // Check if token exists
536
+ const exists = await contract.exists(tokenId).catch(() => true); // fallback true if not implemented
537
+ if (!exists) {
538
+ return BigInt(0); // Token doesn't exist, return 0 balance
539
+ }
540
+ // Get balance for specific token ID
541
+ const balance = await contract.balanceOf(address, tokenId);
542
+ return balance;
543
+ }
544
+ catch (error) {
545
+ throw new Error(`Failed to get ERC-1155 balance: ${error instanceof Error ? error.message : 'Unknown error'}`);
546
+ }
547
+ }
548
+ /**
549
+ * Gets all ERC-1155 token balances for an address from a specific contract.
550
+ * @param address - Wallet address to check balances for
551
+ * @param contractAddress - ERC-1155 contract address
552
+ * @param tokenIds - Array of token IDs to check
553
+ * @returns Promise resolving to array of balance objects
554
+ * @example
555
+ * ```typescript
556
+ * const balances = await nftService.getERC1155Balances('0xAccount...', '0xContract...', ['123', '456', '789']);
557
+ * balances.forEach(balance => {
558
+ * console.log(`Token ${balance.tokenId}: ${balance.amount.toString()}`);
559
+ * });
560
+ * ```
561
+ */
562
+ async getERC1155Balances(address, contractAddress, tokenIds) {
563
+ const chainConfig = (0, chains_1.getChainConfig)(this.chainId);
564
+ if (chainConfig.type === 'solana') {
565
+ throw new Error('ERC-1155 is not supported on Solana');
566
+ }
567
+ const provider = (0, TokenUtils_1.getProviderAuto)(this.chainId);
568
+ if (!provider) {
569
+ throw new Error('Provider not available');
570
+ }
571
+ try {
572
+ const contract = new ethers_1.Contract(contractAddress, ERC1155_ABI, provider);
573
+ // Get balances for all token IDs in parallel
574
+ const balancePromises = tokenIds.map(async (tokenId) => {
575
+ try {
576
+ const exists = await contract.exists(tokenId).catch(() => true);
577
+ if (!exists) {
578
+ return { tokenId, amount: BigInt(0) };
579
+ }
580
+ const balance = await contract.balanceOf(address, tokenId);
581
+ return { tokenId, amount: balance };
582
+ }
583
+ catch (error) {
584
+ console.warn(`Failed to get balance for token ${tokenId}:`, error);
585
+ return { tokenId, amount: BigInt(0) };
586
+ }
587
+ });
588
+ const balances = await Promise.all(balancePromises);
589
+ return balances;
590
+ }
591
+ catch (error) {
592
+ throw new Error(`Failed to get ERC-1155 balances: ${error instanceof Error ? error.message : 'Unknown error'}`);
593
+ }
594
+ }
595
+ /**
596
+ * Gets mint timestamp from blockchain
597
+ * @param contractAddress - NFT contract address
598
+ * @param tokenId - Token ID
599
+ * @returns Promise resolving to mint timestamp
600
+ */
601
+ async getMintTimestamp(contractAddress, tokenId) {
602
+ // This would require querying the first Transfer event for this token
603
+ // For now, return current timestamp
604
+ return Date.now();
605
+ }
606
+ /**
607
+ * Gets last transfer timestamp from blockchain
608
+ * @param contractAddress - NFT contract address
609
+ * @param tokenId - Token ID
610
+ * @returns Promise resolving to last transfer timestamp
611
+ */
612
+ async getLastTransferTimestamp(contractAddress, tokenId) {
613
+ // This would require querying the last Transfer event for this token
614
+ // For now, return current timestamp
615
+ return Date.now();
616
+ }
617
+ /**
618
+ * Estimates gas cost for transferring an NFT
619
+ * @param fromAddress - The sender's address
620
+ * @param toAddress - The recipient's address
621
+ * @param contractAddress - The NFT contract address
622
+ * @param tokenId - The token ID to transfer
623
+ * @param amount - (Optional) Amount to transfer (for ERC-1155, default 1 for ERC-721)
624
+ * @returns Promise resolving to the estimated gas cost as bigint
625
+ * @example
626
+ * ```typescript
627
+ * const gasEstimate = await nftService.estimateGasForTransfer(
628
+ * '0xSender...',
629
+ * '0xRecipient...',
630
+ * '0xContract...',
631
+ * '123'
632
+ * );
633
+ * console.log('Estimated gas:', gasEstimate.toString());
634
+ * ```
635
+ */
636
+ async estimateGasForTransfer(fromAddress, toAddress, contractAddress, tokenId, amount = "1") {
637
+ const chainConfig = (0, chains_1.getChainConfig)(this.chainId);
638
+ if (chainConfig.type === 'solana') {
639
+ // For Solana, return a fixed estimate (would need Solana-specific implementation)
640
+ return BigInt(5000); // Placeholder value
641
+ }
642
+ const provider = (0, TokenUtils_1.getProviderAuto)(this.chainId);
643
+ if (!provider) {
644
+ throw new Error('Provider not available');
645
+ }
646
+ // Try ERC-721 first
647
+ try {
648
+ const contract721 = new ethers_1.Contract(contractAddress, ERC721_ABI, provider);
649
+ // Check if token exists as ERC-721
650
+ await contract721.ownerOf(tokenId); // Throws if not ERC-721
651
+ // Estimate gas for ERC-721 transfer
652
+ return await contract721.transferFrom.estimateGas(fromAddress, toAddress, tokenId);
653
+ }
654
+ catch (e) {
655
+ // Try ERC-1155
656
+ try {
657
+ const contract1155 = new ethers_1.Contract(contractAddress, ERC1155_ABI, provider);
658
+ // Check if token exists as ERC-1155
659
+ const exists = await contract1155.exists(tokenId).catch(() => true); // fallback true if not implemented
660
+ if (!exists)
661
+ throw new Error('ERC-1155 token does not exist');
662
+ // Parse amount to ensure it's a valid number
663
+ const parsedAmount = amount ? amount.toString() : "1";
664
+ // For ERC-1155 gas estimation, we need to use a wallet with private key
665
+ // to pass the "caller is not owner nor approved" check
666
+ if (!this.vault) {
667
+ // If no vault available, return a reasonable estimate
668
+ console.warn('No vault available for ERC-1155 gas estimation. Returning estimated value.');
669
+ return BigInt(65000); // Conservative estimate for ERC-1155 transfer
670
+ }
671
+ try {
672
+ // Create contract with wallet for gas estimation
673
+ const contractWithWallet = await this.createContractWithWallet(contractAddress, ERC1155_ABI, fromAddress);
674
+ // Estimate gas for ERC-1155 transfer with wallet
675
+ return await contractWithWallet.safeTransferFrom.estimateGas(fromAddress, toAddress, tokenId, parsedAmount, '0x');
676
+ }
677
+ catch (walletError) {
678
+ // If wallet estimation fails, return conservative estimate
679
+ console.warn('Wallet-based gas estimation failed, returning conservative estimate:', walletError);
680
+ return BigInt(65000); // Conservative estimate for ERC-1155 transfer
681
+ }
682
+ }
683
+ catch (erc1155Error) {
684
+ console.error('ERC-1155 gas estimation error:', erc1155Error);
685
+ throw new Error(`Contract is not a valid ERC-721 or ERC-1155 NFT: ${erc1155Error instanceof Error ? erc1155Error.message : 'Unknown error'}`);
686
+ }
687
+ }
688
+ }
689
+ /**
690
+ * Estimates gas cost for various NFT operations
691
+ * @param operation - The operation to estimate gas for
692
+ * @param contractAddress - The NFT contract address
693
+ * @param params - Operation-specific parameters
694
+ * @returns Promise resolving to the estimated gas cost as bigint
695
+ * @example
696
+ * ```typescript
697
+ * // Estimate gas for transfer
698
+ * const transferGas = await nftService.estimateGas('transfer', contractAddress, {
699
+ * fromAddress: '0xSender...',
700
+ * toAddress: '0xRecipient...',
701
+ * tokenId: '123'
702
+ * });
703
+ *
704
+ * // Estimate gas for approve
705
+ * const approveGas = await nftService.estimateGas('approve', contractAddress, {
706
+ * toAddress: '0xSpender...',
707
+ * tokenId: '123'
708
+ * });
709
+ * ```
710
+ */
711
+ async estimateGas(operation, contractAddress, params) {
712
+ const chainConfig = (0, chains_1.getChainConfig)(this.chainId);
713
+ if (chainConfig.type === 'solana') {
714
+ // For Solana, return a fixed estimate (would need Solana-specific implementation)
715
+ return BigInt(5000); // Placeholder value
716
+ }
717
+ const provider = (0, TokenUtils_1.getProviderAuto)(this.chainId);
718
+ if (!provider) {
719
+ throw new Error('Provider not available');
720
+ }
721
+ // Try ERC-721 first
722
+ try {
723
+ const contract721 = new ethers_1.Contract(contractAddress, ERC721_ABI, provider);
724
+ switch (operation) {
725
+ case 'transfer':
726
+ if (!params.fromAddress || !params.tokenId) {
727
+ throw new Error('fromAddress and tokenId are required for transfer operation');
728
+ }
729
+ // Check if token exists as ERC-721
730
+ await contract721.ownerOf(params.tokenId);
731
+ return await contract721.transferFrom.estimateGas(params.fromAddress, params.toAddress, params.tokenId);
732
+ case 'approve':
733
+ if (!params.tokenId) {
734
+ throw new Error('tokenId is required for approve operation');
735
+ }
736
+ return await contract721.approve.estimateGas(params.toAddress, params.tokenId);
737
+ case 'setApprovalForAll':
738
+ if (params.approved === undefined) {
739
+ throw new Error('approved parameter is required for setApprovalForAll operation');
740
+ }
741
+ return await contract721.setApprovalForAll.estimateGas(params.toAddress, params.approved);
742
+ default:
743
+ throw new Error(`Unsupported operation: ${operation}`);
744
+ }
745
+ }
746
+ catch (e) {
747
+ // Try ERC-1155
748
+ try {
749
+ const contract1155 = new ethers_1.Contract(contractAddress, ERC1155_ABI, provider);
750
+ switch (operation) {
751
+ case 'transfer':
752
+ if (!params.fromAddress || !params.tokenId) {
753
+ throw new Error('fromAddress and tokenId are required for transfer operation');
754
+ }
755
+ const amount = params.amount || "1";
756
+ // Check if token exists as ERC-1155
757
+ const exists = await contract1155.exists(params.tokenId).catch(() => true);
758
+ if (!exists)
759
+ throw new Error('ERC-1155 token does not exist');
760
+ // For ERC-1155 gas estimation, we need to use a wallet with private key
761
+ if (!this.vault) {
762
+ console.warn('No vault available for ERC-1155 gas estimation. Returning estimated value.');
763
+ return BigInt(65000); // Conservative estimate for ERC-1155 transfer
764
+ }
765
+ try {
766
+ // Create contract with wallet for gas estimation
767
+ const contractWithWallet = await this.createContractWithWallet(contractAddress, ERC1155_ABI, params.fromAddress);
768
+ return await contractWithWallet.safeTransferFrom.estimateGas(params.fromAddress, params.toAddress, params.tokenId, amount, '0x');
769
+ }
770
+ catch (walletError) {
771
+ console.warn('Wallet-based gas estimation failed, returning conservative estimate:', walletError);
772
+ return BigInt(65000); // Conservative estimate for ERC-1155 transfer
773
+ }
774
+ case 'approve':
775
+ case 'setApprovalForAll':
776
+ throw new Error(`Operation ${operation} not supported for ERC-1155`);
777
+ default:
778
+ throw new Error(`Unsupported operation: ${operation}`);
779
+ }
780
+ }
781
+ catch (erc1155Error) {
782
+ throw new Error('Contract is not a valid ERC-721 or ERC-1155 NFT');
783
+ }
784
+ }
785
+ }
786
+ /**
787
+ * Transfers an NFT using Vault (RECOMMENDED). Supports both ERC-721 and ERC-1155.
788
+ * @param fromAddress - The sender's address
789
+ * @param toAddress - The recipient's address
790
+ * @param contractAddress - The NFT contract address
791
+ * @param tokenId - The token ID to transfer
792
+ * @param amount - (Optional) Amount to transfer (for ERC-1155, default 1 for ERC-721)
793
+ * @returns Promise resolving to the transaction response
794
+ */
795
+ async transferNFTWithVault(fromAddress, toAddress, contractAddress, tokenId, amount = "1") {
796
+ // Check if vault is available
797
+ if (!this.vault) {
798
+ throw new Error('Vault not available. Please either:\n' +
799
+ '1. Use the constructor with vault: new NFTService(chainId, vault)\n' +
800
+ '2. Call initializeForWrite(vault, fromAddress) before using this method');
801
+ }
802
+ // Try ERC-721 first
803
+ try {
804
+ const contract721 = await this.createContractWithWallet(contractAddress, ERC721_ABI, fromAddress);
805
+ // Check if token exists as ERC-721
806
+ await contract721.ownerOf(tokenId); // Throws if not ERC-721
807
+ return await contract721.transferFrom(fromAddress, toAddress, tokenId);
808
+ }
809
+ catch (e) {
810
+ // Try ERC-1155
811
+ const contract1155 = await this.createContractWithWallet(contractAddress, ERC1155_ABI, fromAddress);
812
+ // Check if token exists as ERC-1155
813
+ const exists = await contract1155.exists(tokenId).catch(() => true); // fallback true if not implemented
814
+ if (!exists)
815
+ throw new Error('ERC-1155 token does not exist');
816
+ return await contract1155.safeTransferFrom(fromAddress, toAddress, tokenId, amount, '0x');
817
+ }
818
+ }
819
+ /**
820
+ * @deprecated Use transferNFTWithVault instead for better security
821
+ * Transfers an NFT from one address to another. Supports both ERC-721 and ERC-1155.
822
+ * @param fromAddress - The sender's address
823
+ * @param toAddress - The recipient's address
824
+ * @param contractAddress - The NFT contract address
825
+ * @param tokenId - The token ID to transfer
826
+ * @param amount - (Optional) Amount to transfer (for ERC-1155, default 1 for ERC-721)
827
+ * @returns Promise resolving to the transaction response
828
+ */
829
+ async transferNFT(fromAddress, toAddress, contractAddress, tokenId, amount = "1") {
830
+ if (!this.chainService) {
831
+ throw new Error('Chain service not initialized. Please either:\n' +
832
+ '1. Use the constructor with vault: new NFTService(chainId, vault)\n' +
833
+ '2. Call initializeForWrite(vault, fromAddress) before using this method\n' +
834
+ '3. Use transferNFTWithVault(fromAddress, toAddress, contractAddress, tokenId) instead (recommended)');
835
+ }
836
+ const provider = (0, chains_1.getProvider)(this.chainId);
837
+ // Try ERC-721 first
838
+ try {
839
+ const contract721 = new ethers_1.Contract(contractAddress, ERC721_ABI, provider);
840
+ await contract721.ownerOf(tokenId);
841
+ return await contract721.transferFrom(fromAddress, toAddress, tokenId);
842
+ }
843
+ catch (e) {
844
+ // Try ERC-1155
845
+ const contract1155 = new ethers_1.Contract(contractAddress, ERC1155_ABI, provider);
846
+ const exists = await contract1155.exists(tokenId).catch(() => true);
847
+ if (!exists)
848
+ throw new Error('ERC-1155 token does not exist');
849
+ return await contract1155.safeTransferFrom(fromAddress, toAddress, tokenId, amount, '0x');
850
+ }
851
+ }
852
+ /**
853
+ * Checks if an address is owner or approved for ERC-1155 token
854
+ * @param address - Address to check
855
+ * @param contractAddress - ERC-1155 contract address
856
+ * @param tokenId - Token ID
857
+ * @returns Promise resolving to boolean indicating if address can transfer
858
+ */
859
+ async canTransferERC1155(address, contractAddress, tokenId) {
860
+ const chainConfig = (0, chains_1.getChainConfig)(this.chainId);
861
+ if (chainConfig.type === 'solana') {
862
+ throw new Error('ERC-1155 is not supported on Solana');
863
+ }
864
+ const provider = (0, TokenUtils_1.getProviderAuto)(this.chainId);
865
+ if (!provider) {
866
+ throw new Error('Provider not available');
867
+ }
868
+ try {
869
+ const contract = new ethers_1.Contract(contractAddress, ERC1155_ABI, provider);
870
+ // Check if token exists
871
+ const exists = await contract.exists(tokenId).catch(() => true);
872
+ if (!exists) {
873
+ return false;
874
+ }
875
+ // Check balance
876
+ const balance = await contract.balanceOf(address, tokenId);
877
+ if (balance > 0) {
878
+ return true; // Owner can transfer
879
+ }
880
+ // Check if address is approved for all tokens
881
+ const isApprovedForAll = await contract.isApprovedForAll(address, address);
882
+ return isApprovedForAll;
883
+ }
884
+ catch (error) {
885
+ console.error('Error checking ERC-1155 transfer permissions:', error);
886
+ return false;
887
+ }
888
+ }
889
+ /**
890
+ * Creates a contract instance with wallet for gas estimation
891
+ * @param contractAddress - Contract address
892
+ * @param abi - Contract ABI
893
+ * @param fromAddress - Address to use for signing
894
+ * @returns Promise resolving to contract instance with wallet
895
+ */
896
+ async createContractWithWallet(contractAddress, abi, fromAddress) {
897
+ if (!this.vault) {
898
+ throw new Error('Vault not available for wallet-based operations');
899
+ }
900
+ // Initialize chain service if not already done
901
+ if (!this.chainService) {
902
+ await this.initializeChainService(this.vault, fromAddress);
903
+ }
904
+ const provider = (0, chains_1.getProvider)(this.chainId);
905
+ const privateKey = await this.vault.getPrivateKeyFor(fromAddress);
906
+ const wallet = new ethers_1.ethers.Wallet(privateKey, provider);
907
+ return new ethers_1.Contract(contractAddress, abi, wallet);
908
+ }
909
+ }
910
+ exports.NFTService = NFTService;