Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 94 additions & 19 deletions src/hooks/use-send.bioforest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import type { ChainConfig } from '@/services/chain-config'
import { Amount } from '@/types/amount'
import { walletStorageService, WalletStorageError, WalletStorageErrorCode } from '@/services/wallet-storage'
import { createBioforestAdapter } from '@/services/chain-adapter'
import { deriveBioforestKeyFromChainConfig, hexToBytes } from '@/lib/crypto'
import {
createTransferTransaction,
broadcastTransaction,
getAddressInfo,
verifyPayPassword,
} from '@/services/bioforest-sdk'

export interface BioforestFeeResult {
amount: Amount
Expand Down Expand Up @@ -39,6 +44,7 @@ export async function fetchBioforestBalance(chainConfig: ChainConfig, fromAddres
export type SubmitBioforestResult =
| { status: 'ok'; txHash: string }
| { status: 'password' }
| { status: 'password_required'; secondPublicKey: string }
| { status: 'error'; message: string }

export interface SubmitBioforestParams {
Expand All @@ -48,6 +54,48 @@ export interface SubmitBioforestParams {
fromAddress: string
toAddress: string
amount: Amount
payPassword?: string
}

/**
* Check if address has pay password set
*/
export async function checkPayPasswordRequired(
chainConfig: ChainConfig,
address: string,
): Promise<{ required: boolean; secondPublicKey?: string }> {
if (!chainConfig.rpcUrl) {
return { required: false }
}

try {
const info = await getAddressInfo(chainConfig.rpcUrl, chainConfig.id, address)
if (info.secondPublicKey) {
return { required: true, secondPublicKey: info.secondPublicKey }
}
} catch {
// If we can't check, assume not required
}

return { required: false }
}

/**
* Verify pay password for an address
*/
export async function verifyBioforestPayPassword(
walletId: string,
password: string,
payPassword: string,
secondPublicKey: string,
): Promise<boolean> {
try {
const secret = await walletStorageService.getMnemonic(walletId, password)
const result = await verifyPayPassword(secret, payPassword, secondPublicKey)
return result !== false
} catch {
return false
}
}

export async function submitBioforestTransfer({
Expand All @@ -57,7 +105,9 @@ export async function submitBioforestTransfer({
fromAddress,
toAddress,
amount,
payPassword,
}: SubmitBioforestParams): Promise<SubmitBioforestResult> {
// Get mnemonic from wallet storage
let secret: string
try {
secret = await walletStorageService.getMnemonic(walletId, password)
Expand All @@ -71,43 +121,68 @@ export async function submitBioforestTransfer({
}
}

const derived = deriveBioforestKeyFromChainConfig(secret, chainConfig)
if (derived.address !== fromAddress) {
return { status: 'error', message: '签名地址不匹配' }
}

if (!amount.isPositive()) {
return { status: 'error', message: '请输入有效金额' }
}

const rpcUrl = chainConfig.rpcUrl
if (!rpcUrl) {
return { status: 'error', message: 'RPC URL 未配置' }
}

try {
const adapter = createBioforestAdapter(chainConfig)
const unsignedTx = await adapter.transaction.buildTransaction({
// Check if pay password is required but not provided
const addressInfo = await getAddressInfo(rpcUrl, chainConfig.id, fromAddress)
if (addressInfo.secondPublicKey && !payPassword) {
return {
status: 'password_required',
secondPublicKey: addressInfo.secondPublicKey,
}
}

// Verify pay password if provided
if (payPassword && addressInfo.secondPublicKey) {
const isValid = await verifyPayPassword(secret, payPassword, addressInfo.secondPublicKey)
if (!isValid) {
return { status: 'error', message: '支付密码验证失败' }
}
}

// Create transaction using SDK
console.log('[submitBioforestTransfer] Creating transaction with SDK...')
const transaction = await createTransferTransaction({
rpcUrl,
chainId: chainConfig.id,
mainSecret: secret,
paySecret: payPassword,
from: fromAddress,
to: toAddress,
amount,
amount: amount.toRawString(),
assetType: chainConfig.symbol,
})
const signedTx = await adapter.transaction.signTransaction(unsignedTx, hexToBytes(derived.privateKey))
const txHash = await adapter.transaction.broadcastTransaction(signedTx)

console.log('[submitBioforestTransfer] Broadcasting transaction...')
const txHash = await broadcastTransaction(rpcUrl, chainConfig.id, transaction)

console.log('[submitBioforestTransfer] Transaction broadcasted:', txHash)
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 链交易需要特定的交易格式,当前实现可能不完整。`,
}
// Handle specific error cases
if (errorMessage.includes('insufficient') || errorMessage.includes('余额不足')) {
return { status: 'error', message: '余额不足' }
}

if (errorMessage.includes('fee') || errorMessage.includes('手续费')) {
return { status: 'error', message: '手续费不足' }
}

return {
status: 'error',
message: errorMessage || '未知错误',
message: errorMessage || '交易失败,请稍后重试',
}
}
}
13 changes: 13 additions & 0 deletions src/hooks/use-send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,19 @@ export function useSend(options: UseSendOptions = {}): UseSendReturn {
return { status: 'password' as const }
}

if (result.status === 'password_required') {
// Pay password is required - for now treat as error, UI should prompt for pay password
setState((prev) => ({
...prev,
step: 'result',
isSubmitting: false,
resultStatus: 'failed',
txHash: null,
errorMessage: '此地址已设置支付密码,需要输入支付密码才能转账',
}))
return { status: 'error' as const }
}

if (result.status === 'error') {
setState((prev) => ({
...prev,
Expand Down
10 changes: 10 additions & 0 deletions src/services/bioforest-sdk/bioforest-chain-bundle.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Type declarations for the BioForest Chain Bundle
*
* This is a pre-built JavaScript bundle that exports the SDK setup function.
*/

import type { BioforestChainBundleSetup } from './types'

/** Setup function exported by the bundle */
export const setup: BioforestChainBundleSetup
Loading