Skip to content

feat: add keychain support#13

Merged
AaronCQL merged 6 commits intojup-ag:mainfrom
amilz:feat/TOO-238-add-keychain-support
Mar 31, 2026
Merged

feat: add keychain support#13
AaronCQL merged 6 commits intojup-ag:mainfrom
amilz:feat/TOO-238-add-keychain-support

Conversation

@amilz
Copy link
Copy Markdown
Contributor

@amilz amilz commented Mar 27, 2026

Thanks for OS this repo! @AaronCQL I know this isn't a small change, so happy to hop in a TG chat or something if helpful.

--

Adds support for solana-keychain signers ‚ a unified signing interface that extends @solana/signers with 10 remote/managed key backends: AWS KMS, CDP, Crossmint, Dfns, Fireblocks, GCP KMS, Para, Privy, Turnkey, and Vault.

Since both KeyPairSigner and SolanaSigner implement TransactionPartialSigner, we use kit 6.5's partiallySignTransactionWithSigners() for a single signing path. Existing keypair behavior is unchanged.

Usage:

# Add a keychain-backed key (secrets via env vars, config stored locally)
jup keys add my-privy --backend privy --param appId=app_xxx --param walletId=wallet_xxx

# Works with all existing commands
jup spot swap --from SOL --to USDC --amount 1 --key my-vault
jup spot portfolio --key my-vault  # read-only: uses cached address, no signer init

Changes

  • package.json ‚ Add @solana/keychain@^0.6.1, bump @solana/kit to ^6.5.0
  • src/lib/KeychainConfig.ts (new) ‚ Backend registry with per-backend env var mapping, config I/O, factory dispatch (after doing this, I may actually upstream some of this into @solana/keychain)
  • src/lib/Signer.ts ‚ Widen from KeyPairSigner to TransactionPartialSigner; add loadAddress() for cheap read-only resolution from cached keychain config
  • src/commands/KeysCommand.ts ‚ Extend keys add with --backend + --param; unified namespace checks across .json/.keychain.json; unknown param validation
  • src/commands/{Spot,Perps,Lend,Predictions}Command.ts ‚ Use Signer.loadAddress() for read-only flows (no env vars or signer init needed)
  • docs/keys.md ‚ Document all 10 backends with examples

Example

bun run dev -- keys use my-privy
┌──────────┬──────────────────────────────────────────────┬───────┬────────┐
│ Name     │ Address                                      │ Type  │ Active │
├──────────┼──────────────────────────────────────────────┼───────┼────────┤
│ my-privy │ Goxw...VdPZ                                  │ privy │ ✅     │
└──────────┴──────────────────────────────────────────────┴───────┴────────┘

Test plan

  • bun run ci passes (lint, typecheck, 66 tests)

Requires signer keys:

  • keys add with --backend creates .keychain.json, verifies backend availability, caches address
  • keys list shows both keypair and keychain keys with type column
  • keys add --overwrite across types cleans up stale counterpart
  • --param with unknown keys is rejected
  • Read-only commands (portfolio, positions) work with keychain keys without backend env vars set**

amilz added 5 commits March 26, 2026 17:26
Integrate @solana/keychain-* packages so users can sign with
AWS KMS, GCP KMS, Fireblocks, Turnkey, Vault, Crossmint, Para,
and Privy backends via `keys add --backend <type> --param k=v`.

- New KeychainConfig module with backend registry and dynamic imports
- Signer widened from KeyPairSigner to TransactionPartialSigner
- Uses partiallySignTransactionWithSigners for signer abstraction
- keys list/add/delete/edit/use all handle keychain keys
- Fix name collision check between keypair and keychain keys

Refs: TOO-238
Switch from individual @solana/keychain-* packages to the umbrella
@solana/keychain@0.6.1. Peer deps fix (TOO-239) means @solana/signers
resolves to 6.5.0 without overrides.
- Unified key namespace: all mutations check both .json and
  .keychain.json; --overwrite cleans up stale counterpart
- Add Signer.loadAddress() for cheap address resolution from
  cached keychain config; update 7 read-only call sites
- Validate --param keys against backend registry, reject unknowns
Copy link
Copy Markdown
Contributor

@AaronCQL AaronCQL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @amilz for the PR - didn't know the solana-keychain pkg exists.

Left a few comments here, but happy to make the adjustments myself if you prefer.

Also, just a side note: bundling all backend SDKs from solana-keychain increases the built JS bundle of this CLI from 532KB to 5.8MB (11x) and startup time from ~50ms to ~100ms (2x). Both are acceptable for now, but worth noting since 100% of users pay this cost while most may barely use a single backend.

- Convert KeychainConfig to static class (codebase convention)
- Move helpers into KeysCommand as private static methods
- Rename removeStaleCounterpart → removeKey
- Simplify delete() to use removeKey directly
- Document keychain edit limitation in docs
@amilz
Copy link
Copy Markdown
Contributor Author

amilz commented Mar 30, 2026

Left a few comments here, but happy to make the adjustments myself if you prefer.

I pushed the changes in d342ae7 -- but feel free to modify additionally if you'd like.

Also, just a side note: bundling all backend SDKs from solana-keychain increases the built JS bundle of this CLI from 532KB to 5.8MB (11x) and startup time from ~50ms to ~100ms (2x). Both are acceptable for now, but worth noting since 100% of users pay this cost while most may barely use a single backend.

Ya I don't love that.

Each package is importable separately (e.g. @solana/keychain-privy). Looks like AWS and GCP deps are pretty heavy. A couple of ideas:

  • We could just select a subset of all signers available in keychain (maybe nix 2 or 3 of the larger ones).
  • We could alternatively ship with just @solana/keychain-core and do a package detection when a user does keys add --backend xyzPackage. If not detected ask user to y/n install that dep. Perhaps adds a little friction to the user but could be worth if they want to use their desired package.

I'll also see what I can do to cut down on the umbrella bundle size on our side.

@amilz amilz requested a review from AaronCQL March 30, 2026 17:16
@AaronCQL
Copy link
Copy Markdown
Contributor

We could alternatively ship with just @solana/keychain-core and do a package detection when a user does keys add --backend xyzPackage. If not detected ask user to y/n install that dep. Perhaps adds a little friction to the user but could be worth if they want to use their desired package.

This CLI ships as a pre-built binary (either via npm or a standalone bun binary), so installing pkgs on-demand is impossible for this flow.

We could just select a subset of all signers available in keychain (maybe nix 2 or 3 of the larger ones).

Yea, if AWS and GCP are the largest client libs, we could trim it off if there's low usage. But we can leave this for later.


For now, the increase in binary and startup time seems to be reasonable. Will release this and test it out first!

@AaronCQL AaronCQL merged commit 2e0076f into jup-ag:main Mar 31, 2026
1 check passed
@amilz amilz deleted the feat/TOO-238-add-keychain-support branch April 1, 2026 15:43
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.

3 participants