From a714ab7dc47db0dbf384da9e883b2d421d50ad1e Mon Sep 17 00:00:00 2001 From: Gaubee Date: Mon, 16 Feb 2026 01:14:40 +0800 Subject: [PATCH] fix(ecosystem): propagate miniapp remark and update keyapp dweb id --- manifest.json | 6 +- packages/dweb-compat/src/signature.ts | 8 +- src/hooks/use-burn.bioforest.ts | 45 +++++++- .../__tests__/destroy-handler.test.ts | 42 ++++++- .../__tests__/transfer-handler.test.ts | 35 ++++++ src/services/ecosystem/handlers/context.ts | 12 +- src/services/ecosystem/handlers/destroy.ts | 32 ++++- src/services/ecosystem/handlers/transfer.ts | 3 + src/services/ecosystem/types.ts | 1 + src/stackflow/activities/MainTabsActivity.tsx | 109 +++++++++++++++++- .../MiniappConfirmJobs.regression.test.tsx | 79 +++++++++++++ .../sheets/MiniappDestroyConfirmJob.tsx | 54 +++++++-- .../sheets/MiniappTransferConfirmJob.tsx | 7 +- 13 files changed, 408 insertions(+), 25 deletions(-) diff --git a/manifest.json b/manifest.json index 2b0d61751..48e255f1a 100644 --- a/manifest.json +++ b/manifest.json @@ -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 - 多链钱包应用", @@ -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" } diff --git a/packages/dweb-compat/src/signature.ts b/packages/dweb-compat/src/signature.ts index 3da6a5f6e..0a8eeb8fe 100644 --- a/packages/dweb-compat/src/signature.ts +++ b/packages/dweb-compat/src/signature.ts @@ -96,6 +96,7 @@ async function handleTransfer(param: { balance: string assetType?: string contractInfo?: { contractAddress: string; assetType: string; decimals: number | string } + remark?: Record }): Promise { const result = await bioRequest<{ txHash: string; transaction?: object }>('bio_sendTransaction', { from: param.senderAddress, @@ -104,6 +105,7 @@ async function handleTransfer(param: { chain: param.chainName, asset: param.assetType, contractInfo: param.contractInfo, + remark: param.remark, }) return { @@ -145,12 +147,14 @@ async function handleDestroy(param: { senderAddress: string assetType: string destoryAmount: string + remark?: Record }): Promise { 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 } diff --git a/src/hooks/use-burn.bioforest.ts b/src/hooks/use-burn.bioforest.ts index 36bbfaf1e..bf47b3449 100644 --- a/src/hooks/use-burn.bioforest.ts +++ b/src/hooks/use-burn.bioforest.ts @@ -13,6 +13,34 @@ import i18n from '@/i18n'; const t = i18n.t.bind(i18n); +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null; +} + +function cloneTransactionRecord(record: Record): Record { + 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; @@ -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; pendingTxId: string } | { status: 'password' } | { status: 'password_required'; secondPublicKey: string } | { status: 'error'; message: string; pendingTxId?: string }; @@ -93,6 +121,7 @@ export interface SubmitBioforestBurnParams { amount: Amount; fee?: Amount; twoStepSecret?: string; + remark?: Record; } /** @@ -108,6 +137,7 @@ export async function submitBioforestBurn({ amount, fee: _fee, twoStepSecret, + remark, }: SubmitBioforestBurnParams): Promise { // Get mnemonic from wallet storage let secret: string; @@ -169,6 +199,7 @@ export async function submitBioforestBurn({ recipientId: recipientAddress, amount, bioAssetType: assetType, + ...(remark ? { remark } : {}), }); // Sign transaction @@ -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({ diff --git a/src/services/ecosystem/__tests__/destroy-handler.test.ts b/src/services/ecosystem/__tests__/destroy-handler.test.ts index 88946c2b8..1369a92ec 100644 --- a/src/services/ecosystem/__tests__/destroy-handler.test.ts +++ b/src/services/ecosystem/__tests__/destroy-handler.test.ts @@ -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( @@ -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', + }, + }); + }); }); diff --git a/src/services/ecosystem/__tests__/transfer-handler.test.ts b/src/services/ecosystem/__tests__/transfer-handler.test.ts index 69b9be369..722c4bd7d 100644 --- a/src/services/ecosystem/__tests__/transfer-handler.test.ts +++ b/src/services/ecosystem/__tests__/transfer-handler.test.ts @@ -19,6 +19,11 @@ const baseParams: EcosystemTransferParams = { chain: 'bfmeta', } +const baseRemark = { + ex_type: 'exchange.purchase', + ex_id: 'ex-1', +} + describe('handleSendTransaction', () => { beforeEach(() => { HandlerContext.clear() @@ -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, diff --git a/src/services/ecosystem/handlers/context.ts b/src/services/ecosystem/handlers/context.ts index 2b767eed1..2ae7fe258 100644 --- a/src/services/ecosystem/handlers/context.ts +++ b/src/services/ecosystem/handlers/context.ts @@ -101,6 +101,16 @@ export interface TransferDialogResult { transaction?: Record } +/** 销毁对话框返回值 */ +export interface DestroyDialogResult { + /** 链上交易 ID(兼容旧字段) */ + txHash: string + /** 语义化别名:交易 ID */ + txId?: string + /** 可序列化交易对象(供调用方直接提交后端) */ + transaction?: Record +} + /** Handler 回调接口 */ export interface HandlerCallbacks { // Bio (BioChain) callbacks @@ -108,7 +118,7 @@ export interface HandlerCallbacks { getConnectedAccounts: () => BioAccount[] showSigningDialog: (params: SigningParams) => Promise showTransferDialog: (params: EcosystemTransferParams & { app: MiniappInfo }) => Promise - showDestroyDialog?: (params: EcosystemDestroyParams & { app: MiniappInfo }) => Promise<{ txHash: string } | null> + showDestroyDialog?: (params: EcosystemDestroyParams & { app: MiniappInfo }) => Promise showSignTransactionDialog: (params: SignTransactionParams) => Promise // EVM (Ethereum/BSC) callbacks diff --git a/src/services/ecosystem/handlers/destroy.ts b/src/services/ecosystem/handlers/destroy.ts index 70b62a9c1..5f05f871f 100644 --- a/src/services/ecosystem/handlers/destroy.ts +++ b/src/services/ecosystem/handlers/destroy.ts @@ -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) | null = null /** @deprecated 使用 HandlerContext.register 替代 */ export function setDestroyDialog(dialog: typeof _showDestroyDialog): void { @@ -22,6 +22,31 @@ function getDestroyDialog(appId: string) { return callbacks?.showDestroyDialog ?? _showDestroyDialog } +function isRecord(value: unknown): value is Record { + 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 | undefined @@ -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), } @@ -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) } diff --git a/src/services/ecosystem/handlers/transfer.ts b/src/services/ecosystem/handlers/transfer.ts index 1f44a08e8..27cd6fc49 100644 --- a/src/services/ecosystem/handlers/transfer.ts +++ b/src/services/ecosystem/handlers/transfer.ts @@ -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)) diff --git a/src/services/ecosystem/types.ts b/src/services/ecosystem/types.ts index 86bfbcea4..9ac68e23d 100644 --- a/src/services/ecosystem/types.ts +++ b/src/services/ecosystem/types.ts @@ -67,6 +67,7 @@ export interface EcosystemDestroyParams { amount: string; // raw 最小单位整数字符串 chain: string; asset: string; + remark?: Record; } /** Request message from miniapp */ diff --git a/src/stackflow/activities/MainTabsActivity.tsx b/src/stackflow/activities/MainTabsActivity.tsx index 392c471b3..0ef070766 100644 --- a/src/stackflow/activities/MainTabsActivity.tsx +++ b/src/stackflow/activities/MainTabsActivity.tsx @@ -8,12 +8,13 @@ import { EcosystemTab } from "./tabs/EcosystemTab"; import { SettingsTab } from "./tabs/SettingsTab"; import { useFlow } from "../stackflow"; import { superjson } from "@biochain/chain-effect"; -import type { BioAccount, EcosystemTransferParams, SignedTransaction } from "@/services/ecosystem"; +import type { BioAccount, EcosystemDestroyParams, EcosystemTransferParams, SignedTransaction } from "@/services/ecosystem"; import { getBridge, initBioProvider, setChainSwitchConfirm, setCryptoAuthorizeDialog, + setDestroyDialog, setEvmSigningDialog, setEvmTransactionDialog, setEvmWalletPicker, @@ -143,7 +144,7 @@ export const MainTabsActivity: ActivityComponentType = ({ params console.log('[miniapp-transfer-sheet]', stage, payload); }; - const enqueueTransferSheetTask = (task: () => Promise, meta: { requestId: string; source: 'bio' | 'evm' }): Promise => { + const enqueueTransferSheetTask = (task: () => Promise, meta: { requestId: string; source: 'bio' | 'evm' | 'destroy' }): Promise => { logTransferSheet('queue.enqueue', meta); const run = transferSheetTailRef.current @@ -263,6 +264,7 @@ export const MainTabsActivity: ActivityComponentType = ({ params amount: params.amount, chain: params.chain, ...(params.asset ? { asset: params.asset } : {}), + ...(params.remark ? { remark: params.remark } : {}), }); }), { requestId, source: 'bio' }, @@ -270,6 +272,108 @@ export const MainTabsActivity: ActivityComponentType = ({ params }); }); + setDestroyDialog(async (params: EcosystemDestroyParams & { app: { name: string; icon?: string } }) => { + const requestId = `destroy-${Date.now()}-${++transferSheetSeqRef.current}`; + + return new Promise<{ txHash: string; txId?: string; transaction?: Record } | null>((resolveResult) => { + void enqueueTransferSheetTask( + () => + new Promise((releaseQueue) => { + let resultSettled = false; + let queueReleased = false; + + const cleanup = () => { + window.removeEventListener("miniapp-destroy-confirm", handleResult); + window.removeEventListener("miniapp-destroy-sheet-closed", handleSheetClosed); + }; + + const resolveResultOnce = (value: { txHash: string; txId?: string; transaction?: Record } | null) => { + if (resultSettled) return; + resultSettled = true; + resolveResult(value); + if (queueReleased) { + cleanup(); + } + }; + + const releaseQueueOnce = (reason: string) => { + if (queueReleased) return; + queueReleased = true; + logTransferSheet('queue.release', { + requestId, + source: 'destroy', + reason, + resultSettled, + }); + releaseQueue(); + if (resultSettled) { + cleanup(); + } + }; + + const handleResult = (e: Event) => { + const detail = (e as CustomEvent).detail as + | { requestId?: string; confirmed?: boolean; txHash?: string; txId?: string; transaction?: Record } + | undefined; + + if (detail?.requestId !== requestId) { + logTransferSheet('dialog.ignore', { requestId, source: 'destroy', incomingRequestId: detail?.requestId ?? 'unknown' }); + return; + } + + logTransferSheet('dialog.receive', { + requestId, + source: 'destroy', + confirmed: detail?.confirmed === true, + }); + + if (detail?.confirmed && detail.txHash) { + resolveResultOnce({ + txHash: detail.txHash, + txId: detail.txId, + transaction: detail.transaction, + }); + return; + } + resolveResultOnce(null); + }; + + const handleSheetClosed = (e: Event) => { + const detail = (e as CustomEvent).detail as { requestId?: string; reason?: string } | undefined; + if (detail?.requestId !== requestId) { + return; + } + + logTransferSheet('dialog.sheet-closed', { + requestId, + source: 'destroy', + reason: detail?.reason ?? 'unknown', + }); + if (!resultSettled) { + resolveResultOnce(null); + } + releaseQueueOnce(detail?.reason ?? 'sheet-closed'); + }; + + window.addEventListener("miniapp-destroy-confirm", handleResult); + window.addEventListener("miniapp-destroy-sheet-closed", handleSheetClosed); + logTransferSheet('dialog.push', { requestId, source: 'destroy', from: params.from, chain: params.chain, asset: params.asset }); + push("MiniappDestroyConfirmJob", { + requestId, + appName: params.app.name, + appIcon: params.app.icon ?? "", + from: params.from, + amount: params.amount, + chain: params.chain, + asset: params.asset, + ...(params.remark ? { remark: params.remark } : {}), + }); + }), + { requestId, source: 'destroy' }, + ); + }); + }); + setSignTransactionDialog(async (params) => { return new Promise((resolve) => { const timeout = window.setTimeout(() => resolve(null), 60_000); @@ -557,6 +661,7 @@ export const MainTabsActivity: ActivityComponentType = ({ params setGetAccounts(null); setSigningDialog(null); setTransferDialog(null); + setDestroyDialog(null); setSignTransactionDialog(null); }; }, [push]); diff --git a/src/stackflow/activities/sheets/MiniappConfirmJobs.regression.test.tsx b/src/stackflow/activities/sheets/MiniappConfirmJobs.regression.test.tsx index 6c30d2f11..256ed7e05 100644 --- a/src/stackflow/activities/sheets/MiniappConfirmJobs.regression.test.tsx +++ b/src/stackflow/activities/sheets/MiniappConfirmJobs.regression.test.tsx @@ -346,6 +346,85 @@ describe('miniapp confirm jobs regressions', () => { expect(intent.amount.toRawString()).toBe('1000000000'); }); + it('passes remark into transaction intent and keeps it in emitted transaction', async () => { + const buildTransaction = vi.fn(async (intent: unknown) => ({ + chainId: 'bfmetav2', + intentType: 'transfer', + data: intent, + })); + + vi.mocked(getChainProvider).mockReturnValueOnce({ + supportsFullTransaction: true, + buildTransaction, + signTransaction: vi.fn(), + broadcastTransaction: vi.fn(async () => 'tx-hash'), + } as unknown as ReturnType); + + vi.mocked(signUnsignedTransaction).mockImplementation(async (params) => ({ + chainId: 'bfmetav2', + data: params.unsignedTx.data, + signature: 'sig', + })); + + const eventPromise = new Promise }>>((resolve) => { + const handleEvent = (event: Event) => { + const customEvent = event as CustomEvent<{ confirmed?: boolean; transaction?: Record }>; + if (customEvent.detail?.confirmed !== true) { + return; + } + window.removeEventListener('miniapp-transfer-confirm', handleEvent); + resolve(customEvent); + }; + + window.addEventListener('miniapp-transfer-confirm', handleEvent); + }); + + render( + , + ); + + const confirmButton = screen.getByTestId('miniapp-transfer-review-confirm'); + await waitFor(() => { + expect(confirmButton).not.toBeDisabled(); + }); + + fireEvent.click(confirmButton); + fireEvent.click(screen.getByTestId('pattern-lock')); + + await waitFor(() => { + expect(buildTransaction).toHaveBeenCalledTimes(1); + }); + + expect(buildTransaction).toHaveBeenCalledWith( + expect.objectContaining({ + remark: { + ex_type: 'exchange.purchase', + ex_id: 'exchange-001', + }, + }), + ); + + const event = await eventPromise; + expect(event.detail.transaction?.remark).toEqual({ + ex_type: 'exchange.purchase', + ex_id: 'exchange-001', + }); + }); + it('ignores duplicated unlock submission while transfer is in-flight', async () => { vi.mocked(signUnsignedTransaction).mockImplementation(async () => { diff --git a/src/stackflow/activities/sheets/MiniappDestroyConfirmJob.tsx b/src/stackflow/activities/sheets/MiniappDestroyConfirmJob.tsx index 0a6cdc0f1..0a46ea4d3 100644 --- a/src/stackflow/activities/sheets/MiniappDestroyConfirmJob.tsx +++ b/src/stackflow/activities/sheets/MiniappDestroyConfirmJob.tsx @@ -22,6 +22,8 @@ import { ChainAddressDisplay } from '@/components/wallet/chain-address-display'; import { findMiniappWalletByAddress, resolveMiniappChainId } from './miniapp-wallet'; type MiniappDestroyConfirmJobParams = { + /** 请求标识(用于 FIFO 事件隔离) */ + requestId?: string; /** 来源小程序名称 */ appName: string; /** 来源小程序图标 */ @@ -34,13 +36,28 @@ type MiniappDestroyConfirmJobParams = { chain: string; /** 资产类型 */ asset: string; + /** 业务备注(透传到交易体) */ + remark?: Record; +}; + +type MiniappDestroyResultDetail = { + requestId?: string; + confirmed: boolean; + txHash?: string; + txId?: string; + transaction?: Record; +}; + +type MiniappDestroySheetClosedDetail = { + requestId?: string; + reason: 'cancel' | 'confirmed'; }; function MiniappDestroyConfirmJobContent() { const { t } = useTranslation(['common', 'transaction']); const { pop, push } = useFlow(); const params = useActivityParams(); - const { appName, appIcon, from, amount, chain, asset } = params; + const { requestId, appName, appIcon, from, amount, chain, asset, remark } = params; const chainConfigState = useChainConfigState(); const resolvedChainId = useMemo(() => resolveMiniappChainId(chain), [chain]); @@ -53,6 +70,24 @@ function MiniappDestroyConfirmJobContent() { const [isConfirming, setIsConfirming] = useState(false); + const emitDestroyResult = useCallback((detail: MiniappDestroyResultDetail) => { + if (typeof window === 'undefined') return; + const payload: MiniappDestroyResultDetail = { + requestId, + ...detail, + }; + window.dispatchEvent(new CustomEvent('miniapp-destroy-confirm', { detail: payload })); + }, [requestId]); + + const emitSheetClosed = useCallback((reason: MiniappDestroySheetClosedDetail['reason']) => { + if (typeof window === 'undefined') return; + const payload: MiniappDestroySheetClosedDetail = { + requestId, + reason, + }; + window.dispatchEvent(new CustomEvent('miniapp-destroy-sheet-closed', { detail: payload })); + }, [requestId]); + const walletName = targetWallet?.name || t('common:unknownWallet'); const lockDescription = `${appName || t('common:unknownDApp')} ${t('common:requestsDestroy')}`; const parsedAmount = useMemo(() => { @@ -106,6 +141,7 @@ function MiniappDestroyConfirmJobContent() { recipientAddress: applyAddress, assetType: asset, amount: amountObj, + ...(remark ? { remark } : {}), }); if (result.status === 'password') { @@ -119,11 +155,15 @@ function MiniappDestroyConfirmJobContent() { // 发送成功事件 const event = new CustomEvent('miniapp-destroy-confirm', { detail: { + requestId, confirmed: true, txHash: result.status === 'ok' ? result.txHash : undefined, - }, + txId: result.status === 'ok' ? result.txId : undefined, + transaction: result.status === 'ok' ? result.transaction : undefined, + } satisfies MiniappDestroyResultDetail, }); window.dispatchEvent(event); + emitSheetClosed('confirmed'); pop(); return true; @@ -144,15 +184,13 @@ function MiniappDestroyConfirmJobContent() { walletAddress: from, walletChainId: resolvedChainId, }); - }, [isConfirming, targetWallet, chainConfig, asset, from, amount, parsedAmount, pop, push, t, lockDescription, appName, appIcon, walletName, resolvedChainId]); + }, [isConfirming, targetWallet, chainConfig, asset, from, amount, parsedAmount, pop, push, t, lockDescription, appName, appIcon, walletName, resolvedChainId, remark, requestId, emitSheetClosed]); const handleCancel = useCallback(() => { - const event = new CustomEvent('miniapp-destroy-confirm', { - detail: { confirmed: false }, - }); - window.dispatchEvent(event); + emitDestroyResult({ confirmed: false }); + emitSheetClosed('cancel'); pop(); - }, [pop]); + }, [emitDestroyResult, emitSheetClosed, pop]); return ( diff --git a/src/stackflow/activities/sheets/MiniappTransferConfirmJob.tsx b/src/stackflow/activities/sheets/MiniappTransferConfirmJob.tsx index bef27720d..2199410cf 100644 --- a/src/stackflow/activities/sheets/MiniappTransferConfirmJob.tsx +++ b/src/stackflow/activities/sheets/MiniappTransferConfirmJob.tsx @@ -54,6 +54,8 @@ type MiniappTransferConfirmJobParams = { chain: string; /** 代币 (可选) */ asset?: string; + /** 业务备注(透传到交易体) */ + remark?: Record; }; type TransferStep = MiniappTransferFlowStep; @@ -109,7 +111,7 @@ function MiniappTransferConfirmJobContent() { const { pop } = useFlow(); const toast = useToast(); const params = useActivityParams(); - const { requestId, appName, appIcon, from, to, amount, chain, asset } = params; + const { requestId, appName, appIcon, from, to, amount, chain, asset, remark } = params; const fallbackRequestIdRef = useRef(requestId ?? `legacy-transfer-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`); const effectiveRequestId = fallbackRequestIdRef.current; @@ -430,6 +432,7 @@ function MiniappTransferConfirmJobContent() { to, amount: parsedAmount, ...(asset ? { bioAssetType: asset } : {}), + ...(remark ? { remark } : {}), }); const signedTx = await signUnsignedTransaction({ @@ -460,7 +463,7 @@ function MiniappTransferConfirmJobContent() { return { txHash, transaction }; }, - [resolvedChainId, parsedAmount, asset, from, to, walletId], + [resolvedChainId, parsedAmount, asset, from, to, walletId, remark], ); const handleTransferFailure = useCallback(