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
6 changes: 3 additions & 3 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"id": "bfmpay.bfmeta.com.dweb",
"id": "keyapp.bf-meta.org.dweb",
"name": "BFM Pay",
"short_name": "BFM Pay",
"description": "BFM Pay - 多链钱包应用",
Expand All @@ -24,6 +24,6 @@
"application",
"wallet"
],
"home": "bfmpay.bfmeta.info",
"homepage_url": "bfmpay.bfmeta.info"
"home": "keyapp.bf-meta.org",
"homepage_url": "keyapp.bf-meta.org"
}
8 changes: 6 additions & 2 deletions packages/dweb-compat/src/signature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ async function handleTransfer(param: {
balance: string
assetType?: string
contractInfo?: { contractAddress: string; assetType: string; decimals: number | string }
remark?: Record<string, string>
}): Promise<TransferResponse> {
const result = await bioRequest<{ txHash: string; transaction?: object }>('bio_sendTransaction', {
from: param.senderAddress,
Expand All @@ -104,6 +105,7 @@ async function handleTransfer(param: {
chain: param.chainName,
asset: param.assetType,
contractInfo: param.contractInfo,
remark: param.remark,
})

return {
Expand Down Expand Up @@ -145,12 +147,14 @@ async function handleDestroy(param: {
senderAddress: string
assetType: string
destoryAmount: string
remark?: Record<string, string>
}): Promise<TransferResponse> {
const result = await bioRequest<{ txId: string; transaction: object }>('bio_destroyAsset', {
address: param.senderAddress,
from: param.senderAddress,
chain: param.chainName,
assetType: param.assetType,
asset: param.assetType,
amount: param.destoryAmount,
remark: param.remark,
})
return result
}
Expand Down
45 changes: 43 additions & 2 deletions src/hooks/use-burn.bioforest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,34 @@ import i18n from '@/i18n';

const t = i18n.t.bind(i18n);

function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null;
}

function cloneTransactionRecord(record: Record<string, unknown>): Record<string, unknown> {
if (typeof structuredClone === 'function') {
try {
const cloned = structuredClone(record);
if (isRecord(cloned)) {
return cloned;
}
} catch {
// fallback to JSON clone
}
}

try {
const cloned = JSON.parse(JSON.stringify(record)) as unknown;
if (isRecord(cloned)) {
return cloned;
}
} catch {
// fallback to shallow clone
}

return { ...record };
}

export interface BioforestBurnFeeResult {
amount: Amount;
symbol: string;
Expand Down Expand Up @@ -78,7 +106,7 @@ export async function fetchBioforestBurnFee(
}

export type SubmitBioforestBurnResult =
| { status: 'ok'; txHash: string; pendingTxId: string }
| { status: 'ok'; txHash: string; txId: string; transaction: Record<string, unknown>; pendingTxId: string }
| { status: 'password' }
| { status: 'password_required'; secondPublicKey: string }
| { status: 'error'; message: string; pendingTxId?: string };
Expand All @@ -93,6 +121,7 @@ export interface SubmitBioforestBurnParams {
amount: Amount;
fee?: Amount;
twoStepSecret?: string;
remark?: Record<string, string>;
}

/**
Expand All @@ -108,6 +137,7 @@ export async function submitBioforestBurn({
amount,
fee: _fee,
twoStepSecret,
remark,
}: SubmitBioforestBurnParams): Promise<SubmitBioforestBurnResult> {
// Get mnemonic from wallet storage
let secret: string;
Expand Down Expand Up @@ -169,6 +199,7 @@ export async function submitBioforestBurn({
recipientId: recipientAddress,
amount,
bioAssetType: assetType,
...(remark ? { remark } : {}),
});

// Sign transaction
Expand Down Expand Up @@ -203,7 +234,17 @@ export async function submitBioforestBurn({
status: 'broadcasted',
txHash: broadcastTxHash,
});
return { status: 'ok', txHash: broadcastTxHash, pendingTxId: pendingTx.id };
const transaction = isRecord(signedTx.data)
? cloneTransactionRecord(signedTx.data)
: { data: signedTx.data };

return {
status: 'ok',
txHash: broadcastTxHash,
txId: broadcastTxHash,
transaction,
pendingTxId: pendingTx.id,
};
} catch (err) {
const error = err as Error;
await pendingTxService.updateStatus({
Expand Down
42 changes: 40 additions & 2 deletions src/services/ecosystem/__tests__/destroy-handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('handleDestroyAsset amount semantics', () => {
});

it('accepts raw amount and opens dialog', async () => {
const dialog = vi.fn(async () => ({ txHash: 'tx-hash' }));
const dialog = vi.fn(async () => ({ txHash: 'tx-hash', txId: 'tx-hash', transaction: { hash: 'tx-hash' } }));
setDestroyDialog(dialog);

const result = await handleDestroyAsset(
Expand All @@ -52,11 +52,49 @@ describe('handleDestroyAsset amount semantics', () => {
context,
);

expect(result).toEqual({ txHash: 'tx-hash' });
expect(result).toEqual({
txHash: 'tx-hash',
txId: 'tx-hash',
transaction: { hash: 'tx-hash' },
});
expect(dialog).toHaveBeenCalledWith(
expect.objectContaining({
amount: '1000000000',
}),
);
});

it('passes remark to destroy dialog and normalizes transaction object', async () => {
const dialog = vi.fn(async () => ({ txHash: 'tx-hash' }));
setDestroyDialog(dialog);

const result = await handleDestroyAsset(
{
from: 'b_sender',
amount: '1000000000',
chain: 'bfmeta',
asset: 'BFM',
remark: {
ex_type: 'exchange.purchase',
},
},
context,
);

expect(dialog).toHaveBeenCalledWith(
expect.objectContaining({
remark: {
ex_type: 'exchange.purchase',
},
}),
);
expect(result).toEqual({
txHash: 'tx-hash',
txId: 'tx-hash',
transaction: {
txHash: 'tx-hash',
txId: 'tx-hash',
},
});
});
});
35 changes: 35 additions & 0 deletions src/services/ecosystem/__tests__/transfer-handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ const baseParams: EcosystemTransferParams = {
chain: 'bfmeta',
}

const baseRemark = {
ex_type: 'exchange.purchase',
ex_id: 'ex-1',
}

describe('handleSendTransaction', () => {
beforeEach(() => {
HandlerContext.clear()
Expand Down Expand Up @@ -106,6 +111,36 @@ describe('handleSendTransaction', () => {
expect(typeof (result as { transaction: unknown }).transaction).toBe('object')
})

it('passes remark to transfer dialog', async () => {
const showTransferDialog = vi.fn(async () => ({
txHash: 'tx-hash-ctx-icon',
txId: 'tx-hash-ctx-icon',
transaction: { hash: 'tx-hash-ctx-icon' },
}))

HandlerContext.register(baseContext.appId, {
showWalletPicker: async () => null,
getConnectedAccounts: () => [],
showSigningDialog: async () => null,
showTransferDialog,
showSignTransactionDialog: async () => null,
})

await handleSendTransaction(
{
...baseParams,
remark: baseRemark,
},
baseContext,
)

expect(showTransferDialog).toHaveBeenCalledWith(
expect.objectContaining({
remark: baseRemark,
}),
)
})

it('keeps backward compatibility for legacy txHash-only callback', async () => {
HandlerContext.register(baseContext.appId, {
showWalletPicker: async () => null,
Expand Down
12 changes: 11 additions & 1 deletion src/services/ecosystem/handlers/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,24 @@ export interface TransferDialogResult {
transaction?: Record<string, unknown>
}

/** 销毁对话框返回值 */
export interface DestroyDialogResult {
/** 链上交易 ID(兼容旧字段) */
txHash: string
/** 语义化别名:交易 ID */
txId?: string
/** 可序列化交易对象(供调用方直接提交后端) */
transaction?: Record<string, unknown>
}

/** Handler 回调接口 */
export interface HandlerCallbacks {
// Bio (BioChain) callbacks
showWalletPicker: (opts?: { chain?: string; exclude?: string; app?: MiniappInfo }) => Promise<BioAccount | null>
getConnectedAccounts: () => BioAccount[]
showSigningDialog: (params: SigningParams) => Promise<SigningResult | null>
showTransferDialog: (params: EcosystemTransferParams & { app: MiniappInfo }) => Promise<TransferDialogResult | null>
showDestroyDialog?: (params: EcosystemDestroyParams & { app: MiniappInfo }) => Promise<{ txHash: string } | null>
showDestroyDialog?: (params: EcosystemDestroyParams & { app: MiniappInfo }) => Promise<DestroyDialogResult | null>
showSignTransactionDialog: (params: SignTransactionParams) => Promise<SignedTransaction | null>

// EVM (Ethereum/BSC) callbacks
Expand Down
32 changes: 29 additions & 3 deletions src/services/ecosystem/handlers/destroy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

import type { MethodHandler, EcosystemDestroyParams } from '../types'
import { BioErrorCodes } from '../types'
import { HandlerContext, type MiniappInfo, toMiniappInfo } from './context'
import { HandlerContext, type DestroyDialogResult, type MiniappInfo, toMiniappInfo } from './context'
import { enqueueMiniappSheet } from '../sheet-queue'
import { isRawAmountString } from '../raw-amount'

// 兼容旧 API
let _showDestroyDialog: ((params: EcosystemDestroyParams & { app: MiniappInfo }) => Promise<{ txHash: string } | null>) | null = null
let _showDestroyDialog: ((params: EcosystemDestroyParams & { app: MiniappInfo }) => Promise<DestroyDialogResult | null>) | null = null

/** @deprecated 使用 HandlerContext.register 替代 */
export function setDestroyDialog(dialog: typeof _showDestroyDialog): void {
Expand All @@ -22,6 +22,31 @@ function getDestroyDialog(appId: string) {
return callbacks?.showDestroyDialog ?? _showDestroyDialog
}

function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null
}

function normalizeDestroyResult(result: DestroyDialogResult) {
const txHash = result.txHash || result.txId
const txId = result.txId || result.txHash
if (!txHash || !txId) {
throw Object.assign(new Error('Invalid destroy result: missing tx id'), { code: BioErrorCodes.INTERNAL_ERROR })
}

const transaction = isRecord(result.transaction)
? result.transaction
: {
txId,
txHash,
}

return {
txHash,
txId,
transaction,
}
}

/** bio_destroyAsset - Destroy an asset (BioForest chains only) */
export const handleDestroyAsset: MethodHandler = async (params, context) => {
const opts = params as Partial<EcosystemDestroyParams> | undefined
Expand Down Expand Up @@ -49,6 +74,7 @@ export const handleDestroyAsset: MethodHandler = async (params, context) => {
amount: opts.amount,
chain: opts.chain,
asset: opts.asset,
...(opts.remark ? { remark: opts.remark } : {}),
app: toMiniappInfo(context),
}

Expand All @@ -58,5 +84,5 @@ export const handleDestroyAsset: MethodHandler = async (params, context) => {
throw Object.assign(new Error('User rejected'), { code: BioErrorCodes.USER_REJECTED })
}

return result
return normalizeDestroyResult(result)
}
3 changes: 3 additions & 0 deletions src/services/ecosystem/handlers/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ export const handleSendTransaction: MethodHandler = async (params, context) => {
if (opts.tokenAddress) {
transferParams.tokenAddress = opts.tokenAddress
}
if (opts.remark) {
transferParams.remark = opts.remark
}

const result = await enqueueMiniappSheet(context.appId, () => showTransferDialog(transferParams))

Expand Down
1 change: 1 addition & 0 deletions src/services/ecosystem/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export interface EcosystemDestroyParams {
amount: string; // raw 最小单位整数字符串
chain: string;
asset: string;
remark?: Record<string, string>;
}

/** Request message from miniapp */
Expand Down
Loading