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.
- package/README.md +2062 -0
- package/dist/Vault.d.ts +493 -0
- package/dist/Vault.js +1090 -0
- package/dist/chain/ChainService.d.ts +84 -0
- package/dist/chain/ChainService.js +136 -0
- package/dist/chain/SolanaChainService.d.ts +94 -0
- package/dist/chain/SolanaChainService.js +167 -0
- package/dist/config/chains.d.ts +233 -0
- package/dist/config/chains.js +285 -0
- package/dist/config/constants.d.ts +102 -0
- package/dist/config/constants.js +109 -0
- package/dist/config/environment.d.ts +46 -0
- package/dist/config/environment.js +114 -0
- package/dist/config/gas.d.ts +107 -0
- package/dist/config/gas.js +123 -0
- package/dist/crypto/EncryptionService.d.ts +74 -0
- package/dist/crypto/EncryptionService.js +178 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +96 -0
- package/dist/keyrings/HDKeyring.d.ts +72 -0
- package/dist/keyrings/HDKeyring.js +156 -0
- package/dist/keyrings/SimpleKeyring.d.ts +31 -0
- package/dist/keyrings/SimpleKeyring.js +49 -0
- package/dist/keyrings/SolanaKeyring.d.ts +71 -0
- package/dist/keyrings/SolanaKeyring.js +159 -0
- package/dist/services/BatchProcessor.d.ts +42 -0
- package/dist/services/BatchProcessor.js +188 -0
- package/dist/services/MultiTransferService.d.ts +78 -0
- package/dist/services/MultiTransferService.js +252 -0
- package/dist/services/QRCodeService.d.ts +193 -0
- package/dist/services/QRCodeService.js +299 -0
- package/dist/services/TokenUtils.d.ts +307 -0
- package/dist/services/TokenUtils.js +429 -0
- package/dist/services/nft/MetadataResolver.d.ts +57 -0
- package/dist/services/nft/MetadataResolver.js +162 -0
- package/dist/services/nft/NFTAPIService.d.ts +53 -0
- package/dist/services/nft/NFTAPIService.js +122 -0
- package/dist/services/nft/NFTService.d.ts +241 -0
- package/dist/services/nft/NFTService.js +910 -0
- package/dist/types/multiTransfer.d.ts +68 -0
- package/dist/types/multiTransfer.js +2 -0
- package/dist/types/nft/index.d.ts +68 -0
- package/dist/types/nft/index.js +2 -0
- package/dist/types/nft.d.ts +265 -0
- package/dist/types/nft.js +6 -0
- 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;
|