perf(transactions): load the signing backends on demand - #119
Merged
Conversation
|
Total Coverage: 89.71% Coverage Report
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Every consumer downloaded the whole signing stack, including the ones that only read contract state. `partisia-blockchain-applications-crypto` pulls in bip39, elliptic and tiny-secp256k1; the Ledger client pulls in `bip32-path` and the `@ledgerhq` transport. None of it is reachable until a transaction is actually signed. The four `createTransactionFrom*` entry points are now imported dynamically from `ContractRepository.createTransaction`, and each one loads only what its own strategy needs: the crypto module, the wallet module, the Ledger client. `serializeTransaction` does the same, so `builderToBytesBe` -- imported from that module by the record and domain actions, which are on the read path -- no longer drags the crypto module in with it. All three packages stay regular dependencies: nothing changes about what a consumer installs, and there is no new failure mode for a missing package. The imports exist so a bundler can split them out of the entry chunk. Bundling `dist/esm/index.js` with esbuild (minified, esm, code splitting): before 1,192,931 bytes in a single chunk after 342,939 bytes in the entry chunk (-71.2%) The total across all chunks is unchanged; what changes is how much of it a read-only consumer has to download. Full suite is 269/269 passing. Claude-Session: https://claude.ai/code/session_01GceWCwGXu66D1xEBDxZWBb
yeboster
force-pushed
the
feat/optional-signing-backends
branch
from
August 28, 2026 21:53
7504c10 to
ae164d7
Compare
|
Total Coverage: 90.53% Coverage Report
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
yeboster
added a commit
that referenced
this pull request
Aug 29, 2026
`partisia-blockchain-applications-rpc` was last published 2024-03-18 and is
no longer maintained. It contributed 33 of the 45 advisories in the partisia
dependency subtrees -- axios (30), form-data (critical + high) and
follow-redirects -- for what are plain GET/POST/PUT calls against the reader
REST API.
Only six of its methods were ever used, and the SDK already talks to that same
API directly through `helpers/client.ts` and `AvlClient`. This adds
`helpers/sharded-client.ts` covering those six against the same endpoints:
deriveShardId int32be at offset 17 of the address, mod shards
getNonce GET {shard}/blockchain/account/{address}
fetchCoins POST {global}/blockchain/accountPlugin/global
getTransaction GET {shard}/blockchain/transaction/{hash}
broadcastTransaction PUT {shard}/blockchain/transaction
getTransactionEventTrace recursive walk over spawned events
`helpers/client.ts` gains `postRequest` and `putRequestOnce` siblings to
`getRequest`. All three go through the existing abort-on-timeout fetch wrapper,
so the new calls inherit the same 30s ceiling as the read path.
`getTransaction` with no shard queries every shard and takes the first hit, as
the rpc package did -- a transaction hash does not encode its shard, and the
event-trace walker depends on that behaviour.
Two behavioural notes:
- `getTransactionForce` looped forever waiting for finality. The replacement
polls 30 times at 1s and then throws, so a stuck transaction surfaces instead
of hanging the caller.
- Broadcasting uses `putRequestOnce`, which does not retry. `promiseRetry`
around a PUT would resubmit the transaction.
`IContractInfo` is replaced by a local `ContractInfo` interface; only `abi` and
`serializedContract` were ever read from it.
Production dependency graph 122 -> 107 packages, axios and mime-db among them.
Bundled with esbuild (--bundle --minify --format=esm --splitting), total bytes
across all chunks 1,192,467 -> 915,298 (-277,169, -23.2%); the 280 KB shared
chunk holding axios is gone. The entry chunk is unchanged at ~343 KB, since
after #119 the rpc client only reached the lazily loaded signing chunks.
21 suites / 269 tests pass against live testnet.
yeboster
added a commit
that referenced
this pull request
Aug 29, 2026
#120) `partisia-blockchain-applications-rpc` was last published 2024-03-18 and is no longer maintained. It contributed 33 of the 45 advisories in the partisia dependency subtrees -- axios (30), form-data (critical + high) and follow-redirects -- for what are plain GET/POST/PUT calls against the reader REST API. Only six of its methods were ever used, and the SDK already talks to that same API directly through `helpers/client.ts` and `AvlClient`. This adds `helpers/sharded-client.ts` covering those six against the same endpoints: deriveShardId int32be at offset 17 of the address, mod shards getNonce GET {shard}/blockchain/account/{address} fetchCoins POST {global}/blockchain/accountPlugin/global getTransaction GET {shard}/blockchain/transaction/{hash} broadcastTransaction PUT {shard}/blockchain/transaction getTransactionEventTrace recursive walk over spawned events `helpers/client.ts` gains `postRequest` and `putRequestOnce` siblings to `getRequest`. All three go through the existing abort-on-timeout fetch wrapper, so the new calls inherit the same 30s ceiling as the read path. `getTransaction` with no shard queries every shard and takes the first hit, as the rpc package did -- a transaction hash does not encode its shard, and the event-trace walker depends on that behaviour. Two behavioural notes: - `getTransactionForce` looped forever waiting for finality. The replacement polls 30 times at 1s and then throws, so a stuck transaction surfaces instead of hanging the caller. - Broadcasting uses `putRequestOnce`, which does not retry. `promiseRetry` around a PUT would resubmit the transaction. `IContractInfo` is replaced by a local `ContractInfo` interface; only `abi` and `serializedContract` were ever read from it. Production dependency graph 122 -> 107 packages, axios and mime-db among them. Bundled with esbuild (--bundle --minify --format=esm --splitting), total bytes across all chunks 1,192,467 -> 915,298 (-277,169, -23.2%); the 280 KB shared chunk holding axios is gone. The entry chunk is unchanged at ~343 KB, since after #119 the rpc client only reached the lazily loaded signing chunks. 21 suites / 269 tests pass against live testnet.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
ContractRepositorystatically imported../transactions, so reading contract state pulled in the entire signing stack. Following the import graph in the bundled output:A consumer that only looks up domains — the dominant case in the app — shipped all of it.
The change
The four
createTransactionFrom*calls are loaded at their call sites with dynamicimport(), and each loads only the backend its own strategy needs. The MetaMask path no longer pulls in the Ledger transport; the Ledger path no longer pulls in the BIP-39 wordlists.serializeTransactionintransactions/helper.tsgets the same treatment, becauseactions/record.tsandactions/domain.tsimportbuilderToBytesBefrom that module and sit on the read path.Nothing changes for consumers. All three signing packages stay regular
dependencies, sonpm install @metanames/sdkinstalls everything it always did and no code has to change. The only difference is that a bundler can now split the signing backends out of the entry chunk, so a read-only consumer never downloads them.Impact
esbuild
--bundle --minify --format=esm --splitting --platform=node, measured againstmain:mainTotal bytes across all chunks are unchanged — the code is moved, not removed. The remainder splits into strategy-specific chunks; the largest is the wallet crypto that only the private-key path loads.
Requires the ESM build (#118, merged)
With CommonJS, TypeScript downlevels
import()torequire()and a bundler cannot split on it. #118 is now onmain, so the split is live; the numbers above were measured with both applied.Notes
@ledgerhq/hw-transportandpartisia-blockchain-applications-sdkwere already used only as types. Their imports are now markedimport type, so they carry no runtime weight — the previousimport PartisiaSdk from …intransactions/index.tswas a value import of a type-only binding.bip32-pathstays statically imported. It is a few kilobytes, only reachable from the Ledger chunk, andbip32Bufferis a synchronous exported function that would have to become async to defer it — a breaking API change for no measurable gain.partisia-blockchain-applications-rpcstays on the read path. It brings axios + mime-db (~180 KB), which is worth a separate look.Test status
npx tsc --noEmitclean,eslintclean,yarn buildclean,npx jest -i— 21 suites / 269 tests, all passing, including the live-testnet signing tests that exercise every dynamic import end to end.https://claude.ai/code/session_01GceWCwGXu66D1xEBDxZWBb