From 425082b072b705ac129d1898ea4fc9863fe367f9 Mon Sep 17 00:00:00 2001 From: Michael Zaikin Date: Thu, 12 Feb 2026 14:02:49 +0000 Subject: [PATCH 1/4] feat: add proofFacts field to invoke transactions --- package-lock.json | 24 ------------------------ src/account/types/index.type.ts | 1 + src/channel/rpc_0_10_0.ts | 4 +++- src/channel/rpc_0_9_0.ts | 4 +++- src/provider/types/spec.type.ts | 6 ++++++ src/types/lib/index.ts | 1 + src/utils/stark/index.ts | 2 ++ 7 files changed, 16 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index ac42cb117..631dbc487 100644 --- a/package-lock.json +++ b/package-lock.json @@ -169,7 +169,6 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -1035,7 +1034,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -1059,7 +1057,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -2913,7 +2910,6 @@ "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", @@ -4314,7 +4310,6 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.24" @@ -4758,7 +4753,6 @@ "integrity": "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.10.0" } @@ -4814,7 +4808,6 @@ "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "7.18.0", @@ -4849,7 +4842,6 @@ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", @@ -5356,7 +5348,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5417,7 +5408,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -5984,7 +5974,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001737", "electron-to-chromium": "^1.5.211", @@ -6922,7 +6911,6 @@ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -7923,7 +7911,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -7988,7 +7975,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -8090,7 +8076,6 @@ "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -8154,7 +8139,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -11957,7 +11941,6 @@ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", @@ -12655,7 +12638,6 @@ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -15030,7 +15012,6 @@ "dev": true, "inBundle": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -16013,7 +15994,6 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -16756,7 +16736,6 @@ "integrity": "sha512-WRgl5GcypwramYX4HV+eQGzUbD7UUbljVmS+5G1uMwX/wLgYuJAxGeerXJDMO2xshng4+FXqCgyB5QfClV6WjA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/error": "^4.0.0", @@ -18447,7 +18426,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -18619,7 +18597,6 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -19022,7 +18999,6 @@ "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/src/account/types/index.type.ts b/src/account/types/index.type.ts index f7e039207..ba991d984 100644 --- a/src/account/types/index.type.ts +++ b/src/account/types/index.type.ts @@ -71,6 +71,7 @@ export interface UniversalDetails { version?: BigNumberish; resourceBounds?: ResourceBoundsBN; // ignored on estimate skipValidate?: boolean; // ignored on non-estimate + proofFacts?: BigNumberish[]; } export interface PaymasterDetails { diff --git a/src/channel/rpc_0_10_0.ts b/src/channel/rpc_0_10_0.ts index f6828e04d..784c85bd2 100644 --- a/src/channel/rpc_0_10_0.ts +++ b/src/channel/rpc_0_10_0.ts @@ -25,6 +25,7 @@ import { } from '../types'; import assert from '../utils/assert'; import { ETransactionType, JRPC, RPCSPEC010 as RPC } from '../types/api'; +import type { INVOKE_TXN_V3_WITH_PROOF } from '../provider/types/spec.type'; import { BatchClient } from '../utils/batch'; import { CallData } from '../utils/calldata'; import { isSierra } from '../utils/contract'; @@ -771,11 +772,12 @@ export class RpcChannel { }; if (invocation.type === ETransactionType.INVOKE) { - const btx: RPC.INVOKE_TXN_V3 = { + const btx: INVOKE_TXN_V3_WITH_PROOF = { type: RPC.ETransactionType.INVOKE, sender_address: invocation.contractAddress, calldata: CallData.toHex(invocation.calldata), ...details, + proof_facts: invocation.proofFacts?.map((it) => toHex(it)) ?? [], }; return btx as any; // This 'as any' is internal to the generic function - the external API is type-safe } diff --git a/src/channel/rpc_0_9_0.ts b/src/channel/rpc_0_9_0.ts index 6e61a721c..e3c1fd12b 100644 --- a/src/channel/rpc_0_9_0.ts +++ b/src/channel/rpc_0_9_0.ts @@ -25,6 +25,7 @@ import { } from '../types'; import assert from '../utils/assert'; import { ETransactionType, JRPC, RPCSPEC09 as RPC } from '../types/api'; +import type { INVOKE_TXN_V3_WITH_PROOF_09 } from '../provider/types/spec.type'; import { BatchClient } from '../utils/batch'; import { CallData } from '../utils/calldata'; import { isSierra } from '../utils/contract'; @@ -773,11 +774,12 @@ export class RpcChannel { }; if (invocation.type === ETransactionType.INVOKE) { - const btx: RPC.INVOKE_TXN_V3 = { + const btx: INVOKE_TXN_V3_WITH_PROOF_09 = { type: RPC.ETransactionType.INVOKE, sender_address: invocation.contractAddress, calldata: CallData.toHex(invocation.calldata), ...details, + proof_facts: invocation.proofFacts?.map((it) => toHex(it)) ?? [], }; return btx as any; // This 'as any' is internal to the generic function - the external API is type-safe } diff --git a/src/provider/types/spec.type.ts b/src/provider/types/spec.type.ts index c09314770..c61468545 100644 --- a/src/provider/types/spec.type.ts +++ b/src/provider/types/spec.type.ts @@ -223,3 +223,9 @@ export const { ETransactionExecutionStatus: TransactionExecutionStatus } = RPCSP export type BlockTag = RPCSPEC09.EBlockTag; export const { EBlockTag: BlockTag } = RPCSPEC09; + +// Extended invoke types with proof_facts support (remove once upstream types-js adds the field) +export type INVOKE_TXN_V3_WITH_PROOF = RPCSPEC010.INVOKE_TXN_V3 & { proof_facts?: FELT[] }; +export type INVOKE_TXN_V3_WITH_PROOF_09 = RPCSPEC09.INVOKE_TXN_V3 & { + proof_facts?: RPCSPEC09.FELT[]; +}; diff --git a/src/types/lib/index.ts b/src/types/lib/index.ts index 166de9022..2cb7c6a36 100644 --- a/src/types/lib/index.ts +++ b/src/types/lib/index.ts @@ -198,6 +198,7 @@ export type V3TransactionDetails = { accountDeploymentData: BigNumberish[]; nonceDataAvailabilityMode: EDataAvailabilityMode; feeDataAvailabilityMode: EDataAvailabilityMode; + proofFacts: BigNumberish[]; }; /** diff --git a/src/utils/stark/index.ts b/src/utils/stark/index.ts index 1d76beb7b..22366e2bd 100644 --- a/src/utils/stark/index.ts +++ b/src/utils/stark/index.ts @@ -44,6 +44,7 @@ type V3Details = Required< | 'nonceDataAvailabilityMode' | 'feeDataAvailabilityMode' | 'resourceBounds' + | 'proofFacts' > >; @@ -422,6 +423,7 @@ export function v3Details(details: UniversalDetails): V3Details { nonceDataAvailabilityMode: details.nonceDataAvailabilityMode || EDataAvailabilityMode.L1, feeDataAvailabilityMode: details.feeDataAvailabilityMode || EDataAvailabilityMode.L1, resourceBounds: details.resourceBounds ?? zeroResourceBounds(), + proofFacts: details.proofFacts || [], }; } From e982110d3a3c8e7a8fb3977e6564ee58be154f82 Mon Sep 17 00:00:00 2001 From: Michael Zaikin Date: Thu, 12 Feb 2026 17:34:51 +0000 Subject: [PATCH 2/4] fix: include proofFacts in invoke v3 transaction hash computation --- __tests__/utils/transactionHash.test.ts | 49 ++++++++++++++++++++++++- src/channel/rpc_0_10_0.ts | 3 +- src/channel/rpc_0_9_0.ts | 3 +- src/utils/hash/transactionHash/index.ts | 4 +- src/utils/hash/transactionHash/v3.ts | 12 +++++- 5 files changed, 65 insertions(+), 6 deletions(-) diff --git a/__tests__/utils/transactionHash.test.ts b/__tests__/utils/transactionHash.test.ts index b81efd5be..ca2c9b1bf 100644 --- a/__tests__/utils/transactionHash.test.ts +++ b/__tests__/utils/transactionHash.test.ts @@ -1,4 +1,4 @@ -import { constants, v2hash } from '../../src'; +import { constants, hash, v2hash } from '../../src'; describe('TxV2 Hash Tests', () => { describe('calculateTransactionHashCommon()', () => { @@ -17,6 +17,53 @@ describe('TxV2 Hash Tests', () => { }); }); +describe('TxV3 Invoke proofFacts Hash Tests', () => { + const commonParams = { + senderAddress: '0x12fd538', + version: '0x3' as const, + compiledCalldata: ['0x11', '0x26'] as string[], + chainId: constants.StarknetChainId.SN_SEPOLIA, + nonce: 9, + accountDeploymentData: [] as string[], + nonceDataAvailabilityMode: 0, + feeDataAvailabilityMode: 0, + resourceBounds: { + l2_gas: { max_amount: 0n, max_price_per_unit: 0n }, + l1_gas: { max_amount: 0x7c9n, max_price_per_unit: 1n }, + l1_data_gas: { max_amount: 0n, max_price_per_unit: 0n }, + }, + tip: 0, + paymasterData: [] as string[], + }; + + test('empty proofFacts produces the same hash as omitted proofFacts', () => { + const hashWithout = hash.calculateInvokeTransactionHash({ ...commonParams }); + const hashEmpty = hash.calculateInvokeTransactionHash({ ...commonParams, proofFacts: [] }); + expect(hashEmpty).toBe(hashWithout); + }); + + test('non-empty proofFacts changes the transaction hash', () => { + const hashWithout = hash.calculateInvokeTransactionHash({ ...commonParams }); + const hashWithPF = hash.calculateInvokeTransactionHash({ + ...commonParams, + proofFacts: ['0x1', '0x2'], + }); + expect(hashWithPF).not.toBe(hashWithout); + }); + + test('proofFacts order matters for the hash', () => { + const hash1 = hash.calculateInvokeTransactionHash({ + ...commonParams, + proofFacts: ['0x1', '0x2'], + }); + const hash2 = hash.calculateInvokeTransactionHash({ + ...commonParams, + proofFacts: ['0x2', '0x1'], + }); + expect(hash1).not.toBe(hash2); + }); +}); + // TODO: create new tests with rpc0.8+ v3 tx /* describe('TxV3Old Hash Tests', () => { test('DaMode', () => { diff --git a/src/channel/rpc_0_10_0.ts b/src/channel/rpc_0_10_0.ts index 784c85bd2..50cf5f757 100644 --- a/src/channel/rpc_0_10_0.ts +++ b/src/channel/rpc_0_10_0.ts @@ -772,12 +772,13 @@ export class RpcChannel { }; if (invocation.type === ETransactionType.INVOKE) { + const proofFacts = invocation.proofFacts?.map((it) => toHex(it)) ?? []; const btx: INVOKE_TXN_V3_WITH_PROOF = { type: RPC.ETransactionType.INVOKE, sender_address: invocation.contractAddress, calldata: CallData.toHex(invocation.calldata), ...details, - proof_facts: invocation.proofFacts?.map((it) => toHex(it)) ?? [], + ...(proofFacts.length > 0 && { proof_facts: proofFacts }), }; return btx as any; // This 'as any' is internal to the generic function - the external API is type-safe } diff --git a/src/channel/rpc_0_9_0.ts b/src/channel/rpc_0_9_0.ts index e3c1fd12b..33554d49e 100644 --- a/src/channel/rpc_0_9_0.ts +++ b/src/channel/rpc_0_9_0.ts @@ -774,12 +774,13 @@ export class RpcChannel { }; if (invocation.type === ETransactionType.INVOKE) { + const proofFacts = invocation.proofFacts?.map((it) => toHex(it)) ?? []; const btx: INVOKE_TXN_V3_WITH_PROOF_09 = { type: RPC.ETransactionType.INVOKE, sender_address: invocation.contractAddress, calldata: CallData.toHex(invocation.calldata), ...details, - proof_facts: invocation.proofFacts?.map((it) => toHex(it)) ?? [], + ...(proofFacts.length > 0 && { proof_facts: proofFacts }), }; return btx as any; // This 'as any' is internal to the generic function - the external API is type-safe } diff --git a/src/utils/hash/transactionHash/index.ts b/src/utils/hash/transactionHash/index.ts index 76ce13ad4..eaf8e4d91 100644 --- a/src/utils/hash/transactionHash/index.ts +++ b/src/utils/hash/transactionHash/index.ts @@ -35,6 +35,7 @@ type CalcV3InvokeTxHashArgs = { resourceBounds: ResourceBoundsBN; tip: BigNumberish; paymasterData: BigNumberish[]; + proofFacts?: BigNumberish[]; }; type CalcInvokeTxHashArgs = CalcV3InvokeTxHashArgs; @@ -52,7 +53,8 @@ export function calculateInvokeTransactionHash(args: CalcInvokeTxHashArgs) { args.feeDataAvailabilityMode, args.resourceBounds, args.tip, - args.paymasterData + args.paymasterData, + args.proofFacts ?? [] ); } diff --git a/src/utils/hash/transactionHash/v3.ts b/src/utils/hash/transactionHash/v3.ts index 425716287..3aee77314 100644 --- a/src/utils/hash/transactionHash/v3.ts +++ b/src/utils/hash/transactionHash/v3.ts @@ -182,8 +182,16 @@ export function calculateInvokeTransactionHash( feeDataAvailabilityMode: EDAMode, resourceBounds: ResourceBoundsBN, tip: BigNumberish, - paymasterData: BigNumberish[] + paymasterData: BigNumberish[], + proofFacts: BigNumberish[] = [] ): string { + const additionalData: BigNumberish[] = [ + poseidonHashMany(AToBI(accountDeploymentData)), + poseidonHashMany(AToBI(compiledCalldata)), + ]; + if (proofFacts.length > 0) { + additionalData.push(poseidonHashMany(AToBI(proofFacts))); + } return calculateTransactionHashCommon( TransactionHashPrefix.INVOKE, version, @@ -195,6 +203,6 @@ export function calculateInvokeTransactionHash( nonceDataAvailabilityMode, feeDataAvailabilityMode, resourceBounds, - [poseidonHashMany(AToBI(accountDeploymentData)), poseidonHashMany(AToBI(compiledCalldata))] + additionalData ); } From 5e79dfc77565a77051ef31ff8081c3d8ad79ae5a Mon Sep 17 00:00:00 2001 From: Michael Zaikin Date: Tue, 17 Feb 2026 11:29:42 +0000 Subject: [PATCH 3/4] add proof field to invoke transactions alongside proof_facts --- __tests__/utils/transactionHash.test.ts | 45 ++++++++++++++++++++++++- src/account/types/index.type.ts | 1 + src/channel/rpc_0_10_0.ts | 3 +- src/channel/rpc_0_9_0.ts | 3 +- src/provider/types/spec.type.ts | 8 +++-- src/types/lib/index.ts | 3 +- src/utils/hash/transactionHash/v3.ts | 11 ++++-- src/utils/stark/index.ts | 7 ++-- 8 files changed, 69 insertions(+), 12 deletions(-) diff --git a/__tests__/utils/transactionHash.test.ts b/__tests__/utils/transactionHash.test.ts index ca2c9b1bf..ddb7d6f0f 100644 --- a/__tests__/utils/transactionHash.test.ts +++ b/__tests__/utils/transactionHash.test.ts @@ -1,4 +1,4 @@ -import { constants, hash, v2hash } from '../../src'; +import { constants, hash, v2hash, v3hash } from '../../src'; describe('TxV2 Hash Tests', () => { describe('calculateTransactionHashCommon()', () => { @@ -64,6 +64,49 @@ describe('TxV3 Invoke proofFacts Hash Tests', () => { }); }); +describe('TxV3 hashFeeFieldV3B3 — blockifier test vectors', () => { + // Test vectors from blockifier APOLLO-PRE-PROOF-DEMO-11 + // crates/starknet_api/src/transaction_hash_test.rs::test_tip_resource_bounds_hash_vectors + + test('L1Gas variant (no l1_data_gas) matches blockifier', () => { + // ValidResourceBounds::L1Gas: hashes [tip, l1, l2] (3 elements) + // get_l2_bounds returns default (0,0) for L1Gas variant + const bounds = { + l1_gas: { max_amount: 0x1000n, max_price_per_unit: 0x2000n }, + l2_gas: { max_amount: 0n, max_price_per_unit: 0n }, + }; + const result = v3hash.hashFeeFieldV3B3(0, bounds); + expect(result.toString(16)).toBe( + '25630b34ab588bfc7763f0428f8ecb8fe9b1c149b82e0aee3ca10583eed2ebe' + ); + }); + + test('AllResources variant (with l1_data_gas) matches blockifier', () => { + // ValidResourceBounds::AllResources: hashes [tip, l1, l2, l1_data] (4 elements) + const bounds = { + l1_gas: { max_amount: 0x1000n, max_price_per_unit: 0x2000n }, + l2_gas: { max_amount: 0x3000n, max_price_per_unit: 0x4000n }, + l1_data_gas: { max_amount: 0x5000n, max_price_per_unit: 0x6000n }, + }; + const result = v3hash.hashFeeFieldV3B3(0, bounds); + expect(result.toString(16)).toBe( + '3d848944220686a0d567e2a3895f3651ad616d4eccb473a03a93150c40b5e13' + ); + }); + + test('AllResources with zero l1_data_gas matches blockifier', () => { + const bounds = { + l1_gas: { max_amount: 0x1000n, max_price_per_unit: 0x2000n }, + l2_gas: { max_amount: 0x3000n, max_price_per_unit: 0x4000n }, + l1_data_gas: { max_amount: 0n, max_price_per_unit: 0n }, + }; + const result = v3hash.hashFeeFieldV3B3(0, bounds); + expect(result.toString(16)).toBe( + '6916420cf10b91926a408900e0e8f5548bbd3baccb11b2e0ad1a58246b0ffe0' + ); + }); +}); + // TODO: create new tests with rpc0.8+ v3 tx /* describe('TxV3Old Hash Tests', () => { test('DaMode', () => { diff --git a/src/account/types/index.type.ts b/src/account/types/index.type.ts index ba991d984..d677a1263 100644 --- a/src/account/types/index.type.ts +++ b/src/account/types/index.type.ts @@ -72,6 +72,7 @@ export interface UniversalDetails { resourceBounds?: ResourceBoundsBN; // ignored on estimate skipValidate?: boolean; // ignored on non-estimate proofFacts?: BigNumberish[]; + proof?: string; } export interface PaymasterDetails { diff --git a/src/channel/rpc_0_10_0.ts b/src/channel/rpc_0_10_0.ts index 50cf5f757..57e4bb0de 100644 --- a/src/channel/rpc_0_10_0.ts +++ b/src/channel/rpc_0_10_0.ts @@ -778,7 +778,8 @@ export class RpcChannel { sender_address: invocation.contractAddress, calldata: CallData.toHex(invocation.calldata), ...details, - ...(proofFacts.length > 0 && { proof_facts: proofFacts }), + ...(invocation.proofFacts !== undefined && { proof_facts: proofFacts }), + ...(invocation.proof !== undefined && { proof: invocation.proof }), }; return btx as any; // This 'as any' is internal to the generic function - the external API is type-safe } diff --git a/src/channel/rpc_0_9_0.ts b/src/channel/rpc_0_9_0.ts index 33554d49e..9289ee54b 100644 --- a/src/channel/rpc_0_9_0.ts +++ b/src/channel/rpc_0_9_0.ts @@ -780,7 +780,8 @@ export class RpcChannel { sender_address: invocation.contractAddress, calldata: CallData.toHex(invocation.calldata), ...details, - ...(proofFacts.length > 0 && { proof_facts: proofFacts }), + ...(invocation.proofFacts !== undefined && { proof_facts: proofFacts }), + ...(invocation.proof !== undefined && { proof: invocation.proof }), }; return btx as any; // This 'as any' is internal to the generic function - the external API is type-safe } diff --git a/src/provider/types/spec.type.ts b/src/provider/types/spec.type.ts index c61468545..aaa7b73d5 100644 --- a/src/provider/types/spec.type.ts +++ b/src/provider/types/spec.type.ts @@ -224,8 +224,12 @@ export const { ETransactionExecutionStatus: TransactionExecutionStatus } = RPCSP export type BlockTag = RPCSPEC09.EBlockTag; export const { EBlockTag: BlockTag } = RPCSPEC09; -// Extended invoke types with proof_facts support (remove once upstream types-js adds the field) -export type INVOKE_TXN_V3_WITH_PROOF = RPCSPEC010.INVOKE_TXN_V3 & { proof_facts?: FELT[] }; +// Extended invoke types with proof_facts/proof support (remove once upstream types-js adds the fields) +export type INVOKE_TXN_V3_WITH_PROOF = RPCSPEC010.INVOKE_TXN_V3 & { + proof_facts?: FELT[]; + proof?: string; +}; export type INVOKE_TXN_V3_WITH_PROOF_09 = RPCSPEC09.INVOKE_TXN_V3 & { proof_facts?: RPCSPEC09.FELT[]; + proof?: string; }; diff --git a/src/types/lib/index.ts b/src/types/lib/index.ts index 2cb7c6a36..27b78e9a5 100644 --- a/src/types/lib/index.ts +++ b/src/types/lib/index.ts @@ -198,7 +198,8 @@ export type V3TransactionDetails = { accountDeploymentData: BigNumberish[]; nonceDataAvailabilityMode: EDataAvailabilityMode; feeDataAvailabilityMode: EDataAvailabilityMode; - proofFacts: BigNumberish[]; + proofFacts?: BigNumberish[]; + proof?: string; }; /** diff --git a/src/utils/hash/transactionHash/v3.ts b/src/utils/hash/transactionHash/v3.ts index 3aee77314..ba9a646ab 100644 --- a/src/utils/hash/transactionHash/v3.ts +++ b/src/utils/hash/transactionHash/v3.ts @@ -63,13 +63,18 @@ export function encodeDataResourceBoundsL1(bounds: ResourceBoundsBN): bigint { } /** - * hash tip and resource bounds (3 bounds params) V3 RPC 0.8 + * hash tip and resource bounds V3 RPC 0.8+ + * Includes l1_data_gas only when present in bounds (AllResources variant), + * matching the blockifier's ValidResourceBounds::L1Gas vs AllResources logic. */ export function hashFeeFieldV3B3(tip: BigNumberish, bounds: ResourceBoundsBN) { const L1Bound = encodeResourceBoundsL1(bounds); const L2Bound = encodeResourceBoundsL2(bounds); - const L1Data = encodeDataResourceBoundsL1(bounds); - return poseidonHashMany([BigInt(tip), L1Bound, L2Bound, L1Data]); + const elements: bigint[] = [BigInt(tip), L1Bound, L2Bound]; + if ('l1_data_gas' in bounds) { + elements.push(encodeDataResourceBoundsL1(bounds)); + } + return poseidonHashMany(elements); } export function calculateTransactionHashCommon( diff --git a/src/utils/stark/index.ts b/src/utils/stark/index.ts index 22366e2bd..0969c7663 100644 --- a/src/utils/stark/index.ts +++ b/src/utils/stark/index.ts @@ -44,9 +44,9 @@ type V3Details = Required< | 'nonceDataAvailabilityMode' | 'feeDataAvailabilityMode' | 'resourceBounds' - | 'proofFacts' > ->; +> & + Pick; /** * Compress compiled Cairo 0 program @@ -423,7 +423,8 @@ export function v3Details(details: UniversalDetails): V3Details { nonceDataAvailabilityMode: details.nonceDataAvailabilityMode || EDataAvailabilityMode.L1, feeDataAvailabilityMode: details.feeDataAvailabilityMode || EDataAvailabilityMode.L1, resourceBounds: details.resourceBounds ?? zeroResourceBounds(), - proofFacts: details.proofFacts || [], + ...(details.proofFacts !== undefined && { proofFacts: details.proofFacts }), + ...(details.proof !== undefined && { proof: details.proof }), }; } From d9e406d60355ab728ded235b4b88d7c960543968 Mon Sep 17 00:00:00 2001 From: Michael Zaikin Date: Mon, 23 Mar 2026 16:24:14 +0000 Subject: [PATCH 4/4] fix: exclude proof blob from fee estimation to reduce payload size --- src/account/default.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/account/default.ts b/src/account/default.ts index 300a0069c..15171b36e 100644 --- a/src/account/default.ts +++ b/src/account/default.ts @@ -305,7 +305,10 @@ export class Account extends Provider implements AccountInterface { const { resourceBounds: providedResourceBounds } = transactionsDetail; let resourceBounds = providedResourceBounds; if (!resourceBounds) { - const estimateResponse = await this.estimateInvokeFee(calls, detailsWithTip); + const estimateResponse = await this.estimateInvokeFee(calls, { + ...detailsWithTip, + proof: undefined, + }); resourceBounds = estimateResponse.resourceBounds; }