diff --git a/package.json b/package.json index af3639711..deff5c620 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ }, "dependencies": { "@base-ui/react": "^1.0.0", + "@bfmeta/sign-util": "^1.3.10", "@fontsource-variable/dm-sans": "^5.2.8", "@fontsource-variable/figtree": "^5.2.10", "@fontsource/dm-mono": "^5.2.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e7aac53a5..02a09c85a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@base-ui/react': specifier: ^1.0.0 version: 1.0.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@bfmeta/sign-util': + specifier: ^1.3.10 + version: 1.3.10 '@fontsource-variable/dm-sans': specifier: ^5.2.8 version: 5.2.8 @@ -527,6 +530,9 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} + '@bfmeta/sign-util@1.3.10': + resolution: {integrity: sha512-44EWN5lHBeCh/kjPc1BBn/E1Me3pbcq0dG8AMRSZ5NgtesZlxLeolAFUKn+ozAtGMfdazOCjDXMfZMi7UIRLYg==} + '@cbor-extract/cbor-extract-darwin-arm64@2.2.0': resolution: {integrity: sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==} cpu: [arm64] @@ -5024,6 +5030,10 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} + '@bfmeta/sign-util@1.3.10': + dependencies: + tslib: 2.8.1 + '@cbor-extract/cbor-extract-darwin-arm64@2.2.0': optional: true diff --git a/src/hooks/use-send.bioforest.ts b/src/hooks/use-send.bioforest.ts index f6088344c..9f2500187 100644 --- a/src/hooks/use-send.bioforest.ts +++ b/src/hooks/use-send.bioforest.ts @@ -92,9 +92,22 @@ export async function submitBioforestTransfer({ return { status: 'ok', txHash } } catch (error) { + console.error('[submitBioforestTransfer] Transaction failed:', error) + + // Provide more detailed error message + const errorMessage = error instanceof Error ? error.message : String(error) + + // Check for common error patterns + if (errorMessage.includes('Transaction rejected') || errorMessage.includes('Broadcast failed')) { + return { + status: 'error', + message: `交易广播失败: ${errorMessage}。BioForest 链交易需要特定的交易格式,当前实现可能不完整。`, + } + } + return { status: 'error', - message: error instanceof Error ? error.message : '未知错误', + message: errorMessage || '未知错误', } } } diff --git a/src/services/chain-adapter/bioforest/transaction-service.ts b/src/services/chain-adapter/bioforest/transaction-service.ts index 1f502b44e..4e96a5c7a 100644 --- a/src/services/chain-adapter/bioforest/transaction-service.ts +++ b/src/services/chain-adapter/bioforest/transaction-service.ts @@ -263,73 +263,98 @@ export class BioforestTransactionService implements ITransactionService { return [] } - const url = `${this.baseUrl}/wallet/${this.config.id}/transactions/query` - try { - const response = await fetch(url, { + // First get the latest block height + const lastBlockUrl = `${this.baseUrl}/wallet/${this.config.id}/lastblock` + const blockResponse = await fetch(lastBlockUrl) + if (!blockResponse.ok) { + console.warn('[TransactionService] Failed to get lastblock:', blockResponse.status) + return [] + } + const lastBlock = (await blockResponse.json()) as { height: number; timestamp: number } + const maxHeight = lastBlock.height + + // Query transactions using the correct API format + const queryUrl = `${this.baseUrl}/wallet/${this.config.id}/transactions/query` + const response = await fetch(queryUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - address, - pageSize: limit, + maxHeight, + address, // Query all transactions for this address page: 1, + pageSize: limit, + sort: -1, // Newest first }), }) if (!response.ok) { - console.warn('[TransactionService] API error:', response.status, response.statusText, 'for', url) + console.warn('[TransactionService] API error:', response.status, response.statusText, 'for', queryUrl) return [] } + // BioForest API response format: { trs: TransactionDetail[] } const result = (await response.json()) as { - success?: boolean - data?: { - transactions?: Array<{ - signature: string + trs?: Array<{ + height: number + signature: string // Block signature + tIndex: number + transaction: { + signature: string // Transaction ID senderId: string - recipientId: string - amount: string + recipientId?: string fee: string timestamp: number - height?: number - }> - } - // Alternative response format - transactions at root level - transactions?: Array<{ - signature: string - senderId: string - recipientId: string - amount: string - fee: string - timestamp: number - height?: number + type: string + asset?: { + transferAsset?: { + amount: string + assetType: string + } + } + } }> } - // Handle different API response formats - const transactions = result.data?.transactions ?? result.transactions ?? [] + const transactions = result.trs ?? [] if (transactions.length === 0) { console.debug('[TransactionService] No transactions found for', address, 'on', this.config.id) + return [] } const { decimals, symbol } = this.config - return transactions.map((tx) => ({ - hash: tx.signature, - from: tx.senderId, - to: tx.recipientId, - amount: Amount.fromRaw(tx.amount, decimals, symbol), - fee: Amount.fromRaw(tx.fee, decimals, symbol), - status: { - status: tx.height ? 'confirmed' : 'pending', - confirmations: tx.height ? 1 : 0, - requiredConfirmations: 1, - } as const, - timestamp: tx.timestamp, - blockNumber: tx.height ? BigInt(tx.height) : undefined, - type: 'transfer' as const, - })) + return transactions + .filter((item) => { + // Filter for transfer transactions + const txType = item.transaction.type + return txType.includes('AST-01') || txType.includes('AST-02') // TRANSFER_ASSET types + }) + .map((item) => { + const tx = item.transaction + const transferAsset = tx.asset?.transferAsset + + // Amount comes from transferAsset if available + const amountRaw = transferAsset?.amount ?? '0' + const assetSymbol = transferAsset?.assetType ?? symbol + + return { + hash: tx.signature, + from: tx.senderId, + to: tx.recipientId ?? '', + amount: Amount.fromRaw(amountRaw, decimals, assetSymbol), + fee: Amount.fromRaw(tx.fee, decimals, symbol), + status: { + status: 'confirmed' as const, + confirmations: 1, + requiredConfirmations: 1, + }, + timestamp: tx.timestamp * 1000, // Convert to milliseconds + blockNumber: BigInt(item.height), + type: 'transfer' as const, + } + }) } catch (error) { console.error('[TransactionService] Failed to fetch history:', error) return []