Skip to content

About

ngchain JS SDK, browser-extension wallet, and React connect kit

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

3 Commits

Folders and files

Repository files navigation

ngchain-js

A TypeScript monorepo for building on ngchain:

Package What
@ngchain/sdk Core SDK — RPC client, keys/addresses, tx building & signing, the commit–reveal private-tx flow, and the injected-provider spec. Signing is validated byte-for-byte against a live node.
@ngchain/wallet-extension A Manifest-V3 browser wallet built on the SDK: encrypted keyring, popup UI, and a window.ngchain dapp provider with per-request approval.
@ngchain/react React connect kit — NgWalletProvider, hooks, and a ConnectButton (WalletConnect-style UX over the injected provider).
npm install      # install workspace deps
npm run build    # build all packages

SDK

import { NgClient, KeyPair, Network, ngToBaseUnits, baseUnitsToNg } from '@ngchain/sdk';

const ng = NgClient.http('http://testnet-rpc.ngchain.org:52521', Network.TESTNET); // or your node
const key = KeyPair.generate();               // 32-byte secp256k1; key.address is bs58
console.log(key.address);

const { total } = await ng.balance(key.address);
console.log(baseUnitsToNg(total), 'NG');

// a transfer is a one-call fire-and-forget: the SDK salts+signs the reveal,
// binds a commitment, and submits both via ng_sendPrivateTx (the node re-heights
// until they land). Fee is estimated from the node if omitted.
const txid = await ng.transfer(key, { to: recipient, value: ngToBaseUnits('1.5') });

// contracts
await ng.callContract(key, { contract, method: 'transfer', args, value: 0n });
await ng.deployContract(key, wasmBytes);      // empty bytes = destroy

Signing details (matching ngtypes): address = base58(blake3(0x01 ‖ scheme ‖ pubkey)); effect txs sign the height-independent digest so one signature spans the reveal window; the secp256k1 signature travels in the compact recover envelope (0x03 ‖ scheme ‖ 65-byte sig).

Post-quantum wallets

A key is derived from a 32-byte seed exactly as ngcore's NewKeyFromSeed (SHAKE256("ngcore-keygen" ‖ scheme ‖ seed)), so the wallet format scheme ‖ seed is portable between this SDK and the node. Alongside secp256k1 (0x01), the SDK signs natively with the post-quantum ML-DSA-44 (0x11, FIPS 204) and SLH-DSA-128s (0x12, FIPS 205) — deterministic, domain "ngcore", validated byte-for-byte against ngcore vectors (packages/sdk/test/pq-vectors.json). PQ schemes have no key recovery, so their signature travels in the full envelope (0x01 ‖ scheme ‖ pubkey ‖ sig). FN-DSA-512 (Falcon, 0x10) is declared by the protocol but is not reproducible in pure JS (its keygen doesn't match go-fn-dsa); it needs a wasm signer.

import { KeyPair, SigScheme } from '@ngchain/sdk';

const pq = KeyPair.generate(SigScheme.MLDSA44);   // or SLHDSA128
console.log(pq.address);                           // same address ngcore derives from pq.serialize()
const txid = await ng.transfer(pq, { to, value }); // full envelope, submitted like any tx

Browser extension

cd apps/extension && npm run build        # -> dist/

Load apps/extension/dist as an unpacked extension at chrome://extensions. It exposes window.ngchain (EIP-1193-shaped) to pages; connect/sign/send always prompt the user. Keys are encrypted (PBKDF2 + AES-GCM) in storage.local; unlocked keys live only in session memory.

React (native ngchain + EVM migration)

ConnectButton lists the native ngchain wallet and any EVM wallet (EIP-6963). Because ngchain uses secp256k1 (the same curve as Ethereum), an EVM key maps deterministically to an ngchain address — so users can connect straight from MetaMask / a WalletConnect provider and their ngchain address is derived from the same key (linkEvmAccount, via an EIP-191 personal_sign challenge). If the EVM wallet can sign raw hashes it transacts directly (EvmSigner → eth_sign, with a self-verifying capability check); otherwise the UI falls back to importing the key into the native wallet. account is always the ngchain address, either way.

import { NgWalletProvider, ConnectButton, useAccount, useBalance, useSendTransfer } from '@ngchain/react';
import { ngToBaseUnits } from '@ngchain/sdk';

function App() {
  return (
    // rpcUrl is required for the EVM path (submitting the EVM-signed tx to a node)
    <NgWalletProvider rpcUrl="http://testnet-rpc.ngchain.org:52521">
      <ConnectButton />
      <Wallet />
    </NgWalletProvider>
  );
}

function Wallet() {
  const { address, isConnected } = useAccount();
  const { totalNg } = useBalance();
  const [send, { pending, txid }] = useSendTransfer();
  if (!isConnected) return null;
  return (
    <>
      <div>{address}: {totalNg} NG</div>
      <button disabled={pending} onClick={() => send(recipient, ngToBaseUnits('1'))}>
        {pending ? 'Sending…' : 'Send 1 NG'}
      </button>
      {txid && <div>sent {txid.slice(0, 16)}…</div>}
    </>
  );
}

The provider contract

Wallets expose window.ngchain: NgProvider with request({ method, params }) and on(event, …). Methods: ng_requestAccounts, ng_accounts, ng_network, ng_getBalance, ng_sendTransfer, ng_callContract, ng_deploy, ng_signMessage, ng_rpc (read-only passthrough). Events: connect, disconnect, accountsChanged, networkChanged.

ng_signMessage returns the signature envelope (hex), so a verifier can recover the signer's address for any scheme — including the recovery-less PQ schemes, whose envelope carries the public key. Verify it with the SDK:

import { verifySignedMessage } from '@ngchain/sdk';
const address = verifySignedMessage(message, envelopeHex); // bs58 address, or null

This is the basis for wallet login: challenge → ng_signMessage → server calls verifySignedMessage to authenticate the ngchain address, secp256k1 or PQ alike.

About

ngchain JS SDK, browser-extension wallet, and React connect kit

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages