feat: transaction scanning patch 1 - refactor transaction object and add get transaction(s) network utils#87
Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors transaction deserialization into the Transaction model (via factory methods) and adds Horizon-backed transaction fetch/scan utilities to NetworkService, supporting transaction scanning and richer on-chain metadata (e.g., fee_charged).
Changes:
- Moved XDR deserialization from
TransactionBuilderintoTransaction(fromXdr,fromHorizon,fromRaw) and addedfeeCharged+id. - Added
NetworkService.getTransactionandNetworkService.getTransactions(cursor pagination, bounded scanning). - Updated keyring signing flow and tests to use
Transaction.fromXdr, plus new Horizon test fixtures/constants.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/snap/src/services/transaction/TransactionService.ts | Uses Transaction.fromXdr for swap XDR validation flow. |
| packages/snap/src/services/transaction/TransactionBuilder.ts | Removes deserialize method (deserialization moved to Transaction). |
| packages/snap/src/services/transaction/TransactionBuilder.test.ts | Removes tests for the removed deserialize method. |
| packages/snap/src/services/transaction/Transaction.ts | Adds factory constructors, feeCharged, and id accessors. |
| packages/snap/src/services/transaction/Transaction.test.ts | Adds tests for fromXdr, fromHorizon, and invalid XDR behavior. |
| packages/snap/src/services/transaction/exceptions.ts | Introduces TransactionDeserializationException. |
| packages/snap/src/services/transaction/mocks/transaction.fixtures.ts | Adds Horizon transaction/page fixtures for tests. |
| packages/snap/src/services/network/NetworkService.ts | Adds getTransaction/getTransactions and maps Horizon records to Transaction. |
| packages/snap/src/services/network/NetworkService.test.ts | Adds test coverage for new Horizon transaction fetch/scan utilities. |
| packages/snap/src/handlers/keyring/signTransaction.ts | Switches signing flow deserialization to Transaction.fromXdr. |
| packages/snap/src/handlers/keyring/signTransaction.test.ts | Updates tests to align with new deserialization path. |
| packages/snap/src/handlers/keyring/exceptions.ts | Maps TransactionDeserializationException to SEP-43 InvalidRequest. |
| packages/snap/src/context.ts | Removes transactionBuilder injection into SignTransactionHandler. |
| packages/snap/src/constants.ts | Adds scan/page-size constants for Horizon transaction pagination. |
| packages/snap/src/api/xdr.ts | Uses Transaction.fromXdr for op-type inspection; adjusts networkId hashing logic. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const initialTransactionsResponse = await client | ||
| .transactions() | ||
| .forAccount(accountAddress) | ||
| .order(order) | ||
| .cursor(lastScanToken) | ||
| .limit(pageSize) | ||
| .call(); |
There was a problem hiding this comment.
horizon will not reject if cursor= empty
e.g https://horizon.stellar.org/accounts/GA7UCNSASSOPQYTRGJ2NC7TDBSXHMWK6JHS7AO6X2ZQAIQSTB5ELNFSO/transactions?cursor=&limit=200&order=asc
where you find this incorrect information
| let maxScanRemaining = maxScan; | ||
| let nextScanToken: string = lastScanToken; | ||
|
|
||
| try { |
There was a problem hiding this comment.
will use default page = 1 , instead of return empty
| includeSelfTransactionsOnly: boolean, | ||
| ): Transaction[] { | ||
| const result: Transaction[] = []; | ||
|
|
||
| for (const transaction of transactions) { | ||
| if ( | ||
| (includeSelfTransactionsOnly && | ||
| transaction.source_account === accountAddress) || | ||
| !includeSelfTransactionsOnly |
There was a problem hiding this comment.
This filters self transactions only by Horizon source_account. That can skip valid wallet transactions where the wallet is the fee source or an operation source. Since the scan cursor still advances, those skipped records won’t be picked up later.
I’d deserialize first, then filter with transaction.isInvokedByAccount(accountAddress), or include fee_account in the check
There was a problem hiding this comment.
im not sure yet
for fee bump txn
the txn can be not belong to me, should i show them, i think we didnt do that for other network
for sponser transaction
the txn is made for others, but im the one who pay the fee and execute it, should i show it on my broad?
for now, i only consider if the txn is execute by me
let reconfirm this later with others, and create a ticket to follow up?
There was a problem hiding this comment.
Yes, i agree let's defer and open a follow-up ticket. Just let's reuse Transaction.isInvokedByAccount when we revisit so the scan matches the rest of the codebase
|
|
||
| // Non-empty page from `next()` should always expose a paging token on the last row. | ||
| const lastPageRecord = currentResponse.records.at(-1); | ||
| nextScanToken = lastPageRecord?.paging_token ?? ''; |
There was a problem hiding this comment.
We support order: 'desc', but nextScanToken is always set to the last record in the page. For descending scans, that token points to the oldest fetched transaction, so using it for the next scan can skip newer transactions.
If desc is only for loading older history, we should return a separate cursor or document that clearly
There was a problem hiding this comment.
it is very confusing if the nextScanToken has different position by order
i would suggest we store the horizon data in the txn object
and remove the return of last scan token from the func response
and the caller decide which scan token to be store
so if there is a case that the caller need continue scanning from desc order, it works
so if there is a case that the caller need scanning once from desc order, and then scan in asc order base on the first record return from desc order, it works too
the caller decide, rather than the function decide
we should make the func more general
wdyt?
There was a problem hiding this comment.
Ok, sounds good just one thing: make sure the returned records keep their paging_token so the caller can actually choose where to resume from, and let's document which token to use for incremental (asc) vs backfill (desc)
Explanation
This PR refactors transaction deserialization into the Transaction model (via factory methods) and
adds Horizon-backed transaction fetch/scan utilities to NetworkService, s
upporting transaction scanning and richer on-chain metadata (e.g., fee_charged).
References
Checklist