Skip to content

perf(transactions): load the signing backends on demand - #119

Merged
yeboster merged 1 commit into
mainfrom
feat/optional-signing-backends
Aug 29, 2026
Merged

perf(transactions): load the signing backends on demand#119
yeboster merged 1 commit into
mainfrom
feat/optional-signing-backends

Conversation

@yeboster

@yeboster yeboster commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

The problem

ContractRepository statically imported ../transactions, so reading contract state pulled in the entire signing stack. Following the import graph in the bundled output:

dist/transactions/index.js
  partisia-blockchain-applications-crypto/lib/main/wallet
    bip39        291,059 bytes   (every wordlist: czech, japanese, korean, …)
    elliptic     114,569 bytes
    tiny-secp256k1, bn.js, hash.js

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 dynamic import(), 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.

serializeTransaction in transactions/helper.ts gets the same treatment, because actions/record.ts and actions/domain.ts import builderToBytesBe from that module and sit on the read path.

Nothing changes for consumers. All three signing packages stay regular dependencies, so npm install @metanames/sdk installs 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 against main:

bytes
entry chunk on main 1,192,931 (single chunk, nothing lazy)
entry chunk on this branch 342,939
−849,992 (−71.2%)

Total 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() to require() and a bundler cannot split on it. #118 is now on main, so the split is live; the numbers above were measured with both applied.

Notes

  • @ledgerhq/hw-transport and partisia-blockchain-applications-sdk were already used only as types. Their imports are now marked import type, so they carry no runtime weight — the previous import PartisiaSdk from … in transactions/index.ts was a value import of a type-only binding.
  • bip32-path stays statically imported. It is a few kilobytes, only reachable from the Ledger chunk, and bip32Buffer is a synchronous exported function that would have to become async to defer it — a breaking API change for no measurable gain.
  • partisia-blockchain-applications-rpc stays on the read path. It brings axios + mime-db (~180 KB), which is worth a separate look.

Test status

npx tsc --noEmit clean, eslint clean, yarn build clean, 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

@github-actions

Copy link
Copy Markdown

Total Coverage: 89.71%

Coverage Report
File Branch Funcs Lines Uncovered Lines
src
   actions.ts 100% 100% 100%
   index.ts 100% 100% 100%
   interface.ts 100% 100% 100%
   meta-names-sdk.ts 75% 75% 95.65% 22, 44, 45
   partisia-name-system.ts 58% 87.50% 84.07% 106, 114, 12, 139, 152, 160, 164, 169, 170, 171, 178, 180, 188, 189, 19, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 22, 220, 221, 222, 29, 33, 42, 49, 54, 55, 7, 78, 82
   version.ts 100% 100% 100%
src/actions
   domain.ts 64.29% 100% 100% 35, 44, 56, 71, 74
   record.ts 77.78% 100% 100% 14, 48
src/models
   domain.ts 100% 83.33% 95.95% 45, 46, 47
   index.ts 100% 100% 100%
src/models/helpers
   domain.ts 100% 100% 100%
src/providers
   config.ts 66.67% 100% 98.11% 40, 49, 50
   index.ts 100% 100% 100%
   secrets.ts 45.45% 62.50% 78.57% 19, 23, 24, 25, 26, 27, 29, 30, 44, 50, 51, 52, 53, 56, 57, 58, 59, 62, 63, 64, 65
src/providers/config
   mainnet.ts 100% 100% 100%
   testnet.ts 100% 100% 100%
src/repositories
   contract-repository.ts 76.19% 100% 95.67% 102, 113, 127, 128, 129, 130, 132, 133, 134, 135, 137, 138, 139, 140, 142, 143, 152, 168, 190, 206, 33, 45, 46, 56, 62
   domain-repository.ts 60% 100% 100% 102, 113, 117, 118, 133, 142, 150, 176, 220, 225, 240, 256, 262, 269, 34, 53, 54, 96, 98, 99
   index.ts 100% 75% 100%
   record-repository.ts 71.43% 100% 100% 24, 34, 50, 60
src/repositories/contracts
   meta-names-contract-repository.ts 84.62% 88.89% 88.66% 58, 59, 60, 61, 62, 63, 64, 65, 77, 78, 79, 80, 90
src/repositories/helpers
   avl-client.ts 80% 77.78% 61.62% 16, 22, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 95
   client.ts 88.89% 100% 100% 31
   contract.ts 75% 100% 92.45% 41, 42, 43, 44, 45, 50
src/transactions
   helper.ts 80% 85.71% 86% 24, 25, 26, 27, 28, 29, 63, 64, 65, 66, 67, 68, 69, 90, 91, 92
   index.ts 80% 50% 39.46% 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 172, 21, 22, 23, 24, 25, 26, 27, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99
src/validators
   base-validator.ts 100% 100% 100%
   domain-validator.ts 87.50% 100% 100% 29, 30, 31
   index.ts 100% 100% 100%
   record-validator.ts 100% 100% 100%
src/validators/records
   default-validator.ts 100% 100% 100%
   discord-validator.ts 100% 100% 100%
   email-validator.ts 100% 100% 100%
   main-validator.ts 100% 100% 100%
   price-validator.ts 100% 100% 100%
   regex-validator.ts 100% 100% 100%
   twitter-validator.ts 100% 100% 100%
   uri-validator.ts 100% 100% 100%
   wallet-validator.ts 100% 100% 100%
test
test/helpers
   config.ts 0% 100% 100% 7
   helper.ts 100% 60% 68% 25, 26, 27, 28, 29, 30, 31, 32, 35, 36, 37, 38, 39, 40, 41, 42
   index.ts 100% 100% 100%

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
yeboster force-pushed the feat/optional-signing-backends branch from 7504c10 to ae164d7 Compare August 28, 2026 21:53
@github-actions

Copy link
Copy Markdown

Total Coverage: 90.53%

Coverage Report
File Branch Funcs Lines Uncovered Lines
src
   actions.ts 100% 100% 100%
   index.ts 100% 100% 100%
   interface.ts 100% 100% 100%
   meta-names-sdk.ts 75% 75% 95.65% 22, 44, 45
   partisia-name-system.ts 58% 87.50% 84.07% 106, 114, 12, 139, 152, 160, 164, 169, 170, 171, 178, 180, 188, 189, 19, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 22, 220, 221, 222, 29, 33, 42, 49, 54, 55, 7, 78, 82
   version.ts 100% 100% 100%
src/actions
   domain.ts 64.29% 100% 100% 35, 44, 56, 71, 74
   record.ts 77.78% 100% 100% 14, 48
src/models
   domain.ts 100% 83.33% 95.95% 45, 46, 47
   index.ts 100% 100% 100%
src/models/helpers
   domain.ts 100% 100% 100%
src/providers
   config.ts 66.67% 100% 98.11% 40, 49, 50
   index.ts 100% 100% 100%
   secrets.ts 45.45% 62.50% 78.57% 19, 23, 24, 25, 26, 27, 29, 30, 44, 50, 51, 52, 53, 56, 57, 58, 59, 62, 63, 64, 65
src/providers/config
   mainnet.ts 100% 100% 100%
   testnet.ts 100% 100% 100%
src/repositories
   contract-repository.ts 76.19% 100% 95.67% 102, 113, 127, 128, 129, 130, 132, 133, 134, 135, 137, 138, 139, 140, 142, 143, 152, 168, 190, 206, 33, 45, 46, 56, 62
   domain-repository.ts 60% 100% 100% 102, 113, 117, 118, 133, 142, 150, 176, 220, 225, 240, 256, 262, 269, 34, 53, 54, 96, 98, 99
   index.ts 100% 75% 100%
   record-repository.ts 71.43% 100% 100% 24, 34, 50, 60
src/repositories/contracts
   meta-names-contract-repository.ts 84.62% 88.89% 86.14% 57, 58, 59, 60, 61, 62, 63, 64, 78, 79, 80, 81, 82, 83, 84, 94
src/repositories/helpers
   avl-client.ts 80% 77.78% 61.62% 16, 22, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 95
   client.ts 90% 100% 100% 44
   contract.ts 75% 100% 92.45% 41, 42, 43, 44, 45, 50
src/transactions
   helper.ts 78.57% 85.71% 93.48% 55, 56, 57, 58, 59, 60, 61, 83, 83
   index.ts 55.56% 42.86% 41.58% 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 166, 171, 184, 185, 186, 188, 188, 189, 190, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99
src/validators
   base-validator.ts 100% 100% 100%
   domain-validator.ts 87.50% 100% 100% 29, 30, 31
   index.ts 100% 100% 100%
   record-validator.ts 100% 100% 100%
src/validators/idna
   index.ts 81.54% 100% 95.93% 108, 110, 110, 111, 118, 119, 120, 121, 122, 123, 124, 125, 136, 138, 139, 144, 151, 155, 168, 210, 46
   table.ts 100% 100% 100%
src/validators/records
   default-validator.ts 100% 100% 100%
   discord-validator.ts 100% 100% 100%
   email-validator.ts 100% 100% 100%
   main-validator.ts 100% 100% 100%
   price-validator.ts 100% 100% 100%
   regex-validator.ts 100% 100% 100%
   twitter-validator.ts 100% 100% 100%
   uri-validator.ts 100% 100% 100%
   wallet-validator.ts 100% 100% 100%
test
test/helpers
   config.ts 0% 100% 100% 7
   helper.ts 100% 60% 68% 25, 26, 27, 28, 29, 30, 31, 32, 35, 36, 37, 38, 39, 40, 41, 42
   index.ts 100% 100% 100%

@yeboster yeboster changed the title feat!: load signing backends on demand, make them optional peers perf(transactions): load the signing backends on demand Aug 28, 2026
@yeboster
yeboster merged commit ab339f5 into main Aug 29, 2026
8 checks passed
@yeboster
yeboster deleted the feat/optional-signing-backends branch August 29, 2026 09:01
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant