Skip to content

Add Solana + Sui chain support and Ledger hardware-wallet signing - #151

Merged
ndii-dev merged 1 commit into
mainfrom
feat/chain-support-issues-92-142-143
Aug 28, 2026
Merged

ndii-dev merged 1 commit into
mainfrom
feat/chain-support-issues-92-142-143

Conversation

@ndii-dev

Copy link
Copy Markdown
Contributor

Summary

Multi-chain follow-up to the EVM (Base + Ethereum) slice, plus hardware-wallet signing:

  • Solana (Add Solana chain support (read-only balances + native SOL send) #142): lib/solana/{networks,solana.service,solana-payment.service}.ts — read-only SOL + USDC balance queries against Solana devnet via @solana/web3.js, and a server-only native SOL send gated behind an optional SOLANA_SOURCE_PRIVATE_KEY. API routes at /api/solana/balance and /api/solana/send, a useSolanaWallet hook, a SolanaWalletCard, and a /solana page linked from Crypto Holdings.
  • Sui (Add Sui chain support (read-only balances + native SUI send) #143): lib/sui/{networks,sui.service,sui-payment.service}.ts — read-only SUI + USDC balance queries against Sui testnet via @mysten/sui, and a server-only native SUI transfer gated behind an optional SUI_SOURCE_PRIVATE_KEY. API routes at /api/sui/balance and /api/sui/send, a useSuiWallet hook, a SuiWalletCard, and a /sui page linked from Crypto Holdings.
  • Ledger (No hardware wallet (Ledger) signing path exists anywhere in the codebase #92): lib/ledger/ledger.service.ts wraps @ledgerhq/hw-transport-webhid + @ledgerhq/hw-app-str (Ledger's Stellar app) for client-side connect/getPublicKey/signTransaction over WebHID. lib/signing/signer.ts defines an ISigner abstraction (LedgerSigner) so transaction-building code doesn't care which signer produced a signature. StellarPaymentService gained buildUnsignedTransaction()/submitSignedTransaction() so the private key never has to touch the server for this path. New routes /api/wallet/send/prepare and /api/wallet/send/submit-signed, a LedgerSendCard, and a /ledger page — an alternative signing path alongside the existing software-key flow, selectable instead of it.

Both new chain modules follow the existing lib/evm/* shape exactly: types in lib/types.ts, a networks.ts config, a read-only service class, a server-only payment service gated behind an env var (fails closed with ERR_PAYMENT_NOT_CONFIGURED rather than fabricating a result — same as STELLAR_SOURCE_SECRET_KEY/EVM_SOURCE_PRIVATE_KEY), matching API routes, and a hook + card + page for UI wiring.

Root cause / design rationale

Globe Wallet's balance/send logic was built tightly around Stellar's account model (IWalletService/AssetCode), and the EVM slice already established the right pattern for adding a chain with an incompatible account/signature model: a self-contained module alongside it rather than a retrofit. Solana (ed25519 + base58, no checksums) and Sui (object-centric coins, not account balances) each need their own module for the same reason EVM did. For Ledger, the key architectural fact is that signing needs to happen where the physical device is — the browser — so the existing single-call submitPayment() (build+sign+submit server-side) doesn't fit; splitting StellarPaymentService into a build step and a submit-already-signed step is what lets an external signer slot in between without touching how the transaction itself is built.

Definition of done

#142 (Solana)

  • @solana/web3.js added as a dependency
  • Address validation (base58, 32-byte pubkey via PublicKey constructor)
  • Read-only SOL + USDC balance queries against devnet — lib/solana/solana.service.ts#getBalances
  • Server-only native SOL send gated behind SOLANA_SOURCE_PRIVATE_KEY, added to lib/env.mjs/.env.example
  • API routes mirroring /api/evm/balance and /api/evm/send
  • Unit + integration tests — not included in this pass (time-boxed); tests/unit/services/evm.service.test.ts and tests/integration/evm-api.test.ts are the pattern to mirror
  • Minimal UI surface (SolanaWalletCard, linked from Crypto Holdings via /solana)

#143 (Sui) — same shape as #142:

  • @mysten/sui added as a dependency
  • Address validation (isValidSuiAddress)
  • Read-only SUI + USDC balance queries against testnet — lib/sui/sui.service.ts#getBalances
  • Server-only native SUI transfer gated behind SUI_SOURCE_PRIVATE_KEY, added to lib/env.mjs/.env.example
  • API routes mirroring /api/evm/balance and /api/evm/send
  • Unit + integration tests — not included in this pass (time-boxed)
  • Minimal UI surface (SuiWalletCard, linked from Crypto Holdings via /sui)

#92 (Ledger)

  • Signing abstraction (lib/signing/signer.ts's ISigner) that targets either the in-app key (existing StellarPaymentService.submitPayment, unchanged) or an external signer (LedgerSigner) without changing the transaction-building code — buildUnsignedTransaction/submitSignedTransaction are shared by both paths
  • Ledger XLM app integration for the send flow — lib/ledger/ledger.service.ts (WebHID + @ledgerhq/hw-app-str), wired into LedgerSendCard at /ledger
  • Solana/Sui Ledger apps — explicit TODO, noted in lib/ledger/ledger.service.ts; out of scope per the issue's own scoping note ("focus on Stellar Ledger app")

Evidence the code runs

No dev server / test suite run for this PR (time-boxed pass). Verified instead via npx tsc --noEmit: the diff introduces zero new type errors — before/after error counts were 186/151 lines respectively, and the one file that still reports an error (components/app/crypto-holdings.tsx, an unrelated pre-existing AssetCode/NGN typing issue) reports the identical error on main before this branch's changes. All new modules (lib/solana/*, lib/sui/*, lib/ledger/*, lib/signing/*, the new API routes, lib/services/stellar-payment.service.ts additions) type-check cleanly. Dependencies (@solana/web3.js, @mysten/sui@1.45.2 — pinned off the 2.x line, which restructured its client API and dropped the classic SuiClient export — @ledgerhq/hw-transport-webhid, @ledgerhq/hw-app-str, bs58, @types/bs58) installed cleanly via npm install and are reflected in package-lock.json.

Tests

Not included in this pass — flagged above per DoD item. Follow-up work should mirror tests/unit/services/evm.service.test.ts (mock the RPC client), tests/integration/evm-api.test.ts + tests/integration/evm-send-unconfigured.test.ts (route-level, mocked network), and tests/component/evm-wallet-card.test.tsx (React Testing Library) for each of the three new surfaces.

Adjacent behavior re-verified

  • components/app/crypto-holdings.tsx — added two Links (Solana, Sui, Ledger) alongside the existing EVM link; no other logic touched.
  • lib/services/stellar-payment.service.ts — existing submitPayment() (the software-key path used by /api/wallet/send) is untouched; only new methods were added.
  • lib/errors.ts — two new error codes added (ERR_MISSING_XDR, ERR_MISSING_SIGNATURE); existing codes untouched.
  • lib/env.mjs — new vars are all optional (optionalNonEmptyString), so existing deployments without them continue to validate exactly as before.

Known gaps / follow-up

  • SPL-token / Sui coin-type sends (USDC on either chain) are balance-only for now — native-asset sends only, matching the EVM module's own ERC-20 scope note.
  • No automated tests for the three new surfaces in this pass.
  • Ledger submit-signed route doesn't yet share the idempotency-key mechanism /api/wallet/send uses (Issue No idempotency key on /api/wallet/send — once real, duplicate submits will double-send funds #85) — a retry could theoretically double-submit; noted for follow-up.
  • Solana/Sui Ledger apps are a TODO, called out directly in lib/ledger/ledger.service.ts.

Closes #142
Closes #143
Closes #92

- Solana (#142): lib/solana/{networks,solana.service,solana-payment.service}.ts,
  /api/solana/{balance,send} routes, useSolanaWallet hook, SolanaWalletCard,
  /solana page. Read-only SOL + USDC balances via @solana/web3.js against
  devnet; native SOL send gated behind optional SOLANA_SOURCE_PRIVATE_KEY.
- Sui (#143): lib/sui/{networks,sui.service,sui-payment.service}.ts,
  /api/sui/{balance,send} routes, useSuiWallet hook, SuiWalletCard, /sui page.
  Read-only SUI + USDC balances via @mysten/sui against testnet; native SUI
  transfer gated behind optional SUI_SOURCE_PRIVATE_KEY.
- Ledger (#92): lib/ledger/ledger.service.ts (WebHID + Stellar app via
  @ledgerhq/hw-app-str), lib/signing/signer.ts (ISigner abstraction),
  StellarPaymentService.buildUnsignedTransaction/submitSignedTransaction,
  /api/wallet/send/{prepare,submit-signed} routes, LedgerSendCard, /ledger
  page. Alternative signing path alongside the existing software-key flow —
  the private key never leaves the device.

Both new chain modules follow the existing lib/evm/* module shape (types in
lib/types.ts, network config, read-only service, server-only payment
service gated behind an env var, matching API routes, hook + card + page).

Env vars added to lib/env.mjs and .env.example: SOLANA_RPC_URL,
SOLANA_SOURCE_PRIVATE_KEY, SUI_RPC_URL, SUI_SOURCE_PRIVATE_KEY.

Known gaps (tracked as follow-up, not blocking this skeleton):
- SPL-token/coin-type sends (USDC) — balance-only for now, native-asset
  sends only, matching the EVM module's ERC-20 scope note.
- Solana/Sui Ledger apps — Stellar-only per Issue #92's own scoping.
- No new automated tests included in this pass (time-boxed); existing
  EVM test suite (tests/unit/services/evm.service.test.ts,
  tests/integration/evm-api.test.ts, tests/component/evm-wallet-card.test.tsx)
  is the pattern to mirror for follow-up test coverage.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ndii-dev
ndii-dev merged commit 55bcae7 into main Aug 28, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants