npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

soq-lightning-sdk

v0.1.0-alpha.2

Published

Soqucoin eLTOO Lightning SDK: quantum-safe (ML-DSA-44) instant payments on stagenet

Readme

soq-lightning-sdk

Quantum-safe Lightning for Soqucoin. A TypeScript SDK for eLTOO payment channels secured by ML-DSA-44 (Dilithium). No ECDSA, no penalty/revocation, latest state wins.

Status: stagenet alpha (v0.1.0-alpha). The happy path (open, pay, cooperative close) runs against the live LSP today. The unilateral dispute path (0x42-signed eLTOO update/settlement TXs) is landing in channel.ts. See the Roadmap. Do not use for mainnet value.


Why this is different

| | Bitcoin Lightning | soq-lightning | |---|---|---| | Channel sigs | ECDSA / Schnorr | ML-DSA-44 (Dilithium), post-quantum | | Update mechanism | penalty (LN-penalty) | eLTOO (ANYPREVOUTANYSCRIPT, no toxic state) | | Multi-hop | PTLC / HTLC | HTLC (SHA-256 hashlocks, PQ-safe, no adaptor sigs) | | Invoice | BOLT-11 | PQ-native bech32m (soq1ln), Dilithium-signed |

eLTOO means no revocation secrets and no justice transactions. The newest signed state always supersedes older ones via APO rebinding, so an old-state broadcast is simply overwritten, never punished. That removes Lightning's single nastiest footgun.

Install

npm install soq-lightning-sdk

Quickstart (stagenet)

import { SoqLightning } from "soq-lightning-sdk";

const ln = new SoqLightning({ baseUrl: process.env.LSP_URL! });

// Fund from the faucet + auto-open a channel with the LSP
const ch = await ln.fundAndOpen({
  pubKeyHex: myDilithiumPubKeyHex,   // ML-DSA-44 public key
  address:   mySettlementAddress,
  capacitySat: 5_0000_0000,          // 5 SOQ
});

await ln.pay(ch.channel_id, 1_0000_0000);   // send 1 SOQ to the peer
console.log(await ln.channel(ch.channel_id)); // balances shifted, state_index bumped

await ln.close(ch.channel_id);                // cooperative close, L1 settlement

Getting paid (LSP invoices)

Accept a Lightning payment in a few lines — create an invoice on your channel, show the soqln: URI (QR or paste), and wait for it to settle:

// payee: request 0.25 SOQ
const inv = await ln.createInvoice(myChannel.channel_id, 2500_0000, { memo: "coffee" });
console.log(inv.uri);                          // soqln:<id> — show as QR / send to the payer

const settled = await ln.awaitInvoicePaid(inv.invoice_id);
if (settled.status === "paid") console.log("paid — channel balance grew");

// payer: settle a soqln: URI
const id = SoqLightning.parseInvoiceUri(scannedUri)!;
await ln.payInvoice(id, payerChannel.channel_id);

Settlement is custodial on the hosted beta: the LSP hub atomically debits the payer's channel and credits yours (your hosted capacity grows with the credit — every credited sat is backed by the payer's debit). These are LSP invoices, not the PQ-signed soq1ln1… invoices below — the signed format is the trust-minimized target, and this API keeps its shape when that rail lands.

In the browser (zero install)

This SDK is pure TypeScript and the ML-DSA-44 binding (@noble/post-quantum) is WASM-free pure JS, so the entire crypto path runs client-side in the browser — generate a Dilithium wallet, build and sign an invoice or transaction, all without a server ever touching a key. This is what powers the Soqucoin builders playground: a quantum-safe wallet and a real signed transaction, in the browser, in about a minute.

import { mlDsaKeygen, nobleMlDsa, freshPreimage, signInvoice, encodeInvoice } from "soq-lightning-sdk";

// Ephemeral, in-browser ML-DSA-44 wallet — no server, no custody.
const { publicKey, secretKey } = mlDsaKeygen();

const { paymentHash } = freshPreimage((n) => crypto.getRandomValues(new Uint8Array(n)));
const invoice = signInvoice({ version: 1, amountSat: 250000000n, paymentHash,
  destination, timestamp: BigInt(Math.floor(Date.now() / 1000)), expiry: 3600,
  description: "", metadata: new Uint8Array() }, secretKey, nobleMlDsa);
const encoded = encodeInvoice(invoice);   // soq1ln1…

ML-DSA-44 signatures are 2,420 bytes and keys 1,312 bytes. The crypto is pure JS but not tiny — lazy-load it and run signing in a web worker so the UI doesn't jank. Keys generated in the browser are ephemeral and stagenet-only; they hold no real value.

A runnable version of this flow (offline crypto, plus an optional live channel flow) is in examples/quickstart.mjs:

npm run example:quickstart                       # offline, zero infra
LSP_URL=https://<lsp> npm run example:quickstart # also runs open→pay→close

Invoices (PQ-native)

import { freshPreimage, signInvoice, encodeInvoice, verifyAgainstShort } from "soq-lightning-sdk";

// receiver: fresh preimage PER invoice (required, see spec §3.6)
const { preimage, paymentHash } = freshPreimage((n) => crypto.getRandomValues(new Uint8Array(n)));
const invoice = signInvoice({
  version: 1, amountSat: 250000000n, paymentHash, destination, timestamp, expiry: 3600,
  description: "coffee", metadata: new Uint8Array(),
}, mySecretKey, mlDsa);                         // mlDsa: inject your ML-DSA-44 binding
const encoded = encodeInvoice(invoice);         // soq1ln1...

// payer scanning a QR short-form: pay to the QR's hash/amount, not the fetched blob (F-C6)
const trusted = verifyAgainstShort(fetchedInvoice, scannedUri, payeePubKey, mlDsa);

MlDsa is an injected interface (sign/verify). Wire it to @noble/post-quantum or your native Dilithium binding. The SDK never holds a key-recovery path, and invoice authenticity is separate from payment safety (the HTLC locks to paymentHash regardless of what destination claims).

Plugging in real eLTOO transactions

SoqLightning.pay() delegates TX construction to an UpdateTxBuilder. The default (placeholderTxBuilder) sends opaque placeholders that the accept-and-store LSP accepts on the happy path — enough to exercise open/pay/close end to end. For real, disputable bytes, inject DilithiumEltooBuilder (shipped in channel.ts), which builds the actual 0x42-signed eLTOO update/settlement TXs:

import { SoqLightning, DilithiumEltooBuilder } from "soq-lightning-sdk";

const ln = new SoqLightning({
  baseUrl,
  txBuilder: new DilithiumEltooBuilder({ /* funding, keys, mldsa ... */ }),
});

The API does not change — only the bytes on the wire become disputable. Every serializer DilithiumEltooBuilder produces (APO 0x42/0x41, CTV hash, CSFS 2-of-2) is proven byte-identical to the node; see test/vector.test.mjs.

CLI

export LSP_URL=https://<stagenet-lsp>
npx soq-ln status                              # LSP health + info
npx soq-ln open --pub <hex> --addr <a> --cap 500000000
npx soq-ln pay  --channel <id> --amount 100000000
npx soq-ln channel <id>
npx soq-ln close --channel <id>

Canary

A standing end-to-end check (faucet, pay, stale-reject, close) for alerting:

LSP_URL=https://<lsp> npm run canary            # single run, exit 0/1
LSP_URL=https://<lsp> node dist/canary.js --loop 300

Develop

npm install
npm run build      # tsc -> dist/
npm test           # offline: invoice round-trip + mock-peer open/pay/close

The test suite runs fully offline (mock peer, stub ML-DSA), so contributors don't need stagenet access to iterate.

Watchtower

eLTOO has no penalty or revocation. If a counterparty broadcasts a stale update, the defense is to broadcast the latest update, which supersedes it (higher CLTV plus 0x42 rebinding). The watchtower holds your latest update/settlement TXs and does this while you're offline.

Topology (decided, phased, BASE-style): the production towers are firewalled internal-only and run by Soqucoin Labs, with dual-tower defense-in-depth (services-vps plus mining-vps, fan-out, any one supersedes). Developers don't run towers, the same way BASE devs don't run sequencers.

  • Stagenet (now): the LSP auto-arms both towers on every state update. The spoke verifies liveness via the LSP proxy with ln.towerStatus() / ln.assertTowersHealthy(). The TowerClient and watchtower: arming below are for operator / self-hosted use.
  • Mainnet Phase 1: signed tower receipts (the tower ML-DSA-signs (channel_id, state_index, ctv_hash, ts)) let the spoke cryptographically verify its state i+1 is armed.
  • Mainnet Phase 2: federated towers. The spoke arms N independent towers directly via the watchtower: interface (unchanged across all three phases).

The formal proof holds for all phases: it proves that if armed, the spoke is safe. Stagenet's gap is operational (who arms), not protocol.

Verifying tower coverage (stagenet spoke path)

const ln = new SoqLightning({ baseUrl: LSP_URL });
const s = await ln.towerStatus();           // { available, tower_count, towers: [{name, available, status}] }
await ln.assertTowersHealthy(2);            // throws unless >= 2 towers reachable (dual-tower)
import { TowerClient, SoqLightning, DilithiumEltooBuilder } from "soq-lightning-sdk";

const tower = new TowerClient({ baseUrl: TOWER_URL, bearerToken: TOWER_TOKEN });

const ln = new SoqLightning({
  baseUrl: LSP_URL,
  txBuilder: new DilithiumEltooBuilder({ /* funding, keys, mldsa ... */ }),
  watchtower: {
    client: tower,
    fundingFor: (channelId) => ({ funding_txid, funding_vout }),
  },
});

// pay() now ARMS the tower with state i+1 BEFORE it returns (spec §1.6 persist, arm, ack).
// If arming fails, pay() throws and the payment is not treated as locked. No silent theft window.
await ln.pay(channelId, 1_0000_0000);

You can also drive the tower directly: tower.register(...), tower.status() (public), tower.channels(), tower.unregister(id).

Roadmap

  • [x] B2 PQ invoices (sign/verify/bech32m/F-C6 short form)
  • [x] B3 REST client (12 live LSP endpoints) plus the SoqLightning facade
  • [x] Canary plus offline test suite
  • [x] B1 channel.ts: eLTOO update/settlement TX construction, SIGHASH_ANYPREVOUTANYSCRIPT (0x42), CTV templates, eLTOO/HTLC scripts. Plugs into SoqLightning via txBuilder: new DilithiumEltooBuilder({...}). The sighash and CTV serializers are proven byte-identical to the node (SignatureHash/ComputeCTVHash) via node-dumped vectors. See test/vector.test.mjs.
  • [x] Watchtower client (watchtower.ts): register/unregister/status/channels against soq-lightning-tower. Wires into SoqLightning so pay() arms the tower with state i+1 before returning (spec §1.6 persist, arm, ack; a failed arm fails the payment).
  • [x] Multi-hop HTLC forwarding (htlc.ts): §2.2 HTLC script (SHA-256 hashlock, absolute-CLTV timeout), backward-induction route construction (fees plus per-hop cltv deltas), §5.2 forwarding checks (fee / cltv-delta / invoice-binding, error codes), SUCCESS/TIMEOUT witnesses (plain SIGHASH_ALL). sighashAll and htlcScript() are node-proven byte-exact (test/vector_sighashall.test.mjs). This is the construction layer only; the deployed LSP has no update_add_htlc/update_fulfill_htlc wire yet, so live multi-hop is pending Go-side forwarding. Sphinx onion privacy is deferred per §5.5.
  • [ ] Mainnet (package relay, fee bumping)

Every serialization path the SDK produces is byte-proven against the node: APO 0x42, APO 0x41, CTV hash, SIGHASH_ALL, and htlcScript(). See test/vector.test.mjs and test/vector_sighashall.test.mjs. The only remaining mainnet gate on the SDK is an on-chain tx-graph end-to-end.

Security model

Formally verified (Tamarin plus TLA+) for no-theft-from-payer, balance conservation under reorg, and watchtower liveness SLA. See ~/soqucoin-ops/lightning/ for the protocol spec and proofs. Stagenet only, not audited for mainnet value.

License

MIT