Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 25, 2026

Adds Ledger hardware wallet integration using @hot-labs/near-connect's custom wallet executor framework, bringing feature parity with the Web4 implementation.

Implementation

Executor (public/ledger-executor.js)

  • Self-contained script with CDN imports (esm.sh) for Ledger/NEAR dependencies
  • LedgerClient class handles APDU communication (CLA 0x80, 250-byte chunking, derivation path 44'/397'/0'/0'/1')
  • Full wallet interface: signIn, signOut, getAccounts, signAndSendTransaction(s), signMessage (NEP-413)
  • Account ID input UI since Ledger only provides public key
  • Verifies public key has FullAccess via RPC before sign-in

Manifest (lib/ledger-manifest.ts)

{
  id: "ledger",
  executor: "/ledger-executor.js",
  type: "sandbox",
  permissions: { storage: true, usb: true, hid: true }
}

Registration (stores/near-store.ts)

  • Registers Ledger wallet after connector initialization
  • Conditional on WebHID API availability (Chrome/Edge/Opera desktop only)

Browser Support

WebHID API requirement limits support to Chrome/Edge/Opera 89+ on desktop. Gracefully skips registration on unsupported browsers (Firefox, Safari, mobile).

Dependencies

All dependencies loaded from CDN (no build-time imports):

  • @ledgerhq/hw-transport-webhid@6.29.4
  • @near-js/crypto@1.4.1, @near-js/transactions@1.3.3, @near-js/utils@0.2.2

Documentation

  • docs/ledger-wallet-support.md: Technical architecture, APDU protocol details, security model
  • docs/ledger-testing-guide.md: Manual testing procedures for device interaction

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • fonts.googleapis.com
    • Triggering command: /usr/local/bin/node node /home/REDACTED/work/treasury26/treasury26/nt-fe/node_modules/.bin/next build --detach --pid-file /run/containerd/io.containerd.runtime.v2.task/moby/f6f3be56cd0a141a7d1ab82482fe9954cdaa44eb43715352d896e1dbc23ee629/f8ab01d2fb65521395bf8935834cc661e78151be5ceb06e3f5ed1b1cbd6b45c9.pid f6f3be56cd0a141a7d1ab82482fe9954cdaa44eb43715352d896e1dbc23ee629 (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

Summary

Add Ledger hardware wallet support to the treasury26 app by creating a custom wallet executor for @hot-labs/near-connect. This brings feature parity with the Web4 page which already supports Ledger via @near-wallet-selector/ledger.

Background

  • The treasury26 app uses @hot-labs/near-connect for wallet connections
  • The Web4 page (neardevhub-treasury-dashboard) supports Ledger via @near-wallet-selector/ledger
  • @hot-labs/near-connect supports custom wallet executors with usb and hid permissions for hardware wallets
  • We need to create a Ledger executor script that wraps the Ledger communication logic

Implementation Details

1. Create Ledger Executor Script (public/ledger-executor.js)

Create a self-contained JavaScript file that:

  • Uses CDN imports from esm.sh for dependencies:
    • @ledgerhq/hw-transport-webhid for Ledger communication
    • @near-js/crypto, @near-js/transactions, @near-js/utils for NEAR transaction handling
  • Implements the LedgerClient class for APDU communication (ported from @near-wallet-selector/ledger)
  • Implements the wallet interface required by hot-connect:
    • signIn(params) - Connect Ledger, get public key, prompt for account ID, verify access key
    • signOut(params) - Disconnect and clear stored accounts
    • getAccounts(params) - Return stored accounts
    • signAndSendTransaction(params) - Build, sign with Ledger, and broadcast transaction
    • signAndSendTransactions(params) - Handle multiple transactions
    • signMessage(params) - NEP-413 message signing
  • Includes UI for account ID input (Ledger only provides public key)
  • Uses window.selector.ready(wallet) to register with hot-connect
  • Uses window.selector.ui.whenApprove() for user prompts
  • Uses sandboxed localStorage via hot-connect's storage API

Key Ledger constants:

  • Default derivation path: 44'/397'/0'/0'/1'
  • Ledger vendor ID: 0x2c97
  • APDU commands: GET_PUBLIC_KEY (0x04), SIGN_TRANSACTION (0x02), SIGN_MESSAGE (0x07)

2. Create Ledger Manifest (nt-fe/lib/ledger-manifest.ts)

export const ledgerWalletManifest = {
  id: "ledger",
  name: "Ledger",
  icon: "https://cdn.jsdelivr.net/gh/AminMortezaie/icons@master/ledger-logo.svg",
  description: "Secure hardware wallet for NEAR Protocol",
  website: "https://www.ledger.com",
  version: "1.0.0",
  executor: "/ledger-executor.js", // Relative URL served from public folder
  type: "sandbox" as const,
  platform: {},
  features: {
    signMessage: true,
    signInWithoutAddKey: false,
    signAndSendTransaction: true,
    signAndSendTransactions: true,
    mainnet: true,
    testnet: true,
  },
  permissions: {
    storage: true,
    usb: true,  // Required for Ledger USB
    hid: true,  // Required for WebHID protocol
    allowsOpen: [],
  },
};

3. Register Ledger in NearConnector (nt-fe/stores/near-store.ts)

Modify the store to register the Ledger wallet after the connector initializes:

import { ledgerWalletManifest } from "@/lib/ledger-manifest";

// In the init function, after creating the connector:
newConnector.whenManifestLoaded.then(async () => {
  // Check if WebHID is supported (not on mobile, requires secure context)
  if (typeof navigator !== "undefined" && navigator.hid) {
    try {
      await newConnector.registerWallet(ledgerWalletManifest);
      console.log("Ledger wallet registered successfully");
    } catch (e) {
      console.warn("Failed to register Ledger wallet:", e);
    }
  }
});

Technical Notes

Ledger APDU Communication

The executor uses the same APDU protocol as @near-wallet-selector/ledger:

  • CLA: 0x80
  • Network ID: 87 ("W".charCodeAt(0))
  • Chunk size: 250 bytes
  • P1_LAST: 0x80, P1_MORE: 0x00

Transaction Signing Flow

  1. Fetch current nonce and block hash from RPC
  2. Create transaction using @near-js/transactions
  3. Encode transaction to bytes
  4. Sign with Ledger device
  5. Create SignedTransaction with signature
  6. Broadcast via RPC

Account Discovery

Since Ledger only provides a public key, the user must enter their account ID. The executor:

  1. Gets public key from Ledger
  2. Prompts user to enter account ID
  3. Verifies the public key is registered with that account via view_access_key RPC
  4. Requires FullAccess permission

References

Related Issue

Resolves #89

This pull request was created from Copilot chat.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 4 commits January 25, 2026 19:40
… registration

Co-authored-by: petersalomonsen <9760441+petersalomonsen@users.noreply.github.com>
…rse errors, use specific icon commit

Co-authored-by: petersalomonsen <9760441+petersalomonsen@users.noreply.github.com>
Co-authored-by: petersalomonsen <9760441+petersalomonsen@users.noreply.github.com>
Co-authored-by: petersalomonsen <9760441+petersalomonsen@users.noreply.github.com>
Copilot AI changed the title [WIP] Add Ledger hardware wallet support to treasury26 app feat: add Ledger hardware wallet support Jan 25, 2026
Copilot AI requested a review from petersalomonsen January 25, 2026 19:47
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.

Support Ledger for wallet sign in

2 participants