@quantabit/nft-sdk 1.0.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/dist/index.cjs ADDED
@@ -0,0 +1,1231 @@
1
+ 'use strict';
2
+
3
+ var sdkConfig = require('@quantabit/sdk-config');
4
+ var React = require('react');
5
+
6
+ /**
7
+ * NFT SDK - API 客户端
8
+ * NFT 系统后端接口封装
9
+ *
10
+ * 使用 BaseApiClient 基类简化代码
11
+ */
12
+
13
+
14
+ /**
15
+ * NFT API 客户端
16
+ */
17
+ class NftApiClient extends sdkConfig.BaseApiClient {
18
+ constructor(config = {}) {
19
+ super('/nft', config);
20
+ }
21
+
22
+ // ============ NFT 查询 ============
23
+
24
+ /**
25
+ * 获取 NFT 列表
26
+ * @param {Object} params - 查询参数
27
+ */
28
+ async getNFTs(params = {}) {
29
+ return this.get('/list', params);
30
+ }
31
+
32
+ /**
33
+ * 兼容旧 Hook 命名
34
+ */
35
+ async getNfts(params = {}) {
36
+ return this.getNFTs(params);
37
+ }
38
+
39
+ /**
40
+ * 获取 NFT 详情
41
+ * @param {string} nftId - NFT ID
42
+ */
43
+ async getNFT(nftId) {
44
+ return this.get(`/${nftId}`);
45
+ }
46
+
47
+ /**
48
+ * 兼容旧 Hook 命名
49
+ */
50
+ async getNftDetail(nftId) {
51
+ return this.getNFT(nftId);
52
+ }
53
+
54
+ /**
55
+ * 获取 NFT 元数据
56
+ * @param {string} nftId - NFT ID
57
+ */
58
+ async getMetadata(nftId) {
59
+ return this.get(`/${nftId}/metadata`);
60
+ }
61
+
62
+ /**
63
+ * 获取 NFT 历史
64
+ * @param {string} nftId - NFT ID
65
+ */
66
+ async getHistory(nftId) {
67
+ return this.get(`/${nftId}/history`);
68
+ }
69
+
70
+ // ============ 我的 NFT ============
71
+
72
+ /**
73
+ * 获取我的 NFT
74
+ * @param {Object} params - 查询参数
75
+ */
76
+ async getMyNFTs(params = {}) {
77
+ return this.get('/my', params);
78
+ }
79
+
80
+ /**
81
+ * 兼容旧 Hook 命名
82
+ */
83
+ async getMyNfts(params = {}) {
84
+ return this.getMyNFTs(params);
85
+ }
86
+
87
+ /**
88
+ * 获取收藏的 NFT
89
+ * @param {Object} params - 查询参数
90
+ */
91
+ async getFavorites(params = {}) {
92
+ return this.get('/my/favorites', params);
93
+ }
94
+
95
+ /**
96
+ * 收藏 NFT
97
+ * @param {string} nftId - NFT ID
98
+ */
99
+ async favorite(nftId) {
100
+ return this.post(`/${nftId}/favorite`);
101
+ }
102
+ async addFavorite(nftId) {
103
+ return this.favorite(nftId);
104
+ }
105
+
106
+ /**
107
+ * 取消收藏
108
+ * @param {string} nftId - NFT ID
109
+ */
110
+ async unfavorite(nftId) {
111
+ return this.delete(`/${nftId}/favorite`);
112
+ }
113
+ async removeFavorite(nftId) {
114
+ return this.unfavorite(nftId);
115
+ }
116
+
117
+ // ============ NFT 铸造 ============
118
+
119
+ /**
120
+ * 铸造 NFT
121
+ * @param {Object} data - 铸造数据
122
+ */
123
+ async mint(data) {
124
+ return this.post('/mint', data);
125
+ }
126
+ async mintNft(data) {
127
+ return this.mint(data);
128
+ }
129
+
130
+ /**
131
+ * 批量铸造
132
+ * @param {Object[]} items - NFT 数据列表
133
+ */
134
+ async batchMint(items) {
135
+ return this.post('/mint/batch', {
136
+ items
137
+ });
138
+ }
139
+
140
+ /**
141
+ * 获取铸造费用估算
142
+ * @param {Object} data - 铸造数据
143
+ */
144
+ async estimateMintFee(data) {
145
+ return this.post('/mint/estimate', data);
146
+ }
147
+
148
+ // ============ NFT 转移 ============
149
+
150
+ /**
151
+ * 转移 NFT
152
+ * @param {string} nftId - NFT ID
153
+ * @param {string} toAddress - 目标地址
154
+ */
155
+ async transfer(nftId, toAddress) {
156
+ return this.post(`/${nftId}/transfer`, {
157
+ to_address: toAddress
158
+ });
159
+ }
160
+
161
+ /**
162
+ * 批量转移
163
+ * @param {Object[]} transfers - 转移列表
164
+ */
165
+ async batchTransfer(transfers) {
166
+ return this.post('/transfer/batch', {
167
+ transfers
168
+ });
169
+ }
170
+
171
+ // ============ 合集 ============
172
+
173
+ /**
174
+ * 获取合集列表
175
+ * @param {Object} params - 查询参数
176
+ */
177
+ async getCollections(params = {}) {
178
+ return this.get('/collections', params);
179
+ }
180
+
181
+ /**
182
+ * 获取合集详情
183
+ * @param {string} collectionId - 合集 ID
184
+ */
185
+ async getCollection(collectionId) {
186
+ return this.get(`/collections/${collectionId}`);
187
+ }
188
+
189
+ /**
190
+ * 获取合集中的 NFT
191
+ * @param {string} collectionId - 合集 ID
192
+ * @param {Object} params - 查询参数
193
+ */
194
+ async getCollectionNFTs(collectionId, params = {}) {
195
+ return this.get(`/collections/${collectionId}/nfts`, params);
196
+ }
197
+
198
+ /**
199
+ * 创建合集
200
+ * @param {Object} data - 合集数据
201
+ */
202
+ async createCollection(data) {
203
+ return this.post('/collections', data);
204
+ }
205
+
206
+ // ============ 市场 ============
207
+
208
+ /**
209
+ * 上架 NFT
210
+ * @param {string} nftId - NFT ID
211
+ * @param {Object} listing - 上架信息
212
+ */
213
+ async list(nftId, listing) {
214
+ return this.post(`/${nftId}/list`, listing);
215
+ }
216
+
217
+ /**
218
+ * 下架 NFT
219
+ * @param {string} nftId - NFT ID
220
+ */
221
+ async unlist(nftId) {
222
+ return this.post(`/${nftId}/unlist`);
223
+ }
224
+
225
+ /**
226
+ * 购买 NFT
227
+ * @param {string} nftId - NFT ID
228
+ */
229
+ async buy(nftId) {
230
+ return this.post(`/${nftId}/buy`);
231
+ }
232
+ async buyNft(nftId, price, options = {}) {
233
+ return this.post(`/${nftId}/buy`, {
234
+ price,
235
+ ...options
236
+ });
237
+ }
238
+
239
+ /**
240
+ * 出价
241
+ * @param {string} nftId - NFT ID
242
+ * @param {Object} offer - 出价信息
243
+ */
244
+ async makeOffer(nftId, offer) {
245
+ return this.post(`/${nftId}/offer`, offer);
246
+ }
247
+
248
+ /**
249
+ * 接受出价
250
+ * @param {string} nftId - NFT ID
251
+ * @param {string} offerId - 出价 ID
252
+ */
253
+ async acceptOffer(nftId, offerId) {
254
+ return this.post(`/${nftId}/offer/${offerId}/accept`);
255
+ }
256
+
257
+ // ============ 链上证明 ============
258
+
259
+ /**
260
+ * 提交 NFT 链上交易哈希
261
+ */
262
+ async submitChainTx(nftId, txHash, data = {}) {
263
+ return this.post(`/${nftId}/chain/submit`, {
264
+ tx_hash: txHash,
265
+ chain: 'qbit',
266
+ network: 'mainnet',
267
+ ...data
268
+ });
269
+ }
270
+
271
+ /**
272
+ * 查询 NFT 链上确认状态
273
+ */
274
+ async getChainStatus(nftId) {
275
+ return this.get(`/${nftId}/chain/status`);
276
+ }
277
+ async getAuction(nftId) {
278
+ return this.get(`/${nftId}/auction`);
279
+ }
280
+ async placeBid(nftId, amount, options = {}) {
281
+ return this.post(`/${nftId}/bid`, {
282
+ amount,
283
+ ...options
284
+ });
285
+ }
286
+ }
287
+
288
+ // 创建默认实例
289
+ const nftApi = new NftApiClient();
290
+
291
+ const NFT_ANCHOR_NAMESPACE = 'qbit_nft';
292
+ function pickNftId(nft) {
293
+ return nft?.id || nft?.nft_id || nft?.nftId || nft?.token_id || nft?.tokenId || nft?.mint;
294
+ }
295
+ function pickContentHash(nft, options) {
296
+ return options.contentHash || nft?.content_hash || nft?.metadata_hash || nft?.image_hash || nft?.hash || null;
297
+ }
298
+ async function buildNftAnchorMemo(nft, options = {}) {
299
+ const {
300
+ buildQBitAnchorMemo
301
+ } = await import('@quantabit/qbit-chain-sdk');
302
+ const nftId = options.nftId || pickNftId(nft);
303
+ const contentHash = pickContentHash(nft, options);
304
+ return buildQBitAnchorMemo({
305
+ action: options.action || 'nft_anchor',
306
+ subject: nftId ? `nft:${nftId}` : 'nft',
307
+ resource_id: nftId,
308
+ content_hash: contentHash,
309
+ version: options.version,
310
+ timestamp: options.timestamp,
311
+ extra: {
312
+ collection_id: nft?.collection_id || nft?.collectionId || nft?.collection?.id,
313
+ owner: nft?.owner || nft?.owner_address,
314
+ creator_did: nft?.creator_did,
315
+ ...options.extra
316
+ }
317
+ }, {
318
+ namespace: options.namespace || NFT_ANCHOR_NAMESPACE,
319
+ maxBytes: options.maxBytes
320
+ });
321
+ }
322
+ function buildNftChainSubmit(nftId, txHash, options = {}) {
323
+ return {
324
+ nft_id: nftId,
325
+ tx_hash: txHash,
326
+ chain: options.chain || 'qbit',
327
+ network: options.network || 'mainnet',
328
+ action: options.action || 'nft_anchor',
329
+ memo: options.memo,
330
+ content_hash: options.contentHash,
331
+ ...options.extra
332
+ };
333
+ }
334
+
335
+ /**
336
+ * NFT SDK - 类型定义
337
+ */
338
+
339
+ const NftRarity = {};
340
+ const NftStatus = {};
341
+
342
+ /**
343
+ * NFT SDK - 国际化
344
+ * NFT系统多语言支持
345
+ */
346
+
347
+ const SUPPORTED_LANGUAGES = ['en', 'zh', 'ja', 'ko'];
348
+ const messages = {
349
+ zh: {
350
+ // NFT
351
+ nft: 'NFT',
352
+ nfts: 'NFT',
353
+ myNfts: '我的NFT',
354
+ allNfts: '全部NFT',
355
+ // 详情
356
+ name: '名称',
357
+ description: '描述',
358
+ collection: '系列',
359
+ creator: '创作者',
360
+ owner: '拥有者',
361
+ tokenId: 'Token ID',
362
+ contractAddress: '合约地址',
363
+ contract: '合约地址',
364
+ blockchain: '区块链',
365
+ chain: '区块链',
366
+ history: '交易历史',
367
+ // 属性
368
+ attributes: '属性',
369
+ properties: '属性',
370
+ rarity: '稀有度',
371
+ common: '普通',
372
+ rare: '稀有',
373
+ epic: '史诗',
374
+ legendary: '传说',
375
+ // 交易
376
+ price: '价格',
377
+ lastSale: '最近成交',
378
+ buy: '购买',
379
+ sell: '出售',
380
+ transfer: '转移',
381
+ list: '上架',
382
+ delist: '下架',
383
+ unlist: '下架',
384
+ // 竞拍
385
+ auction: '拍卖',
386
+ bid: '出价',
387
+ currentBid: '当前出价',
388
+ minBid: '最低出价',
389
+ placeBid: '出价',
390
+ endTime: '结束时间',
391
+ // 铸造
392
+ mint: '铸造',
393
+ minting: '铸造中...',
394
+ mintSuccess: '铸造成功',
395
+ mintFailed: '铸造失败',
396
+ supply: '发行量',
397
+ // 筛选
398
+ filter: '筛选',
399
+ sort: '排序',
400
+ priceHighToLow: '价格从高到低',
401
+ priceLowToHigh: '价格从低到高',
402
+ newest: '最新',
403
+ oldest: '最早',
404
+ // 状态
405
+ loading: '加载中...',
406
+ noNfts: '暂无NFT',
407
+ error: '加载失败',
408
+ // 其他
409
+ viewOnExplorer: '在浏览器中查看',
410
+ share: '分享',
411
+ favorite: '收藏',
412
+ favorites: '收藏'
413
+ },
414
+ en: {
415
+ nft: 'NFT',
416
+ nfts: 'NFTs',
417
+ myNfts: 'My NFTs',
418
+ allNfts: 'All NFTs',
419
+ name: 'Name',
420
+ description: 'Description',
421
+ collection: 'Collection',
422
+ creator: 'Creator',
423
+ owner: 'Owner',
424
+ tokenId: 'Token ID',
425
+ contractAddress: 'Contract',
426
+ contract: 'Contract',
427
+ blockchain: 'Blockchain',
428
+ chain: 'Blockchain',
429
+ history: 'History',
430
+ attributes: 'Attributes',
431
+ properties: 'Properties',
432
+ rarity: 'Rarity',
433
+ common: 'Common',
434
+ rare: 'Rare',
435
+ epic: 'Epic',
436
+ legendary: 'Legendary',
437
+ price: 'Price',
438
+ lastSale: 'Last Sale',
439
+ buy: 'Buy',
440
+ sell: 'Sell',
441
+ transfer: 'Transfer',
442
+ list: 'List',
443
+ delist: 'Delist',
444
+ unlist: 'Delist',
445
+ auction: 'Auction',
446
+ bid: 'Bid',
447
+ currentBid: 'Current Bid',
448
+ minBid: 'Min Bid',
449
+ placeBid: 'Place Bid',
450
+ endTime: 'End Time',
451
+ mint: 'Mint',
452
+ minting: 'Minting...',
453
+ mintSuccess: 'Minted!',
454
+ mintFailed: 'Mint Failed',
455
+ supply: 'Supply',
456
+ filter: 'Filter',
457
+ sort: 'Sort',
458
+ priceHighToLow: 'Price: High to Low',
459
+ priceLowToHigh: 'Price: Low to High',
460
+ newest: 'Newest',
461
+ oldest: 'Oldest',
462
+ loading: 'Loading...',
463
+ noNfts: 'No NFTs',
464
+ error: 'Error',
465
+ viewOnExplorer: 'View on Explorer',
466
+ share: 'Share',
467
+ favorite: 'Favorite',
468
+ favorites: 'Favorites'
469
+ },
470
+ ja: {
471
+ nft: 'NFT',
472
+ nfts: 'NFT',
473
+ myNfts: 'マイNFT',
474
+ allNfts: 'すべてのNFT',
475
+ name: '名前',
476
+ description: '説明',
477
+ collection: 'コレクション',
478
+ creator: 'クリエイター',
479
+ owner: 'オーナー',
480
+ tokenId: 'トークンID',
481
+ contractAddress: 'コントラクト',
482
+ contract: 'コントラクト',
483
+ blockchain: 'ブロックチェーン',
484
+ chain: 'ブロックチェーン',
485
+ history: '取引履歴',
486
+ attributes: '属性',
487
+ properties: 'プロパティ',
488
+ rarity: 'レアリティ',
489
+ common: 'コモン',
490
+ rare: 'レア',
491
+ epic: 'エピック',
492
+ legendary: 'レジェンダリー',
493
+ price: '価格',
494
+ lastSale: '最終取引',
495
+ buy: '購入',
496
+ sell: '売却',
497
+ transfer: '転送',
498
+ list: '出品',
499
+ delist: '取り下げ',
500
+ unlist: '取り下げ',
501
+ auction: 'オークション',
502
+ bid: '入札',
503
+ currentBid: '現在の入札',
504
+ minBid: '最低入札',
505
+ placeBid: '入札する',
506
+ endTime: '終了時間',
507
+ mint: 'ミント',
508
+ minting: 'ミント中...',
509
+ mintSuccess: 'ミント完了',
510
+ mintFailed: 'ミント失敗',
511
+ supply: '発行数',
512
+ filter: 'フィルター',
513
+ sort: '並び替え',
514
+ priceHighToLow: '価格: 高い順',
515
+ priceLowToHigh: '価格: 安い順',
516
+ newest: '新着順',
517
+ oldest: '古い順',
518
+ loading: '読み込み中...',
519
+ noNfts: 'NFTなし',
520
+ error: 'エラー',
521
+ viewOnExplorer: 'エクスプローラーで見る',
522
+ share: '共有',
523
+ favorite: 'お気に入り',
524
+ favorites: 'お気に入り'
525
+ },
526
+ ko: {
527
+ nft: 'NFT',
528
+ nfts: 'NFT',
529
+ myNfts: '내 NFT',
530
+ allNfts: '모든 NFT',
531
+ name: '이름',
532
+ description: '설명',
533
+ collection: '컬렉션',
534
+ creator: '제작자',
535
+ owner: '소유자',
536
+ tokenId: '토큰 ID',
537
+ contractAddress: '컨트랙트',
538
+ contract: '컨트랙트',
539
+ blockchain: '블록체인',
540
+ chain: '블록체인',
541
+ history: '거래 내역',
542
+ attributes: '속성',
543
+ properties: '속성',
544
+ rarity: '희귀도',
545
+ common: '일반',
546
+ rare: '레어',
547
+ epic: '에픽',
548
+ legendary: '전설',
549
+ price: '가격',
550
+ lastSale: '최근 거래',
551
+ buy: '구매',
552
+ sell: '판매',
553
+ transfer: '전송',
554
+ list: '등록',
555
+ delist: '등록 취소',
556
+ unlist: '등록 취소',
557
+ auction: '경매',
558
+ bid: '입찰',
559
+ currentBid: '현재 입찰',
560
+ minBid: '최소 입찰',
561
+ placeBid: '입찰하기',
562
+ endTime: '종료 시간',
563
+ mint: '민팅',
564
+ minting: '민팅 중...',
565
+ mintSuccess: '민팅 완료',
566
+ mintFailed: '민팅 실패',
567
+ supply: '발행량',
568
+ filter: '필터',
569
+ sort: '정렬',
570
+ priceHighToLow: '가격: 높은 순',
571
+ priceLowToHigh: '가격: 낮은 순',
572
+ newest: '최신순',
573
+ oldest: '오래된 순',
574
+ loading: '로딩 중...',
575
+ noNfts: 'NFT 없음',
576
+ error: '오류',
577
+ viewOnExplorer: '탐색기에서 보기',
578
+ share: '공유',
579
+ favorite: '즐겨찾기',
580
+ favorites: '즐겨찾기'
581
+ }
582
+ };
583
+ let currentLanguage = 'zh';
584
+ function setLanguage(lang) {
585
+ if (SUPPORTED_LANGUAGES.includes(lang)) currentLanguage = lang;
586
+ }
587
+ function getLanguage() {
588
+ return currentLanguage;
589
+ }
590
+ function t(key) {
591
+ return (messages[currentLanguage] || messages.en)[key] || key;
592
+ }
593
+
594
+ /**
595
+ * NFT SDK - React Hooks
596
+ * NFT系统相关的状态管理
597
+ */
598
+
599
+
600
+ /**
601
+ * 获取NFT列表
602
+ */
603
+ function useNfts(options = {}) {
604
+ const [nfts, setNfts] = React.useState([]);
605
+ const [loading, setLoading] = React.useState(true);
606
+ const [error, setError] = React.useState(null);
607
+ const [pagination, setPagination] = React.useState({
608
+ page: 1,
609
+ total: 0,
610
+ hasMore: true
611
+ });
612
+ const {
613
+ collection,
614
+ owner,
615
+ sort = 'newest',
616
+ limit = 20
617
+ } = options;
618
+ const fetchNfts = React.useCallback(async (page = 1) => {
619
+ try {
620
+ setLoading(true);
621
+ const response = await nftApi.getNfts({
622
+ collection,
623
+ owner,
624
+ sort,
625
+ page,
626
+ limit
627
+ });
628
+ if (page === 1) {
629
+ setNfts(response.data || []);
630
+ } else {
631
+ setNfts(prev => [...prev, ...(response.data || [])]);
632
+ }
633
+ setPagination({
634
+ page,
635
+ total: response.total || 0,
636
+ hasMore: response.hasMore || false
637
+ });
638
+ setError(null);
639
+ } catch (err) {
640
+ setError(err.message);
641
+ } finally {
642
+ setLoading(false);
643
+ }
644
+ }, [collection, owner, sort, limit]);
645
+ React.useEffect(() => {
646
+ fetchNfts(1);
647
+ }, [fetchNfts]);
648
+ const loadMore = React.useCallback(() => {
649
+ if (!loading && pagination.hasMore) {
650
+ fetchNfts(pagination.page + 1);
651
+ }
652
+ }, [loading, pagination, fetchNfts]);
653
+ return {
654
+ nfts,
655
+ loading,
656
+ error,
657
+ pagination,
658
+ loadMore,
659
+ refresh: () => fetchNfts(1)
660
+ };
661
+ }
662
+
663
+ /**
664
+ * 获取NFT详情
665
+ */
666
+ function useNftDetail(tokenId) {
667
+ const [nft, setNft] = React.useState(null);
668
+ const [loading, setLoading] = React.useState(true);
669
+ const [error, setError] = React.useState(null);
670
+ const fetchDetail = React.useCallback(async () => {
671
+ if (!tokenId) return;
672
+ try {
673
+ setLoading(true);
674
+ const response = await nftApi.getNftDetail(tokenId);
675
+ setNft(response);
676
+ setError(null);
677
+ } catch (err) {
678
+ setError(err.message);
679
+ } finally {
680
+ setLoading(false);
681
+ }
682
+ }, [tokenId]);
683
+ React.useEffect(() => {
684
+ fetchDetail();
685
+ }, [fetchDetail]);
686
+ return {
687
+ nft,
688
+ loading,
689
+ error,
690
+ refresh: fetchDetail
691
+ };
692
+ }
693
+
694
+ /**
695
+ * 我的NFT
696
+ */
697
+ function useMyNfts() {
698
+ const [nfts, setNfts] = React.useState([]);
699
+ const [loading, setLoading] = React.useState(true);
700
+ const [error, setError] = React.useState(null);
701
+ const fetchNfts = React.useCallback(async () => {
702
+ try {
703
+ setLoading(true);
704
+ const response = await nftApi.getMyNfts();
705
+ setNfts(response.data || []);
706
+ setError(null);
707
+ } catch (err) {
708
+ setError(err.message);
709
+ } finally {
710
+ setLoading(false);
711
+ }
712
+ }, []);
713
+ React.useEffect(() => {
714
+ fetchNfts();
715
+ }, [fetchNfts]);
716
+
717
+ // 按系列分组
718
+ const grouped = React.useMemo(() => {
719
+ const groups = {};
720
+ nfts.forEach(nft => {
721
+ const collection = nft.collection?.name || 'Others';
722
+ if (!groups[collection]) groups[collection] = [];
723
+ groups[collection].push(nft);
724
+ });
725
+ return groups;
726
+ }, [nfts]);
727
+ return {
728
+ nfts,
729
+ grouped,
730
+ totalCount: nfts.length,
731
+ loading,
732
+ error,
733
+ refresh: fetchNfts
734
+ };
735
+ }
736
+
737
+ /**
738
+ * NFT收藏
739
+ */
740
+ function useFavoriteNfts() {
741
+ const [favorites, setFavorites] = React.useState([]);
742
+ const [loading, setLoading] = React.useState(true);
743
+ const fetchFavorites = React.useCallback(async () => {
744
+ try {
745
+ setLoading(true);
746
+ const response = await nftApi.getFavorites();
747
+ setFavorites(response.data || []);
748
+ } catch (err) {
749
+ console.error('Get favorites error:', err);
750
+ } finally {
751
+ setLoading(false);
752
+ }
753
+ }, []);
754
+ React.useEffect(() => {
755
+ fetchFavorites();
756
+ }, [fetchFavorites]);
757
+ const toggleFavorite = React.useCallback(async tokenId => {
758
+ try {
759
+ const isFav = favorites.some(f => f.tokenId === tokenId);
760
+ if (isFav) {
761
+ await nftApi.removeFavorite(tokenId);
762
+ setFavorites(prev => prev.filter(f => f.tokenId !== tokenId));
763
+ } else {
764
+ await nftApi.addFavorite(tokenId);
765
+ fetchFavorites(); // 重新加载
766
+ }
767
+ } catch (err) {
768
+ console.error('Toggle favorite error:', err);
769
+ }
770
+ }, [favorites, fetchFavorites]);
771
+ const isFavorite = React.useCallback(tokenId => {
772
+ return favorites.some(f => f.tokenId === tokenId);
773
+ }, [favorites]);
774
+ return {
775
+ favorites,
776
+ toggleFavorite,
777
+ isFavorite,
778
+ loading,
779
+ refresh: fetchFavorites
780
+ };
781
+ }
782
+
783
+ /**
784
+ * 购买NFT
785
+ */
786
+ function useBuyNft() {
787
+ const [loading, setLoading] = React.useState(false);
788
+ const [error, setError] = React.useState(null);
789
+ const [result, setResult] = React.useState(null);
790
+ const buy = React.useCallback(async (tokenId, price) => {
791
+ try {
792
+ setLoading(true);
793
+ setError(null);
794
+ const response = await nftApi.buyNft(tokenId, price);
795
+ setResult(response);
796
+ return response;
797
+ } catch (err) {
798
+ setError(err.message);
799
+ throw err;
800
+ } finally {
801
+ setLoading(false);
802
+ }
803
+ }, []);
804
+ const reset = React.useCallback(() => {
805
+ setResult(null);
806
+ setError(null);
807
+ }, []);
808
+ return {
809
+ buy,
810
+ loading,
811
+ error,
812
+ result,
813
+ reset
814
+ };
815
+ }
816
+
817
+ /**
818
+ * 铸造NFT
819
+ */
820
+ function useMintNft() {
821
+ const [loading, setLoading] = React.useState(false);
822
+ const [error, setError] = React.useState(null);
823
+ const [result, setResult] = React.useState(null);
824
+ const [progress, setProgress] = React.useState(0);
825
+ const mint = React.useCallback(async metadata => {
826
+ try {
827
+ setLoading(true);
828
+ setError(null);
829
+ setProgress(0);
830
+
831
+ // 上传资源
832
+ setProgress(30);
833
+ const response = await nftApi.mintNft(metadata);
834
+ setProgress(100);
835
+ setResult(response);
836
+ return response;
837
+ } catch (err) {
838
+ setError(err.message);
839
+ throw err;
840
+ } finally {
841
+ setLoading(false);
842
+ }
843
+ }, []);
844
+ const reset = React.useCallback(() => {
845
+ setResult(null);
846
+ setError(null);
847
+ setProgress(0);
848
+ }, []);
849
+ return {
850
+ mint,
851
+ loading,
852
+ error,
853
+ result,
854
+ progress,
855
+ reset
856
+ };
857
+ }
858
+
859
+ /**
860
+ * NFT拍卖
861
+ */
862
+ function useNftAuction(tokenId) {
863
+ const [auction, setAuction] = React.useState(null);
864
+ const [loading, setLoading] = React.useState(true);
865
+ const [bidding, setBidding] = React.useState(false);
866
+ const fetchAuction = React.useCallback(async () => {
867
+ if (!tokenId) return;
868
+ try {
869
+ setLoading(true);
870
+ const response = await nftApi.getAuction(tokenId);
871
+ setAuction(response);
872
+ } catch (err) {
873
+ console.error('Get auction error:', err);
874
+ } finally {
875
+ setLoading(false);
876
+ }
877
+ }, [tokenId]);
878
+ React.useEffect(() => {
879
+ fetchAuction();
880
+ }, [fetchAuction]);
881
+ const placeBid = React.useCallback(async amount => {
882
+ try {
883
+ setBidding(true);
884
+ await nftApi.placeBid(tokenId, amount);
885
+ await fetchAuction();
886
+ } catch (err) {
887
+ console.error('Place bid error:', err);
888
+ throw err;
889
+ } finally {
890
+ setBidding(false);
891
+ }
892
+ }, [tokenId, fetchAuction]);
893
+ return {
894
+ auction,
895
+ placeBid,
896
+ loading,
897
+ bidding,
898
+ refresh: fetchAuction
899
+ };
900
+ }
901
+
902
+ /**
903
+ * NFT SDK - React 组件
904
+ * NFT系统可视化组件
905
+ */
906
+
907
+
908
+ /**
909
+ * NFT卡片
910
+ */
911
+ function NftCard({
912
+ nft,
913
+ onClick,
914
+ showPrice = true
915
+ }) {
916
+ const {
917
+ isFavorite,
918
+ toggleFavorite
919
+ } = useFavoriteNfts();
920
+ const isFav = isFavorite(nft.tokenId);
921
+ return /*#__PURE__*/React.createElement("div", {
922
+ className: "eco-nft-card",
923
+ onClick: () => onClick?.(nft)
924
+ }, /*#__PURE__*/React.createElement("div", {
925
+ className: "eco-nft-image"
926
+ }, /*#__PURE__*/React.createElement("img", {
927
+ src: nft.image,
928
+ alt: nft.name
929
+ }), nft.rarity && /*#__PURE__*/React.createElement("span", {
930
+ className: `eco-nft-rarity ${nft.rarity}`
931
+ }, t(nft.rarity)), /*#__PURE__*/React.createElement("button", {
932
+ className: `eco-nft-fav ${isFav ? 'active' : ''}`,
933
+ onClick: e => {
934
+ e.stopPropagation();
935
+ toggleFavorite(nft.tokenId);
936
+ }
937
+ }, isFav ? '❤️' : '🤍')), /*#__PURE__*/React.createElement("div", {
938
+ className: "eco-nft-info"
939
+ }, /*#__PURE__*/React.createElement("span", {
940
+ className: "eco-nft-collection"
941
+ }, nft.collection?.name), /*#__PURE__*/React.createElement("h4", {
942
+ className: "eco-nft-name"
943
+ }, nft.name), showPrice && nft.price && /*#__PURE__*/React.createElement("div", {
944
+ className: "eco-nft-price"
945
+ }, /*#__PURE__*/React.createElement("span", {
946
+ className: "eco-nft-price-value"
947
+ }, nft.price, " ETH"))));
948
+ }
949
+
950
+ /**
951
+ * NFT网格列表
952
+ */
953
+ function NftGrid({
954
+ nfts,
955
+ onNftClick,
956
+ loading
957
+ }) {
958
+ if (loading && nfts.length === 0) {
959
+ return /*#__PURE__*/React.createElement("div", {
960
+ className: "eco-nft-grid eco-nft-loading"
961
+ }, /*#__PURE__*/React.createElement("div", {
962
+ className: "eco-nft-spinner"
963
+ }));
964
+ }
965
+ if (nfts.length === 0) {
966
+ return /*#__PURE__*/React.createElement("div", {
967
+ className: "eco-nft-grid eco-nft-empty"
968
+ }, /*#__PURE__*/React.createElement("span", {
969
+ className: "eco-nft-empty-icon"
970
+ }, "\uD83D\uDDBC\uFE0F"), /*#__PURE__*/React.createElement("span", null, t('noNfts')));
971
+ }
972
+ return /*#__PURE__*/React.createElement("div", {
973
+ className: "eco-nft-grid"
974
+ }, nfts.map(nft => /*#__PURE__*/React.createElement(NftCard, {
975
+ key: nft.tokenId,
976
+ nft: nft,
977
+ onClick: onNftClick
978
+ })));
979
+ }
980
+
981
+ /**
982
+ * NFT详情
983
+ */
984
+ function NftDetail({
985
+ tokenId,
986
+ onClose
987
+ }) {
988
+ const {
989
+ nft,
990
+ loading
991
+ } = useNftDetail(tokenId);
992
+ const {
993
+ buy,
994
+ loading: buying
995
+ } = useBuyNft();
996
+ const handleBuy = React.useCallback(async () => {
997
+ try {
998
+ await buy(tokenId, nft.price);
999
+ onClose?.();
1000
+ } catch (err) {
1001
+ console.error('Buy error:', err);
1002
+ }
1003
+ }, [buy, tokenId, nft, onClose]);
1004
+ if (loading) {
1005
+ return /*#__PURE__*/React.createElement("div", {
1006
+ className: "eco-nft-detail eco-nft-loading"
1007
+ }, /*#__PURE__*/React.createElement("div", {
1008
+ className: "eco-nft-spinner"
1009
+ }));
1010
+ }
1011
+ if (!nft) return null;
1012
+ return /*#__PURE__*/React.createElement("div", {
1013
+ className: "eco-nft-detail"
1014
+ }, /*#__PURE__*/React.createElement("div", {
1015
+ className: "eco-nft-detail-image"
1016
+ }, /*#__PURE__*/React.createElement("img", {
1017
+ src: nft.image,
1018
+ alt: nft.name
1019
+ })), /*#__PURE__*/React.createElement("div", {
1020
+ className: "eco-nft-detail-content"
1021
+ }, /*#__PURE__*/React.createElement("span", {
1022
+ className: "eco-nft-detail-collection"
1023
+ }, nft.collection?.name), /*#__PURE__*/React.createElement("h2", {
1024
+ className: "eco-nft-detail-name"
1025
+ }, nft.name), /*#__PURE__*/React.createElement("div", {
1026
+ className: "eco-nft-detail-owner"
1027
+ }, /*#__PURE__*/React.createElement("span", null, t('owner')), /*#__PURE__*/React.createElement("span", null, nft.owner?.slice(0, 8), "...")), /*#__PURE__*/React.createElement("p", {
1028
+ className: "eco-nft-detail-desc"
1029
+ }, nft.description), nft.attributes?.length > 0 && /*#__PURE__*/React.createElement("div", {
1030
+ className: "eco-nft-attributes"
1031
+ }, /*#__PURE__*/React.createElement("h4", null, t('attributes')), /*#__PURE__*/React.createElement("div", {
1032
+ className: "eco-nft-attributes-grid"
1033
+ }, nft.attributes.map((attr, qbit) => /*#__PURE__*/React.createElement("div", {
1034
+ key: qbit,
1035
+ className: "eco-nft-attribute"
1036
+ }, /*#__PURE__*/React.createElement("span", {
1037
+ className: "eco-nft-attribute-type"
1038
+ }, attr.trait_type), /*#__PURE__*/React.createElement("span", {
1039
+ className: "eco-nft-attribute-value"
1040
+ }, attr.value))))), nft.isForSale && /*#__PURE__*/React.createElement("div", {
1041
+ className: "eco-nft-detail-buy"
1042
+ }, /*#__PURE__*/React.createElement("div", {
1043
+ className: "eco-nft-detail-price"
1044
+ }, /*#__PURE__*/React.createElement("span", null, t('price')), /*#__PURE__*/React.createElement("span", {
1045
+ className: "eco-nft-detail-price-value"
1046
+ }, nft.price, " ETH")), /*#__PURE__*/React.createElement("button", {
1047
+ className: "eco-nft-buy-btn",
1048
+ onClick: handleBuy,
1049
+ disabled: buying
1050
+ }, buying ? '...' : t('buy')))));
1051
+ }
1052
+
1053
+ /**
1054
+ * 我的NFT收藏页
1055
+ */
1056
+ function MyNftsPage() {
1057
+ const {
1058
+ nfts,
1059
+ grouped,
1060
+ totalCount,
1061
+ loading
1062
+ } = useMyNfts();
1063
+ const [selectedNft, setSelectedNft] = React.useState(null);
1064
+ return /*#__PURE__*/React.createElement("div", {
1065
+ className: "eco-nft-my-page"
1066
+ }, /*#__PURE__*/React.createElement("div", {
1067
+ className: "eco-nft-my-header"
1068
+ }, /*#__PURE__*/React.createElement("h2", null, t('myNfts')), /*#__PURE__*/React.createElement("span", {
1069
+ className: "eco-nft-count"
1070
+ }, totalCount, " NFTs")), Object.entries(grouped).map(([collection, items]) => /*#__PURE__*/React.createElement("div", {
1071
+ key: collection,
1072
+ className: "eco-nft-collection-section"
1073
+ }, /*#__PURE__*/React.createElement("h3", null, collection), /*#__PURE__*/React.createElement(NftGrid, {
1074
+ nfts: items,
1075
+ loading: loading,
1076
+ onNftClick: setSelectedNft
1077
+ }))), selectedNft && /*#__PURE__*/React.createElement("div", {
1078
+ className: "eco-nft-modal"
1079
+ }, /*#__PURE__*/React.createElement("div", {
1080
+ className: "eco-nft-modal-overlay",
1081
+ onClick: () => setSelectedNft(null)
1082
+ }), /*#__PURE__*/React.createElement("div", {
1083
+ className: "eco-nft-modal-content"
1084
+ }, /*#__PURE__*/React.createElement("button", {
1085
+ className: "eco-nft-modal-close",
1086
+ onClick: () => setSelectedNft(null)
1087
+ }, "\xD7"), /*#__PURE__*/React.createElement(NftDetail, {
1088
+ tokenId: selectedNft.tokenId,
1089
+ onClose: () => setSelectedNft(null)
1090
+ }))));
1091
+ }
1092
+
1093
+ /**
1094
+ * 拍卖卡片
1095
+ */
1096
+ function AuctionCard({
1097
+ tokenId
1098
+ }) {
1099
+ const {
1100
+ auction,
1101
+ placeBid,
1102
+ bidding,
1103
+ loading
1104
+ } = useNftAuction(tokenId);
1105
+ const [bidAmount, setBidAmount] = React.useState('');
1106
+ const handleBid = React.useCallback(async () => {
1107
+ if (!bidAmount) return;
1108
+ try {
1109
+ await placeBid(parseFloat(bidAmount));
1110
+ setBidAmount('');
1111
+ } catch (err) {
1112
+ console.error('Bid error:', err);
1113
+ }
1114
+ }, [placeBid, bidAmount]);
1115
+ if (loading) {
1116
+ return /*#__PURE__*/React.createElement("div", {
1117
+ className: "eco-nft-auction eco-nft-loading"
1118
+ }, /*#__PURE__*/React.createElement("div", {
1119
+ className: "eco-nft-spinner"
1120
+ }));
1121
+ }
1122
+ if (!auction) return null;
1123
+ return /*#__PURE__*/React.createElement("div", {
1124
+ className: "eco-nft-auction"
1125
+ }, /*#__PURE__*/React.createElement("h4", null, t('auction')), /*#__PURE__*/React.createElement("div", {
1126
+ className: "eco-nft-auction-info"
1127
+ }, /*#__PURE__*/React.createElement("div", {
1128
+ className: "eco-nft-auction-item"
1129
+ }, /*#__PURE__*/React.createElement("span", null, t('currentBid')), /*#__PURE__*/React.createElement("span", {
1130
+ className: "eco-nft-auction-value"
1131
+ }, auction.currentBid, " ETH")), /*#__PURE__*/React.createElement("div", {
1132
+ className: "eco-nft-auction-item"
1133
+ }, /*#__PURE__*/React.createElement("span", null, t('endTime')), /*#__PURE__*/React.createElement("span", null, new Date(auction.endTime).toLocaleString()))), /*#__PURE__*/React.createElement("div", {
1134
+ className: "eco-nft-auction-bid"
1135
+ }, /*#__PURE__*/React.createElement("input", {
1136
+ type: "number",
1137
+ value: bidAmount,
1138
+ onChange: e => setBidAmount(e.target.value),
1139
+ placeholder: `${t('minBid')}: ${auction.minBid} ETH`,
1140
+ step: "0.01"
1141
+ }), /*#__PURE__*/React.createElement("button", {
1142
+ className: "eco-nft-bid-btn",
1143
+ onClick: handleBid,
1144
+ disabled: bidding
1145
+ }, bidding ? '...' : t('placeBid'))));
1146
+ }
1147
+
1148
+ /**
1149
+ * NFT市场首页
1150
+ */
1151
+ function NftMarketplace({
1152
+ onNftClick
1153
+ }) {
1154
+ const [sort, setSort] = React.useState('newest');
1155
+ const {
1156
+ nfts,
1157
+ loading,
1158
+ pagination,
1159
+ loadMore
1160
+ } = useNfts({
1161
+ sort
1162
+ });
1163
+ return /*#__PURE__*/React.createElement("div", {
1164
+ className: "eco-nft-marketplace"
1165
+ }, /*#__PURE__*/React.createElement("div", {
1166
+ className: "eco-nft-toolbar"
1167
+ }, /*#__PURE__*/React.createElement("h2", null, t('allNfts')), /*#__PURE__*/React.createElement("select", {
1168
+ className: "eco-nft-sort",
1169
+ value: sort,
1170
+ onChange: e => setSort(e.target.value)
1171
+ }, /*#__PURE__*/React.createElement("option", {
1172
+ value: "newest"
1173
+ }, t('newest')), /*#__PURE__*/React.createElement("option", {
1174
+ value: "oldest"
1175
+ }, t('oldest')), /*#__PURE__*/React.createElement("option", {
1176
+ value: "priceHighToLow"
1177
+ }, t('priceHighToLow')), /*#__PURE__*/React.createElement("option", {
1178
+ value: "priceLowToHigh"
1179
+ }, t('priceLowToHigh')))), /*#__PURE__*/React.createElement(NftGrid, {
1180
+ nfts: nfts,
1181
+ loading: loading,
1182
+ onNftClick: onNftClick
1183
+ }), pagination.hasMore && /*#__PURE__*/React.createElement("button", {
1184
+ className: "eco-nft-load-more",
1185
+ onClick: loadMore,
1186
+ disabled: loading
1187
+ }, loading ? '...' : t('loading')));
1188
+ }
1189
+
1190
+ /**
1191
+ * @quantabit/nft-sdk
1192
+ * NFT System SDK - Full Version
1193
+ */
1194
+
1195
+ const getNfts = (...args) => nftApi.getNfts(...args);
1196
+ const getNftDetail = (...args) => nftApi.getNftDetail(...args);
1197
+ const getMyNfts = (...args) => nftApi.getMyNfts(...args);
1198
+ const buyNft = (...args) => nftApi.buyNft(...args);
1199
+ const mintNft = (...args) => nftApi.mintNft(...args);
1200
+
1201
+ exports.AuctionCard = AuctionCard;
1202
+ exports.MyNftsPage = MyNftsPage;
1203
+ exports.NFT_ANCHOR_NAMESPACE = NFT_ANCHOR_NAMESPACE;
1204
+ exports.NftApiClient = NftApiClient;
1205
+ exports.NftCard = NftCard;
1206
+ exports.NftDetail = NftDetail;
1207
+ exports.NftGrid = NftGrid;
1208
+ exports.NftMarketplace = NftMarketplace;
1209
+ exports.NftRarity = NftRarity;
1210
+ exports.NftStatus = NftStatus;
1211
+ exports.SUPPORTED_LANGUAGES = SUPPORTED_LANGUAGES;
1212
+ exports.buildNftAnchorMemo = buildNftAnchorMemo;
1213
+ exports.buildNftChainSubmit = buildNftChainSubmit;
1214
+ exports.buyNft = buyNft;
1215
+ exports.getLanguage = getLanguage;
1216
+ exports.getMyNfts = getMyNfts;
1217
+ exports.getNftDetail = getNftDetail;
1218
+ exports.getNfts = getNfts;
1219
+ exports.messages = messages;
1220
+ exports.mintNft = mintNft;
1221
+ exports.nftApi = nftApi;
1222
+ exports.setLanguage = setLanguage;
1223
+ exports.t = t;
1224
+ exports.useBuyNft = useBuyNft;
1225
+ exports.useFavoriteNfts = useFavoriteNfts;
1226
+ exports.useMintNft = useMintNft;
1227
+ exports.useMyNfts = useMyNfts;
1228
+ exports.useNftAuction = useNftAuction;
1229
+ exports.useNftDetail = useNftDetail;
1230
+ exports.useNfts = useNfts;
1231
+ //# sourceMappingURL=index.cjs.map