Skip to content
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

feat: include gas/fee info and decoded transaction in TransactionResponse #587

Merged
merged 6 commits into from
Nov 10, 2022
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
5 changes: 5 additions & 0 deletions .changeset/spotty-pillows-smash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fuel-ts/providers": minor
---

include gas/fee info in TransactionResponse
3 changes: 3 additions & 0 deletions packages/fuel-gauge/src/contract-factory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ describe('Contract Factory', () => {
}),
time: expect.any(String),
transactionId: expect.any(String),
gasUsed: bn(0),
fee: bn(0),
transaction: expect.any(Object),
});

const { callResult } = await contact.functions.increment_counter(1).dryRun();
Expand Down
6 changes: 3 additions & 3 deletions packages/fuel-gauge/src/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -653,12 +653,12 @@ describe('Contract', () => {
const response = await contract.wallet!.sendTransaction(transactionRequestParsed);
const {
value: [resultA, resultB],
transactionResponse,
transactionResult,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} = await FunctionInvocationResult.build<any>(invocationScopes, response, true, contract);

expect(transactionResponse.request.witnesses.length).toEqual(1);
expect(transactionResponse.request.witnesses[0]).toEqual(signedTransaction);
expect(transactionResult.transaction.witnesses.length).toEqual(1);
expect(transactionResult.transaction.witnesses[0].data).toEqual(signedTransaction);
expect(resultA.toHex()).toEqual(bn(num).add(1).toHex());
expect(resultB.a).toEqual(!struct.a);
expect(resultB.b.toHex()).toEqual(bn(struct.b).add(1).toHex());
Expand Down
1 change: 1 addition & 0 deletions packages/providers/src/operations.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
fragment transactionFragment on Transaction {
id
rawPayload
gasPrice
status {
type: __typename
... on SubmittedStatus {
Expand Down
20 changes: 9 additions & 11 deletions packages/providers/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ import { Address } from '@fuel-ts/address';
import { NativeAssetId } from '@fuel-ts/constants';
import type { AbstractAddress, AbstractPredicate } from '@fuel-ts/interfaces';
import type { BigNumberish, BN } from '@fuel-ts/math';
import { max, bn, multiply } from '@fuel-ts/math';
import { max, bn } from '@fuel-ts/math';
import type { Transaction } from '@fuel-ts/transactions';
import {
TransactionType,
InputMessageCoder,
GAS_PRICE_FACTOR,
MAX_GAS_PER_TX,
ReceiptType,
ReceiptCoder,
Expand Down Expand Up @@ -41,11 +40,7 @@ import type {
TransactionResultReceipt,
} from './transaction-response/transaction-response';
import { TransactionResponse } from './transaction-response/transaction-response';
import {
calculatePriceWithFactor,
getGasUsedFromReceipts,
getReceiptsWithMissingOutputVariables,
} from './util';
import { calculateTransactionFee, getReceiptsWithMissingOutputVariables } from './util';

const MAX_RETRIES = 10;

Expand Down Expand Up @@ -265,7 +260,7 @@ export default class Provider {
submit: { id: transactionId },
} = await this.operations.submit({ encodedTransaction });

const response = new TransactionResponse(transactionId, transactionRequest, this);
const response = new TransactionResponse(transactionId, this);
return response;
}

Expand Down Expand Up @@ -368,14 +363,17 @@ export default class Provider {

// Execute dryRun not validated transaction to query gasUsed
const { receipts } = await this.call(transactionRequest);
const gasUsed = multiply(getGasUsedFromReceipts(receipts), margin);
const gasFee = calculatePriceWithFactor(gasUsed, gasPrice, GAS_PRICE_FACTOR);
const { gasUsed, fee } = calculateTransactionFee({
gasPrice,
receipts,
margin,
});

return {
minGasPrice,
gasPrice,
gasUsed,
fee: gasFee,
fee,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ import type {
ReceiptTransferOut,
ReceiptScriptResult,
ReceiptMessageOut,
Transaction,
} from '@fuel-ts/transactions';
import { ReceiptType, ReceiptCoder } from '@fuel-ts/transactions';
import { TransactionCoder, ReceiptType, ReceiptCoder } from '@fuel-ts/transactions';

import type {
GqlGetTransactionWithReceiptsQuery,
GqlReceiptFragmentFragment,
} from '../__generated__/operations';
import type Provider from '../provider';
import type { TransactionRequest } from '../transaction-request';
import { getGasUsedFromReceipts, sleep } from '../util';
import { calculateTransactionFee, sleep } from '../util';

export type TransactionResultCallReceipt = ReceiptCall;
export type TransactionResultReturnReceipt = ReceiptReturn;
Expand Down Expand Up @@ -60,6 +60,9 @@ export type TransactionResult<TStatus extends 'success' | 'failure'> = {
transactionId: string;
blockId: any;
time: any;
gasUsed: BN;
fee: BN;
transaction: Transaction;
};

const STATUS_POLLING_INTERVAL_MAX_MS = 5000;
Expand Down Expand Up @@ -89,17 +92,15 @@ const processGqlReceipt = (gqlReceipt: GqlReceiptFragmentFragment): TransactionR
export class TransactionResponse {
/** Transaction ID */
id: string;
/** Transaction request */
request: TransactionRequest;
/** Current provider */
provider: Provider;
/** Gas used on the transaction */
gasUsed: BN = bn(0);
/** Number off attempts to get the committed tx */
attempts: number = 0;

constructor(id: string, request: TransactionRequest, provider: Provider) {
constructor(id: string, provider: Provider) {
this.id = id;
this.request = request;
this.provider = provider;
}

Expand All @@ -117,6 +118,11 @@ export class TransactionResponse {
async waitForResult(): Promise<TransactionResult<any>> {
const transaction = await this.#fetch();

const decodedTransaction = new TransactionCoder().decode(
arrayify(transaction.rawPayload),
0
)?.[0];

switch (transaction.status?.type) {
case 'SubmittedStatus': {
// This code implements a similar approach from the fuel-core await_transaction_commit
Expand All @@ -133,24 +139,39 @@ export class TransactionResponse {
}
case 'FailureStatus': {
const receipts = transaction.receipts!.map(processGqlReceipt);
this.gasUsed = getGasUsedFromReceipts(receipts);
const { gasUsed, fee } = calculateTransactionFee({
receipts,
gasPrice: bn(transaction?.gasPrice),
});

this.gasUsed = gasUsed;
return {
status: { type: 'failure', reason: transaction.status.reason },
receipts,
transactionId: this.id,
blockId: transaction.status.block.id,
time: transaction.status.time,
gasUsed,
fee,
transaction: decodedTransaction,
};
}
case 'SuccessStatus': {
const receipts = transaction.receipts!.map(processGqlReceipt);
this.gasUsed = getGasUsedFromReceipts(receipts);
const { gasUsed, fee } = calculateTransactionFee({
receipts,
gasPrice: bn(transaction?.gasPrice),
});

return {
status: { type: 'success', programState: transaction.status.programState },
receipts,
transactionId: this.id,
blockId: transaction.status.block.id,
time: transaction.status.time,
gasUsed,
fee,
transaction: decodedTransaction,
};
}
default: {
Expand Down
26 changes: 24 additions & 2 deletions packages/providers/src/util.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import type { BytesLike } from '@ethersproject/bytes';
import { arrayify } from '@ethersproject/bytes';
import type { BN } from '@fuel-ts/math';
import { bn } from '@fuel-ts/math';
import { FAILED_TRANSFER_TO_ADDRESS_SIGNAL, ReceiptType } from '@fuel-ts/transactions';
import { multiply, bn } from '@fuel-ts/math';
import {
FAILED_TRANSFER_TO_ADDRESS_SIGNAL,
GAS_PRICE_FACTOR,
ReceiptType,
} from '@fuel-ts/transactions';

import type { TransactionResultReceipt } from './transaction-response';

Expand Down Expand Up @@ -49,3 +53,21 @@ export const getReceiptsWithMissingOutputVariables = (
receipt.type === ReceiptType.Revert &&
receipt.val.toString('hex') === FAILED_TRANSFER_TO_ADDRESS_SIGNAL
);

export const calculateTransactionFee = ({
receipts,
gasPrice,
margin,
}: {
receipts: TransactionResultReceipt[];
gasPrice: BN;
margin?: number;
}) => {
const gasUsed = multiply(getGasUsedFromReceipts(receipts), margin || 0);
const fee = calculatePriceWithFactor(gasUsed, gasPrice, GAS_PRICE_FACTOR);

return {
gasUsed,
fee,
};
};
2 changes: 1 addition & 1 deletion packages/transactions/src/coders/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ export class InputCoinCoder extends Coder<InputCoin, InputCoin> {
[decoded, o] = new NumberCoder('u32').decode(data, o);
const maturity = decoded;
[decoded, o] = new NumberCoder('u16').decode(data, o);
[decoded, o] = new NumberCoder('u16').decode(data, o);
const predicateLength = decoded;
[decoded, o] = new NumberCoder('u16').decode(data, o);
const predicateDataLength = decoded;
[decoded, o] = new ByteArrayCoder(predicateLength).decode(data, o);
const predicate = decoded;
Expand Down
Loading