Skip to content

feat: seeker wallet solana support#786

Draft
NeOMakinG wants to merge 11 commits intomasterfrom
feat/seeker-wallet
Draft

feat: seeker wallet solana support#786
NeOMakinG wants to merge 11 commits intomasterfrom
feat/seeker-wallet

Conversation

@NeOMakinG
Copy link
Contributor

@NeOMakinG NeOMakinG commented Jan 27, 2026

Summary

Adds Seeker mobile wallet support to HDWallet, enabling Solana transaction signing through React Native WebView communication using the Solana Mobile Wallet Adapter (MWA) protocol.

Seeker is a Solana-focused mobile wallet that securely stores vault files (.skr) on the user's device. This integration allows web applications to connect to Seeker vaults via WebView postMessage, providing a hardware wallet-like experience for Solana users on mobile devices.

Changes

Core Types (packages/hdwallet-core/src/seeker.ts)

  • SeekerWalletInfo / SeekerWallet - Wallet interface for Seeker support
  • SeekerMessageHandlers - Platform-agnostic message handler interface
  • SeekerAuthResponse - Authorization result with vault address and label
  • SeekerAddress - Address derivation interface
  • SeekerSignTransactionRequest / SeekerSignTransactionResponse - Transaction signing interfaces
  • seekerDescribePath - Path description for Seeker

Adapter Package (packages/hdwallet-seeker/)

  • SeekerHDWallet - Main wallet adapter class
    • Implements HDWallet interface from @shapeshiftoss/hdwallet-core
    • Supports Solana transaction signing via message handlers
    • Handles wallet initialization and feature detection
    • Manages authorization state with vault address and label

Architecture

The Seeker adapter uses a message handler pattern to abstract platform-specific communication:

interface SeekerMessageHandlers {
  checkAvailability: () => Promise<{ available: boolean }>
  authorize: (cluster: string) => Promise<SeekerAuthResponse>
  deauthorize: () => Promise<void>
  getAddress: (derivationPath: number[]) => Promise<string>
  signTransaction: (request: SeekerSignTransactionRequest) => Promise<SeekerSignTransactionResponse>
  signAndSendTransaction: (request: SeekerSignTransactionRequest) => Promise<SeekerSignTransactionResponse>
}

This design allows:

  • HDWallet adapter to remain platform-agnostic
  • Web environment to inject React Native WebView communication handlers
  • Easy testing through mock handlers
  • Clean separation between wallet logic and communication layer

Seeker Specifications

Property Value
Supported Chains Solana
Communication Protocol Solana Mobile Wallet Adapter (MWA)
Transport Layer React Native WebView postMessage
Vault Storage Device-local .skr files
Signature Algorithm Ed25519 (Solana)

Message Flow

  1. Availability Check: Web app checks if Seeker is available via window.ReactNativeWebView.postMessage
  2. Authorization: Request user approval and receive vault address + label
  3. Transaction Signing: Send transaction for signing via postMessage
  4. Response Handling: Receive signed transaction or error via postMessage response

Testing

  • Build: yarn build
  • Lint: yarn lint
  • Test: yarn test
  • Integration test with Seeker mobile app (requires actual device + Seeker app)

Issue (if applicable)

N/A - New wallet adapter integration

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features
    • Added Solana wallet support through Seeker integration
    • Enables signing and broadcasting Solana transactions
    • Supports multiple Solana clusters (mainnet-beta, devnet, testnet)
    • Implements core wallet features including account management and session handling

✏️ Tip: You can customize this high-level summary in your review settings.

NeOMakinG and others added 9 commits January 22, 2026 18:29
Adds initial POC implementation for Seeker wallet integration using MWA protocol.
Add Seeker wallet adapter for Solana Mobile support.

Features:
- Message-based communication interface for platform flexibility
- Full Solana transaction signing and broadcasting
- Compatible with ShapeShift's hdwallet interface
- Designed for React Native WebView integration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Remove old seeker.ts and solana.ts files
- Simplify adapter implementation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Create @shapeshiftoss/hdwallet-seeker package
- Implements HDWallet interface for Seeker Solana Mobile wallet
- Uses message handler pattern for platform flexibility
- Supports Solana transaction signing and broadcasting
- Add proper TypeScript declarations and build setup
- Reset dependencies to use standard versions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Jan 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
hdwallet Ready Ready Preview, Comment Jan 28, 2026 5:37am

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Jan 27, 2026

📝 Walkthrough

Walkthrough

A new package @shapeshiftoss/hdwallet-seeker is introduced with a SeekerHDWallet adapter class implementing HDWallet interface for Solana support. Includes TypeScript type definitions, configuration files, package metadata, and integration with a SeekerMessageHandler for transaction signing operations.

Changes

Cohort / File(s) Summary
Workspace Configuration
lerna.json
Reformatted ignoreChanges array from single-line to multi-line indentation; no semantic changes
Package Configuration
packages/hdwallet-seeker/package.json, packages/hdwallet-seeker/tsconfig.json
Added package metadata, build scripts (tsc, clean, prepublishOnly), and TypeScript project configuration with dependencies on @shapeshiftoss/hdwallet-core and @solana/web3.js
Wallet Adapter Implementation
packages/hdwallet-seeker/src/adapter.ts
Introduced SeekerHDWallet class implementing HDWallet interface with Solana-specific methods (solanaSignTx, solanaSendTx, solanaGetAddress, solanaGetAccountPaths); delegates signing/sending to SeekerMessageHandler; most HDWallet interface methods are stub implementations
Type Definitions
packages/hdwallet-seeker/src/types.ts
Added result types (SeekerAvailabilityResult, SeekerAuthResult, SeekerAddressResult, etc.) and three key interfaces: SeekerMessageHandler (async lifecycle and crypto operations), SeekerAppIdentity (metadata), SeekerConfig (aggregates identity and optional cluster)
Module Exports
packages/hdwallet-seeker/src/index.ts
Added barrel export file re-exporting SeekerHDWallet and all public types/interfaces from types module

Sequence Diagram

sequenceDiagram
    participant App as Application
    participant Wallet as SeekerHDWallet
    participant Handler as SeekerMessageHandler
    
    App->>Wallet: solanaSignTx(msg)
    Wallet->>Wallet: solanaBuildTransaction()
    Wallet->>Wallet: serialize to base64
    Wallet->>Handler: signTransaction(transactionBase64)
    Handler->>Handler: sign transaction
    Handler-->>Wallet: serialized tx + signatures
    Wallet-->>App: SolanaSignedTx
    
    App->>Wallet: solanaSendTx(msg)
    Wallet->>Wallet: solanaBuildTransaction()
    Wallet->>Wallet: serialize to base64
    Wallet->>Handler: signAndSendTransaction(transactionBase64)
    Handler->>Handler: sign and broadcast
    Handler-->>Wallet: signature
    Wallet-->>App: SolanaTxSignature
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Hopping through branches, a new package takes flight,
With SeekerHDWallet and types shining bright!
Solana transactions flow smooth as can be,
Message handlers and signing in harmony! 🎉
Fresh crypto paths for the journey ahead,
Building with style, just as I said! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: seeker wallet solana support' directly and accurately describes the main change: adding Seeker wallet support with Solana transaction signing capability.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/seeker-wallet

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Reset all package versions and internal dependencies from 1.62.42-seeker-poc.1 to 1.62.41 to match master branch. This makes the PR diff cleaner and focused only on the Seeker integration changes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
The gitHead field is automatically added by npm/lerna during publish
and should not be committed to the repository. This keeps the git
history clean and avoids unnecessary diff noise.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@NeOMakinG NeOMakinG marked this pull request as ready for review January 28, 2026 05:40
@NeOMakinG NeOMakinG requested a review from a team as a code owner January 28, 2026 05:40
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@packages/hdwallet-seeker/src/adapter.ts`:
- Around line 103-105: getPublicKeys currently always returns a single PublicKey
regardless of the number of GetPublicKey requests, which breaks callers
expecting one result per request; update getPublicKeys to return an array
matching _msg.length (e.g., map over _msg and return { xpub: this.pubkey } for
each entry) or, if only single-key use is supported, validate and throw a clear
error when _msg.length !== 1; refer to getPublicKeys, GetPublicKey, PublicKey
and this.pubkey when making the change.
- Around line 184-196: In solanaSignTx, instead of returning the full serialized
transaction in the signatures array, deserialize result.signedTransaction (the
base64 signed transaction) back into a Transaction, extract each signature
(Transaction.signatures or equivalent) and convert each signature's .signature
bytes to base64 strings, then return signatures as an array of those
base64-encoded signature strings; update the return to use the serialized
signedTransaction for serialized and the extracted base64 signatures for
signatures, referencing solanaSignTx, solanaBuildTransaction,
messageHandler.signTransaction, and result.signedTransaction to locate the
change.

Comment on lines +103 to +105
getPublicKeys(_msg: GetPublicKey[]): Promise<(PublicKey | null)[] | null> {
return Promise.resolve([{ xpub: this.pubkey }])
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Align getPublicKeys output length with the request.

Line 103 returns a single element regardless of input size, which can break callers expecting one result per request. Either validate single-key usage or map to the input length.

✅ Suggested fix (mirror input length)
-  getPublicKeys(_msg: GetPublicKey[]): Promise<(PublicKey | null)[] | null> {
-    return Promise.resolve([{ xpub: this.pubkey }])
-  }
+  getPublicKeys(msg: GetPublicKey[]): Promise<(PublicKey | null)[] | null> {
+    return Promise.resolve(msg.map(() => ({ xpub: this.pubkey })))
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
getPublicKeys(_msg: GetPublicKey[]): Promise<(PublicKey | null)[] | null> {
return Promise.resolve([{ xpub: this.pubkey }])
}
getPublicKeys(msg: GetPublicKey[]): Promise<(PublicKey | null)[] | null> {
return Promise.resolve(msg.map(() => ({ xpub: this.pubkey })))
}
🧰 Tools
🪛 ESLint

[error] 103-103: '_msg' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 104-104: Insert ;

(prettier/prettier)

🤖 Prompt for AI Agents
In `@packages/hdwallet-seeker/src/adapter.ts` around lines 103 - 105,
getPublicKeys currently always returns a single PublicKey regardless of the
number of GetPublicKey requests, which breaks callers expecting one result per
request; update getPublicKeys to return an array matching _msg.length (e.g., map
over _msg and return { xpub: this.pubkey } for each entry) or, if only
single-key use is supported, validate and throw a clear error when _msg.length
!== 1; refer to getPublicKeys, GetPublicKey, PublicKey and this.pubkey when
making the change.

Comment on lines +184 to +196
async solanaSignTx(msg: SolanaSignTx): Promise<SolanaSignedTx | null> {
const transaction = solanaBuildTransaction(msg, this.pubkey)
const serializedTx = Buffer.from(transaction.serialize()).toString('base64')

const result = await this.messageHandler.signTransaction(serializedTx)
if (!result.success || !result.signedTransaction) {
throw new Error(result.error ?? 'Failed to sign transaction')
}

return {
serialized: result.signedTransaction,
signatures: [result.signedTransaction],
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find SolanaSignedTx definition and examine its structure
rg -n "interface SolanaSignedTx|type SolanaSignedTx|class SolanaSignedTx" packages -g '*.ts' -A 5

Repository: shapeshift/hdwallet

Length of output: 45


🏁 Script executed:

# Find all solanaSignTx implementations
rg -n "solanaSignTx" packages -g '*.ts' -B 2 -A 15

Repository: shapeshift/hdwallet

Length of output: 45


🏁 Script executed:

# Check SolanaSignTx and related types in hdwallet-core
fd -e ts packages/hdwallet-core | head -20

Repository: shapeshift/hdwallet

Length of output: 456


🏁 Script executed:

# List available packages
ls -la packages/ | head -20

Repository: shapeshift/hdwallet

Length of output: 1368


🏁 Script executed:

# Search for SolanaSignedTx definition
rg "SolanaSignedTx" packages --type ts -B 2 -A 5

Repository: shapeshift/hdwallet

Length of output: 45


🏁 Script executed:

# Search for solanaSignTx method implementations
rg "solanaSignTx\s*\(" packages --type ts -B 1 -A 15

Repository: shapeshift/hdwallet

Length of output: 45


🏁 Script executed:

# Continue listing packages
ls -la packages/ | tail -10

Repository: shapeshift/hdwallet

Length of output: 745


🏁 Script executed:

# Search for SolanaSignedTx without context flags
rg "SolanaSignedTx" packages

Repository: shapeshift/hdwallet

Length of output: 45


🏁 Script executed:

# Look for seeker package
ls -la packages/ | grep seeker

Repository: shapeshift/hdwallet

Length of output: 127


🏁 Script executed:

# Search for Solana-related types more broadly
rg "Solana" packages --type ts | grep -i "interface\|type\|class" | head -20

Repository: shapeshift/hdwallet

Length of output: 45


🏁 Script executed:

# Look at the seeker adapter file mentioned in the review
cat packages/hdwallet-seeker/src/adapter.ts | head -220 | tail -50

Repository: shapeshift/hdwallet

Length of output: 1659


🏁 Script executed:

# Check hdwallet-core for Solana types
find packages/hdwallet-core -name "*.ts" | xargs grep -l "Solana" | head -5

Repository: shapeshift/hdwallet

Length of output: 172


🏁 Script executed:

# Check SolanaSignedTx type definition in hdwallet-core
cat packages/hdwallet-core/src/solana.ts | head -150

Repository: shapeshift/hdwallet

Length of output: 4945


🏁 Script executed:

# Look for other solanaSignTx implementations
find packages -name "*.ts" -exec grep -l "solanaSignTx" {} \;

Repository: shapeshift/hdwallet

Length of output: 583


🏁 Script executed:

# Check if there are any other Solana adapter implementations
ls -la packages/hdwallet-*/src/ | grep -i solana

Repository: shapeshift/hdwallet

Length of output: 482


🏁 Script executed:

# Check phantom solanaSignTx implementation
cat packages/hdwallet-phantom/src/solana.ts

Repository: shapeshift/hdwallet

Length of output: 1096


🏁 Script executed:

# Check ledger solanaSignTx implementation
cat packages/hdwallet-ledger/src/solana.ts

Repository: shapeshift/hdwallet

Length of output: 1876


🏁 Script executed:

# Check native solanaSignTx implementation
cat packages/hdwallet-native/src/solana.ts

Repository: shapeshift/hdwallet

Length of output: 2589


🏁 Script executed:

# Check gridplus solanaSignTx implementation
cat packages/hdwallet-gridplus/src/solana.ts

Repository: shapeshift/hdwallet

Length of output: 1977


🏁 Script executed:

# Check trezor solanaSignTx implementation
cat packages/hdwallet-trezor/src/solana.ts

Repository: shapeshift/hdwallet

Length of output: 2918


signatures array should contain individual signature strings, not the full serialized transaction.

The current code returns [result.signedTransaction], but downstream Solana consumers expect signatures to contain the actual signature bytes (base64-encoded). This mirrors all other adapter implementations (Phantom, Ledger, Native, GridPlus, Trezor), which extract signatures from the signed transaction and convert to base64.

Deserialize the signed transaction and extract its signatures:

Suggested fix
+import { Transaction } from '@solana/web3.js'

 async solanaSignTx(msg: SolanaSignTx): Promise<SolanaSignedTx | null> {
   const transaction = solanaBuildTransaction(msg, this.pubkey)
   const serializedTx = Buffer.from(transaction.serialize()).toString('base64')

   const result = await this.messageHandler.signTransaction(serializedTx)
   if (!result.success || !result.signedTransaction) {
     throw new Error(result.error ?? 'Failed to sign transaction')
   }

+  const signedTx = Transaction.from(Buffer.from(result.signedTransaction, 'base64'))
+  const signatures = signedTx.signatures
+    .map(s => (s.signature ? Buffer.from(s.signature).toString('base64') : null))
+    .filter((s): s is string => Boolean(s))
+
   return {
     serialized: result.signedTransaction,
-    signatures: [result.signedTransaction],
+    signatures,
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async solanaSignTx(msg: SolanaSignTx): Promise<SolanaSignedTx | null> {
const transaction = solanaBuildTransaction(msg, this.pubkey)
const serializedTx = Buffer.from(transaction.serialize()).toString('base64')
const result = await this.messageHandler.signTransaction(serializedTx)
if (!result.success || !result.signedTransaction) {
throw new Error(result.error ?? 'Failed to sign transaction')
}
return {
serialized: result.signedTransaction,
signatures: [result.signedTransaction],
}
import { Transaction } from '@solana/web3.js'
async solanaSignTx(msg: SolanaSignTx): Promise<SolanaSignedTx | null> {
const transaction = solanaBuildTransaction(msg, this.pubkey)
const serializedTx = Buffer.from(transaction.serialize()).toString("base64");
const result = await this.messageHandler.signTransaction(serializedTx)
if (!result.success || !result.signedTransaction) {
throw new Error(result.error ?? "Failed to sign transaction");
}
const signedTx = Transaction.from(Buffer.from(result.signedTransaction, "base64"));
const signatures = signedTx.signatures
.map(s => (s.signature ? Buffer.from(s.signature).toString("base64") : null))
.filter((s): s is string => Boolean(s))
return {
serialized: result.signedTransaction,
signatures,
};
}
🧰 Tools
🪛 ESLint

[error] 185-185: Insert ;

(prettier/prettier)


[error] 186-186: Replace 'base64') with "base64");

(prettier/prettier)


[error] 188-188: Insert ;

(prettier/prettier)


[error] 190-190: Replace 'Failed·to·sign·transaction') with "Failed·to·sign·transaction");

(prettier/prettier)


[error] 196-196: Insert ;

(prettier/prettier)

🤖 Prompt for AI Agents
In `@packages/hdwallet-seeker/src/adapter.ts` around lines 184 - 196, In
solanaSignTx, instead of returning the full serialized transaction in the
signatures array, deserialize result.signedTransaction (the base64 signed
transaction) back into a Transaction, extract each signature
(Transaction.signatures or equivalent) and convert each signature's .signature
bytes to base64 strings, then return signatures as an array of those
base64-encoded signature strings; update the return to use the serialized
signedTransaction for serialized and the extracted base64 signatures for
signatures, referencing solanaSignTx, solanaBuildTransaction,
messageHandler.signTransaction, and result.signedTransaction to locate the
change.

@NeOMakinG NeOMakinG marked this pull request as draft January 28, 2026 08:33
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