diff --git a/.changeset/bright-oranges-pick.md b/.changeset/bright-oranges-pick.md new file mode 100644 index 000000000..39273bcce --- /dev/null +++ b/.changeset/bright-oranges-pick.md @@ -0,0 +1,6 @@ +--- +"@onflow/transport-http": minor +"@onflow/sdk": minor +--- + +Add optional `blockId` field to `GetTransactionResult`. This is used to disambiguate system transactions sharing IDs between blocks and is not applicable to most developers. diff --git a/package-lock.json b/package-lock.json index 73f954747..4b9804f2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32794,16 +32794,16 @@ }, "packages/fcl": { "name": "@onflow/fcl", - "version": "1.20.1", + "version": "1.20.2", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", "@onflow/config": "1.6.1", - "@onflow/fcl-core": "1.21.1", - "@onflow/fcl-wc": "6.0.6", + "@onflow/fcl-core": "1.21.2", + "@onflow/fcl-wc": "6.0.7", "@onflow/interaction": "0.0.11", "@onflow/rlp": "1.2.4", - "@onflow/sdk": "1.10.1", + "@onflow/sdk": "1.10.2", "@onflow/types": "1.4.2", "@onflow/util-actor": "1.3.5", "@onflow/util-address": "1.2.4", @@ -32869,7 +32869,7 @@ }, "packages/fcl-core": { "name": "@onflow/fcl-core", - "version": "1.21.1", + "version": "1.21.2", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", @@ -32877,8 +32877,8 @@ "@onflow/config": "1.6.1", "@onflow/interaction": "0.0.11", "@onflow/rlp": "1.2.4", - "@onflow/sdk": "1.10.1", - "@onflow/transport-http": "1.13.1", + "@onflow/sdk": "1.10.2", + "@onflow/transport-http": "1.13.2", "@onflow/types": "1.4.2", "@onflow/util-actor": "1.3.5", "@onflow/util-address": "1.2.4", @@ -32938,14 +32938,14 @@ }, "packages/fcl-ethereum-provider": { "name": "@onflow/fcl-ethereum-provider", - "version": "0.0.8", + "version": "0.0.9", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", "@noble/hashes": "^1.7.1", - "@onflow/fcl-wc": "6.0.6", + "@onflow/fcl-wc": "6.0.7", "@onflow/rlp": "^1.2.4", "@walletconnect/ethereum-provider": "^2.20.2", "@walletconnect/jsonrpc-http-connection": "^1.0.8", @@ -32966,7 +32966,7 @@ "jest": "^29.7.0" }, "peerDependencies": { - "@onflow/fcl": "1.20.1" + "@onflow/fcl": "1.20.2" } }, "packages/fcl-ethereum-provider/node_modules/@react-native-async-storage/async-storage": { @@ -33206,14 +33206,14 @@ }, "packages/fcl-rainbowkit-adapter": { "name": "@onflow/fcl-rainbowkit-adapter", - "version": "0.2.4", + "version": "0.2.5", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl-ethereum-provider": "0.0.8", - "@onflow/fcl-wagmi-adapter": "0.0.8", + "@onflow/fcl-ethereum-provider": "0.0.9", + "@onflow/fcl-wagmi-adapter": "0.0.9", "@onflow/rlp": "^1.2.4", "@wagmi/core": "^2.16.3", "mipd": "^0.0.7", @@ -33234,7 +33234,7 @@ "jest": "^29.7.0" }, "peerDependencies": { - "@onflow/fcl": "1.20.1", + "@onflow/fcl": "1.20.2", "@rainbow-me/rainbowkit": "^2.2.3", "react": "17.x || 18.x || 19.x" } @@ -33251,15 +33251,15 @@ }, "packages/fcl-react-native": { "name": "@onflow/fcl-react-native", - "version": "1.13.1", + "version": "1.13.2", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", "@onflow/config": "1.6.1", - "@onflow/fcl-core": "1.21.1", + "@onflow/fcl-core": "1.21.2", "@onflow/interaction": "0.0.11", "@onflow/rlp": "1.2.4", - "@onflow/sdk": "1.10.1", + "@onflow/sdk": "1.10.2", "@onflow/types": "1.4.2", "@onflow/util-actor": "1.3.5", "@onflow/util-address": "1.2.4", @@ -33310,13 +33310,13 @@ }, "packages/fcl-wagmi-adapter": { "name": "@onflow/fcl-wagmi-adapter", - "version": "0.0.8", + "version": "0.0.9", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl-ethereum-provider": "0.0.8", + "@onflow/fcl-ethereum-provider": "0.0.9", "@onflow/rlp": "^1.2.4", "viem": "^2.22.21" }, @@ -33332,13 +33332,13 @@ "jest": "^29.7.0" }, "peerDependencies": { - "@onflow/fcl": "1.20.1", + "@onflow/fcl": "1.20.2", "@wagmi/core": "^2.16.3" } }, "packages/fcl-wc": { "name": "@onflow/fcl-wc", - "version": "6.0.6", + "version": "6.0.7", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", @@ -33365,7 +33365,7 @@ "jest-preset-preact": "^4.1.1" }, "peerDependencies": { - "@onflow/fcl-core": "1.21.1" + "@onflow/fcl-core": "1.21.2" } }, "packages/fcl-wc/node_modules/@react-native-async-storage/async-storage": { @@ -33654,7 +33654,7 @@ }, "packages/react-sdk": { "name": "@onflow/react-sdk", - "version": "0.10.2", + "version": "0.10.3", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", @@ -33711,13 +33711,13 @@ }, "packages/sdk": { "name": "@onflow/sdk", - "version": "1.10.1", + "version": "1.10.2", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", "@onflow/config": "1.6.1", "@onflow/rlp": "1.2.4", - "@onflow/transport-http": "1.13.1", + "@onflow/transport-http": "1.13.2", "@onflow/typedefs": "1.7.1", "@onflow/types": "1.4.2", "@onflow/util-actor": "1.3.5", @@ -33767,13 +33767,13 @@ }, "devDependencies": { "@onflow/fcl-bundle": "1.7.1", - "@onflow/sdk": "1.10.1", + "@onflow/sdk": "1.10.2", "jest": "^29.7.0" } }, "packages/transport-http": { "name": "@onflow/transport-http", - "version": "1.13.1", + "version": "1.13.2", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", @@ -33791,7 +33791,7 @@ "devDependencies": { "@onflow/fcl-bundle": "1.7.1", "@onflow/rlp": "1.2.4", - "@onflow/sdk": "1.10.1", + "@onflow/sdk": "1.10.2", "@onflow/types": "1.4.2", "jest": "^29.7.0", "jest-websocket-mock": "^2.5.0", diff --git a/packages/react-sdk/src/components/TransactionDialog.tsx b/packages/react-sdk/src/components/TransactionDialog.tsx index 219ec889a..e8a065ff2 100644 --- a/packages/react-sdk/src/components/TransactionDialog.tsx +++ b/packages/react-sdk/src/components/TransactionDialog.tsx @@ -9,6 +9,7 @@ import {useFlowTransactionStatus} from "../hooks/useFlowTransactionStatus" interface TransactionDialogProps { open: boolean onOpenChange: (open: boolean) => void + /** The transaction ID (256-bit hash as hex string) or scheduled transaction ID (UInt64 as decimal string) to monitor */ txId?: string onSuccess?: () => void pendingTitle?: string diff --git a/packages/react-sdk/src/components/TransactionLink.tsx b/packages/react-sdk/src/components/TransactionLink.tsx index 3005865c2..0d01b7f08 100644 --- a/packages/react-sdk/src/components/TransactionLink.tsx +++ b/packages/react-sdk/src/components/TransactionLink.tsx @@ -6,6 +6,7 @@ import {FlowNetwork} from "../core/types" import {ExternalLinkIcon} from "../icons/ExternalLink" interface TransactionLinkProps { + /** The transaction ID (256-bit hash as hex string) or scheduled transaction ID (UInt64 as decimal string) */ txId: string variant?: ButtonProps["variant"] } diff --git a/packages/react-sdk/src/hooks/useFlowTransaction.ts b/packages/react-sdk/src/hooks/useFlowTransaction.ts index 1711fb9a7..3f1cd1ad4 100644 --- a/packages/react-sdk/src/hooks/useFlowTransaction.ts +++ b/packages/react-sdk/src/hooks/useFlowTransaction.ts @@ -6,7 +6,7 @@ import {useFlowQueryClient} from "../provider/FlowQueryClient" import {useFlowClient} from "./useFlowClient" export interface UseFlowTransactionArgs { - /** Flow transaction ID to fetch */ + /** The transaction ID (256-bit hash as hex string) or scheduled transaction ID (UInt64 as decimal string) to fetch */ txId?: string /** React Query settings (staleTime, retry, enabled, select, etc.) */ query?: Omit< @@ -20,7 +20,7 @@ export interface UseFlowTransactionArgs { /** * Fetches a Flow transaction by ID. * - * @param args.txId - Flow transaction ID + * @param args.txId - The transaction ID (256-bit hash as hex string) or scheduled transaction ID (UInt64 as decimal string) * @param args.query - Optional React Query options * @returns {UseQueryResult} Transaction object or null */ diff --git a/packages/react-sdk/src/hooks/useFlowTransactionStatus.ts b/packages/react-sdk/src/hooks/useFlowTransactionStatus.ts index a9a08e5bc..65c72f24b 100644 --- a/packages/react-sdk/src/hooks/useFlowTransactionStatus.ts +++ b/packages/react-sdk/src/hooks/useFlowTransactionStatus.ts @@ -4,7 +4,7 @@ import {useFlowClient} from "./useFlowClient" import {TransactionError} from "@onflow/fcl" export interface UseFlowTransactionStatusArgs { - /** The Flow transaction ID to monitor */ + /** The transaction ID (256-bit hash as hex string) or scheduled transaction ID (UInt64 as decimal string) to monitor */ id?: string | null flowClient?: ReturnType } @@ -22,7 +22,7 @@ export interface UseFlowTransactionStatusResult { * @remarks * This hook was previously named `useFlowTransaction`. * - * @param args.id - The Flow transaction ID to watch + * @param args.id - The transaction ID (256-bit hash as hex string) or scheduled transaction ID (UInt64 as decimal string) to watch * @returns {UseFlowTransactionStatusResult} */ export function useFlowTransactionStatus({ diff --git a/packages/sdk/readme.md b/packages/sdk/readme.md index b34d3200e..81b8b7405 100644 --- a/packages/sdk/readme.md +++ b/packages/sdk/readme.md @@ -484,7 +484,10 @@ const txId: string = "9dda49b2f2b1b9bc12d5cabe09f8a8cb49828c9c449574c1f46f3b3a5e // Build the interaction const interaction = await sdk.build([ - sdk.getTransactionStatus(txId) + sdk.getTransactionStatus(txId), + // Optionally disambiguate system txns by providing a specific block id + // (uses HTTP query param ?block_id=... under the hood) + sdk.atBlockId("abc123blockid") ]) // Send the interaction to the Access Node diff --git a/packages/sdk/src/build/build-get-transaction-status.ts b/packages/sdk/src/build/build-get-transaction-status.ts index 9c17eb71e..7d4dfebaa 100644 --- a/packages/sdk/src/build/build-get-transaction-status.ts +++ b/packages/sdk/src/build/build-get-transaction-status.ts @@ -12,7 +12,7 @@ import { * * Consider using 'fcl.tx(id)' instead of calling this method directly for real-time transaction monitoring. * - * @param transactionId The id of the transaction to get the status of + * @param transactionId The transaction ID (256-bit hash as hex string) or scheduled transaction ID (UInt64 as decimal string) * @returns A function that processes an interaction object * * @example diff --git a/packages/sdk/src/build/build-get-transaction.ts b/packages/sdk/src/build/build-get-transaction.ts index cef671ca5..ed1778c6a 100644 --- a/packages/sdk/src/build/build-get-transaction.ts +++ b/packages/sdk/src/build/build-get-transaction.ts @@ -15,7 +15,7 @@ import { * * Consider using 'fcl.tx(id).onceExecuted()' instead of calling this method directly for real-time transaction monitoring. * - * @param transactionId The id of the transaction to get + * @param id The transaction ID (256-bit hash as hex string) or scheduled transaction ID (UInt64 as decimal string) * @returns A function that processes an interaction object * * @example diff --git a/packages/transport-http/src/send/send-get-transaction-status.js b/packages/transport-http/src/send/send-get-transaction-status.js index 3ae69c9dc..6f97e05f7 100644 --- a/packages/transport-http/src/send/send-get-transaction-status.js +++ b/packages/transport-http/src/send/send-get-transaction-status.js @@ -28,9 +28,16 @@ export async function sendGetTransactionStatus(ix, context = {}, opts = {}) { ix = await ix + const blockId = ix && ix.block && ix.block.id + const path = blockId + ? `/v1/transaction_results/${ix.transaction.id}?block_id=${encodeURIComponent( + blockId + )}` + : `/v1/transaction_results/${ix.transaction.id}` + const res = await httpRequest({ hostname: opts.node, - path: `/v1/transaction_results/${ix.transaction.id}`, + path, method: "GET", body: null, }) diff --git a/packages/transport-http/src/send/send-get-transaction-status.test.js b/packages/transport-http/src/send/send-get-transaction-status.test.js index 4e6a6e119..c1baf2027 100644 --- a/packages/transport-http/src/send/send-get-transaction-status.test.js +++ b/packages/transport-http/src/send/send-get-transaction-status.test.js @@ -77,4 +77,84 @@ describe("Get Transaction Status", () => { ], }) }) + + test("GetTransactionResult with decimal transaction id passes through", async () => { + const httpRequestMock = jest.fn() + + const returnedTransactionStatus = { + status: "Sealed", + status_code: 0, + error_message: "", + computation_used: "100", + block_id: "abc123", + events: [], + } + + httpRequestMock.mockReturnValue(returnedTransactionStatus) + + await sendGetTransactionStatus( + await resolve(await build([getTransactionStatus("12453151")])), + { + response: responseADT, + Buffer, + }, + { + httpRequest: httpRequestMock, + node: "localhost", + } + ) + + const valueSent = httpRequestMock.mock.calls[0][0] + expect(valueSent).toEqual({ + hostname: "localhost", + path: "/v1/transaction_results/12453151", + method: "GET", + body: null, + }) + }) + + test("GetTransactionResult with block_id query when atBlockId is provided", async () => { + const httpRequestMock = jest.fn() + + const returnedTransactionStatus = { + status: "Sealed", + status_code: 0, + error_message: "", + computation_used: "100", + block_id: "blockABC", + events: [], + } + + httpRequestMock.mockReturnValue(returnedTransactionStatus) + + const response = await sendGetTransactionStatus( + await resolve( + await build([ + getTransactionStatus("MyTxID"), + // set block id through existing builder + ix => ({...ix, block: {...ix.block, id: "blockABC"}}), + ]) + ), + { + response: responseADT, + Buffer, + }, + { + httpRequest: httpRequestMock, + node: "localhost", + } + ) + + expect(httpRequestMock.mock.calls.length).toEqual(1) + const valueSent = httpRequestMock.mock.calls[0][0] + + expect(valueSent).toEqual({ + hostname: "localhost", + path: "/v1/transaction_results/MyTxID?block_id=blockABC", + method: "GET", + body: null, + }) + + expect(response.transactionStatus.blockId).toBe("blockABC") + }) }) diff --git a/packages/transport-http/src/send/send-get-transaction.test.js b/packages/transport-http/src/send/send-get-transaction.test.js index 9bd873b02..82f70b568 100644 --- a/packages/transport-http/src/send/send-get-transaction.test.js +++ b/packages/transport-http/src/send/send-get-transaction.test.js @@ -73,4 +73,47 @@ describe("Get Transaction", () => { envelopeSignatures: [], }) }) + + test("GetTransaction with decimal transaction id passes through", async () => { + const httpRequestMock = jest.fn() + + const returnedTransaction = { + script: "Q2FkZW5jZSBDb2Rl", + arguments: [], + reference_block_id: "a1b2c3", + gas_limit: "123", + proposal_key: { + address: "1654653399040a61", + key_index: "1", + signer_index: "0", + sequence_number: "1", + }, + payer: "1654653399040a61", + authorizers: [], + payload_signatures: [], + envelope_signatures: [], + } + + httpRequestMock.mockReturnValue(returnedTransaction) + + await sendGetTransaction( + await resolve(await build([getTransaction("12453151")])), + { + response: responseADT, + Buffer, + }, + { + httpRequest: httpRequestMock, + node: "localhost", + } + ) + + const valueSent = httpRequestMock.mock.calls[0][0] + expect(valueSent).toEqual({ + hostname: "localhost", + path: "/v1/transactions/12453151", + method: "GET", + body: null, + }) + }) })