-
Notifications
You must be signed in to change notification settings - Fork 2
feat: return the network fee when sending tx with FBT #139
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
e24866a
a3fdc2a
90f16a9
a58117d
a2b9d86
c5c7fbf
4bdfeb3
821d51e
f7152de
8aa912b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,9 +6,8 @@ | |
| */ | ||
|
|
||
| import { z } from 'zod'; | ||
| import { constants, Transaction } from '@hathor/wallet-lib'; | ||
| import { constants, Transaction, tokensUtils } from '@hathor/wallet-lib'; | ||
| import type { DataScriptOutputRequestObj, IHathorWallet } from '@hathor/wallet-lib'; | ||
| import type { IDataOutput } from '@hathor/wallet-lib/lib/types'; | ||
| import { | ||
| TriggerTypes, | ||
| PinConfirmationPrompt, | ||
|
|
@@ -33,6 +32,23 @@ import { | |
| } from '../errors'; | ||
| import { validateNetwork, fetchTokenDetails } from '../helpers'; | ||
|
|
||
| /** | ||
| * Unified send transaction interface for both HathorWallet and HathorWalletServiceWallet. | ||
| * | ||
| * Both wallet implementations provide sendTransaction services that support | ||
| * a prepare-then-sign flow through this interface. This allows building the | ||
| * transaction before requesting user confirmation, then signing with the PIN | ||
| * only after approval. | ||
| * | ||
| * TODO: Remove this once wallet-lib exports a unified ISendTransaction with | ||
| * prepareTx/signTx (see hathor-wallet-lib PR #1022). | ||
| */ | ||
| interface ISendTransactionService { | ||
| prepareTx(): Promise<Transaction>; | ||
| signTx(pin: string): Promise<Transaction>; | ||
| runFromMining(): Promise<Transaction>; | ||
| } | ||
|
|
||
| const OutputValueSchema = z.object({ | ||
| address: z.string(), | ||
| value: z.string().regex(/^\d+$/) | ||
|
|
@@ -101,23 +117,20 @@ export async function sendTransaction( | |
| const { params } = validationResult.data; | ||
| validateNetwork(wallet, params.network); | ||
|
|
||
| // sendManyOutputsSendTransaction throws if it doesn't receive a pin, | ||
| // but doesn't use it until prepareTxData is called, so we can just assign | ||
| // an arbitrary value to it and then mutate the instance after we get the | ||
| // actual pin from the pin prompt. | ||
| const stubPinCode = '111111'; | ||
|
|
||
| // Create the transaction service but don't run it yet | ||
| const sendTransaction = await wallet.sendManyOutputsSendTransaction(params.outputs, { | ||
| // Create the transaction service and cast to the unified interface that works | ||
| // with both HathorWallet (SendTransaction) and HathorWalletServiceWallet | ||
| // (SendTransactionWalletService) implementations. | ||
| const sendTransactionService = await wallet.sendManyOutputsSendTransaction(params.outputs, { | ||
| inputs: params.inputs || [], | ||
| changeAddress: params.changeAddress, | ||
| pinCode: stubPinCode, | ||
| }); | ||
| }) as unknown as ISendTransactionService; | ||
|
|
||
| // Prepare the transaction to get all inputs (including automatically selected ones) | ||
| let preparedTx; | ||
| // Prepare the full transaction without signing to get inputs, outputs, and fee. | ||
| // This builds the tx so we can show it to the user for confirmation before | ||
| // requesting their PIN. | ||
| let preparedTx: Transaction; | ||
| try { | ||
| preparedTx = await sendTransaction.prepareTxData(); | ||
| preparedTx = await sendTransactionService.prepareTx(); | ||
| } catch (err) { | ||
| if (err instanceof Error) { | ||
| if (err.message.includes('Insufficient amount of tokens')) { | ||
|
|
@@ -127,22 +140,33 @@ export async function sendTransaction( | |
| throw new PrepareSendTransactionError(err instanceof Error ? err.message : 'An unknown error occurred while preparing the transaction'); | ||
| } | ||
|
|
||
| // Extract token UIDs from outputs and fetch their details | ||
| const tokenUids = preparedTx.outputs | ||
| .filter((output): output is IDataOutput & { token: string } => 'token' in output && typeof output.token === 'string') | ||
| .map(output => output.token); | ||
| // Extract token UIDs from the user's requested outputs and fetch their details | ||
| const tokenUids = params.outputs | ||
| .filter((output): output is { token: string } & typeof output => 'token' in output && typeof output.token === 'string') | ||
| .map(output => output.token) | ||
| .filter(uid => uid !== constants.NATIVE_TOKEN_UID); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't you also filter for
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done 821d51e |
||
| const tokenDetails = await fetchTokenDetails(wallet, tokenUids); | ||
|
|
||
| // Show the complete transaction (with all inputs) to the user | ||
| // Calculate network fee: fee header (fee-based tokens) + data output fees | ||
| const feeHeader = preparedTx.getFeeHeader(); | ||
| const feeHeaderAmount = feeHeader | ||
| ? feeHeader.entries.reduce((sum, entry) => sum + entry.amount, 0n) | ||
| : 0n; | ||
| const dataOutputCount = params.outputs.filter(output => 'data' in output).length; | ||
| const networkFee = feeHeaderAmount + tokensUtils.getDataFee(dataOutputCount); | ||
|
|
||
| // Show the user's original parameters for confirmation | ||
| const prompt: SendTransactionConfirmationPrompt = { | ||
| ...rpcRequest, | ||
| type: TriggerTypes.SendTransactionConfirmationPrompt, | ||
| data: { | ||
| outputs: preparedTx.outputs, | ||
| inputs: preparedTx.inputs, | ||
| outputs: params.outputs, | ||
| inputs: params.inputs, | ||
| changeAddress: params.changeAddress, | ||
| pushTx: params.pushTx, | ||
| tokenDetails, | ||
| networkFee, | ||
| preparedTx, | ||
| } | ||
| }; | ||
|
|
||
|
|
@@ -170,13 +194,16 @@ export async function sendTransaction( | |
| promptHandler(loadingTrigger, requestMetadata); | ||
|
|
||
| try { | ||
| // Now execute the prepared transaction | ||
| // Sign the prepared transaction with the user's PIN | ||
| const signedTx = await sendTransactionService.signTx(pinResponse.data.pinCode); | ||
|
|
||
| let response: Transaction | string; | ||
| if (params.pushTx === false) { | ||
| const transaction = await sendTransaction.run('prepare-tx', pinResponse.data.pinCode); | ||
| response = transaction.toHex(); | ||
| // Return the signed transaction as hex without mining/pushing | ||
| response = signedTx.toHex(); | ||
| } else { | ||
| response = await sendTransaction.run(null, pinResponse.data.pinCode); | ||
| // Mine and push the signed transaction | ||
| response = await sendTransactionService.runFromMining(); | ||
| } | ||
|
|
||
| const loadingFinishedTrigger: SendTransactionLoadingFinishedTrigger = { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,8 +5,9 @@ | |
| * LICENSE file in the root directory of this source tree. | ||
| */ | ||
| import { AddressInfoObject, GetBalanceObject, TokenDetailsObject } from '@hathor/wallet-lib/lib/wallet/types'; | ||
| import { TokenVersion } from '@hathor/wallet-lib'; | ||
| import { NanoContractAction } from '@hathor/wallet-lib/lib/nano_contracts/types'; | ||
| import { IDataInput, IDataOutput } from '@hathor/wallet-lib/lib/types'; | ||
| import type { DataScriptOutputRequestObj, Transaction } from '@hathor/wallet-lib'; | ||
| import { RequestMetadata, RpcRequest } from './rpcRequest'; | ||
|
|
||
| export enum TriggerTypes { | ||
|
|
@@ -186,6 +187,11 @@ export interface NanoContractParams { | |
| parsedArgs: unknown[]; | ||
| pushTx: boolean; | ||
| tokenDetails?: Map<string, TokenDetailsObject>; | ||
| /** | ||
| * Calculated network fee for the transaction. | ||
| * This is calculated based on the token outputs and their versions. | ||
| */ | ||
| networkFee?: bigint; | ||
| } | ||
|
|
||
| export interface CreateTokenParams { | ||
|
|
@@ -202,6 +208,21 @@ export interface CreateTokenParams { | |
| meltAuthorityAddress: string | null, | ||
| allowExternalMeltAuthorityAddress: boolean, | ||
| data: string[] | null, | ||
| /** | ||
| * Token version for the new token. | ||
| * If not provided, the wallet-lib will use the default (NATIVE). | ||
| */ | ||
| tokenVersion?: TokenVersion, | ||
| /** | ||
| * Calculated network fee for the token creation. | ||
| * This is only applicable for fee tokens (TokenVersion.FEE). | ||
| */ | ||
| networkFee?: bigint, | ||
| /** | ||
| * Calculated deposit amount for the token creation. | ||
| * This is only applicable for deposit tokens (TokenVersion.DEPOSIT). | ||
| */ | ||
| depositAmount?: bigint, | ||
| } | ||
|
|
||
| // Extended type for nano contract token creation | ||
|
|
@@ -298,14 +319,37 @@ export interface SignOracleDataConfirmationResponse { | |
| data: boolean; | ||
| } | ||
|
|
||
| export interface SendTransactionOutputParam { | ||
| address: string; | ||
| value: bigint; | ||
| token: string; | ||
| timelock?: number; | ||
| } | ||
|
|
||
| export interface SendTransactionInputParam { | ||
| txId: string; | ||
| index: number; | ||
| } | ||
|
|
||
| export type SendTransactionConfirmationPrompt = BaseConfirmationPrompt & { | ||
| type: TriggerTypes.SendTransactionConfirmationPrompt; | ||
| data: { | ||
| outputs: IDataOutput[], | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here @pedroferreira1 |
||
| inputs: IDataInput[], | ||
| outputs: (SendTransactionOutputParam | DataScriptOutputRequestObj)[]; | ||
| inputs?: SendTransactionInputParam[]; | ||
| changeAddress?: string; | ||
| pushTx: boolean; | ||
| tokenDetails?: Map<string, TokenDetailsObject>; | ||
| /** | ||
| * Calculated network fee for the transaction. | ||
| * This is calculated based on the token outputs and their versions. | ||
| */ | ||
| networkFee?: bigint; | ||
| /** | ||
| * The full prepared Transaction object before signing. | ||
| * Available for clients that need access to the complete transaction details | ||
| * (e.g. inputs with scripts, outputs with token indexes, etc.). | ||
| */ | ||
| preparedTx: Transaction; | ||
| } | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.