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

@neuraiproject/neurai-scripts

v0.9.2

Published

Script-assembly library for Neurai: opcode constants, ScriptBuilder, standard scripts (P2PKH, P2WPKH, P2WSH, AuthScript, multisig, OP_RETURN) and covenants (partial-fill sell orders, legacy + PQ) targeting the DePIN-Test opcode set

Readme

@neuraiproject/neurai-scripts

Script-assembly library for Neurai. Provides:

  • Core primitives — opcode constants, ScriptBuilder, byte/hex helpers.
  • Standard scripts — P2PKH, P2WPKH, P2WSH, AuthScript (+ witness builders for legacy ECDSA / PQ ML-DSA-44 / NoAuth / NIP-015 ref spends), OP_RETURN null-data, classic m-of-n multisig + P2SH wrapper.
  • Covenants — partial-fill sell-order covenant (legacy and PQ variants) targeting the DePIN-Test opcode set (OP_OUTPUTSCRIPT, OP_OUTPUTVALUE, OP_OUTPUTASSETFIELD, OP_INPUTASSETFIELD, OP_TXFIELD, OP_CHECKTEMPLATEVERIFY, OP_CHECKSIGFROMSTACK, OP_CHAINCONTEXT, 64-bit OP_MUL/OP_SUB, …).

This is the canonical place to add new Neurai script builders; callers compose transactions with @neuraiproject/neurai-create-transaction and sign with @neuraiproject/neurai-sign-transaction.

⚠️ DePIN-Test opcodes, OP_CHECKSIGFROMSTACK, OP_TXHASH, and vrefin are gated by consensus flags active in testnet / regtest today. Mainnet activation follows the fork process described in the NIPs. The library accepts both xna-test and xna networks so the same call sites work once mainnet activates. Here those labels only name the chain (mainnet / testnet-regtest); in neurai-key 5 xna also names the ECDSA witness v3 address type.


Install

npm install @neuraiproject/neurai-scripts

Build from source:

npm install
npm run build
npm test

Three bundles are produced under dist/:

| File | Target | |---|---| | dist/index.js / index.cjs | Node (ESM / CJS) | | dist/browser.js | Browser ESM | | dist/NeuraiScripts.global.js | IIFE global (window.NeuraiScripts) |


Module layout

src/
├── core/                   Low-level primitives
│   ├── opcodes.ts          Opcode bytes + TXFIELD / TXHASH / ASSETFIELD selectors
│   ├── script-builder.ts   CScriptNum encoding, pushBytes/Int/Hex, ScriptBuilder
│   ├── bytes.ts            Hex ↔ Uint8Array helpers (ensureHex, hexToBytes, …)
│   └── index.ts            Barrel
├── standard/               Standard scripts
│   ├── p2pkh.ts            encodeP2PKHScriptPubKey
│   ├── p2wpkh.ts           encodeP2WPKHScriptPubKey
│   ├── p2wsh.ts            encodeP2WSHScriptPubKey
│   ├── authscript.ts       encodeAuthScriptScriptPubKey + witness builders
│   ├── nulldata.ts         encodeNullDataScript (OP_RETURN)
│   ├── multisig.ts         encodeMultisigRedeemScript / encodeP2SHScriptPubKey
│   └── index.ts            Barrel
├── covenants/              Covenant scripts
│   ├── partial-fill/       Legacy (ECDSA cancel) + PQ (ML-DSA-44 via CSFS)
│   └── index.ts            Barrel
├── address.ts              encodeSellerScriptPubKey (bech32m / base58check → spk)
├── types.ts                Public types
├── entries/                Rollup entry points (node / browser / IIFE)
└── index.ts                Public API

All exports are available from the top-level entry:

import { ScriptBuilder, opcodes, encodeP2WPKHScriptPubKey, buildPartialFillScriptHex }
  from '@neuraiproject/neurai-scripts';

Asset-transfer wrappers and NIP-040

splitAssetWrappedScriptPubKey(scriptPubKeyHex) separates a spendable script prefix from its OP_XNA_ASSET <payload> OP_DROP transfer wrapper. It returns the original prefix as prefixHex and either assetTransfer: null or:

{
  assetName: string;
  amountRaw: bigint;
  marker: 'rvn' | 'xna';
  payloadHex: string;
}

NIP-040 changed the marker used by newly created asset outputs from rvn to xna. The parser always accepts both because pre-activation UTXOs remain part of chain history and stay spendable. Parsing therefore needs no network, height, or activation flag; marker reports what was actually encoded.

The helper remains intentionally limited to transfer payloads (0x74). Issuance, owner, and reissue payload types are outside this API and continue to raise not a transfer.


Core primitives

Opcode constants (./core/opcodes.ts)

Complete coverage of the classic Script opcodes (push values, control, stack, arithmetic, bitwise, crypto) plus every DePIN-Test addition (OP_CHECKTEMPLATEVERIFY, OP_CHECKSIGFROMSTACK, OP_TXHASH, OP_TXFIELD, OP_TXLOCKTIME, OP_OUTPUTVALUE, OP_OUTPUTSCRIPT, OP_INPUTCOUNT/OP_OUTPUTCOUNT, OP_OUTPUTASSETFIELD, OP_INPUTASSETFIELD, OP_REFINPUTCOUNT/OP_REFINPUTFIELD/ OP_REFINPUTASSETFIELD, OP_CHAINCONTEXT, OP_CAT, OP_SPLIT, OP_REVERSEBYTES, OP_OUTPUTAUTHDEST, OP_ZKVERIFY, OP_INPUTFIELD, 64-bit OP_MUL/OP_DIV/OP_MOD).

Also exports selector tables:

// For OP_TXFIELD, OP_INPUTFIELD and OP_REFINPUTFIELD
opcodes.TXFIELD_VALUE                  // 0x01
opcodes.TXFIELD_AUTHSCRIPT_COMMITMENT  // 0x02
opcodes.TXFIELD_SCRIPTPUBKEY           // 0x03
opcodes.TXFIELD_AUTHDEST               // 0x04 (NIP-041)

// For OP_OUTPUTASSETFIELD / OP_INPUTASSETFIELD / OP_REFINPUTASSETFIELD
opcodes.ASSETFIELD_NAME                // 0x01
opcodes.ASSETFIELD_AMOUNT              // 0x02
opcodes.ASSETFIELD_UNITS               // 0x03
opcodes.ASSETFIELD_REISSUABLE          // 0x04
opcodes.ASSETFIELD_HAS_IPFS            // 0x05
opcodes.ASSETFIELD_IPFS_HASH           // 0x06
opcodes.ASSETFIELD_TYPE                // 0x07
opcodes.ASSETFIELD_MESSAGE             // 0x08 (NIP-043)

// For OP_CHAINCONTEXT
opcodes.CHAINCONTEXT_HEIGHT            // 0x01
opcodes.CHAINCONTEXT_MTP               // 0x02
opcodes.CHAINCONTEXT_CHAIN_ID          // 0x03

// NIP-042: push every OP_TXHASH mask as two raw LE bytes (0x001..0x1ff).
opcodes.TXHASH_VERSION            // 0x01
opcodes.TXHASH_LOCKTIME           // 0x02
opcodes.TXHASH_INPUT_PREVOUTS     // 0x04
opcodes.TXHASH_INPUT_SEQUENCES    // 0x08
opcodes.TXHASH_OUTPUTS            // 0x10
opcodes.TXHASH_CURRENT_PREVOUT    // 0x20
opcodes.TXHASH_CURRENT_SEQUENCE   // 0x40
opcodes.TXHASH_CURRENT_INDEX      // 0x80
opcodes.TXHASH_REFINPUTS          // 0x100
opcodes.TXHASH_ALL                // 0x1ff

ScriptBuilder (./core/script-builder.ts)

import { ScriptBuilder, opcodes } from '@neuraiproject/neurai-scripts';

const script = new ScriptBuilder()
  .op(opcodes.OP_DUP, opcodes.OP_HASH160)
  .pushBytes(pkh20)
  .op(opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG)
  .build();               // Uint8Array
// or .buildHex() → string

Also exported atomically: pushBytes(data), pushInt(value), pushHex(hex), encodeScriptNum(value).

Push elements up to 3072 bytes (NIP-18 cap when CSFS is active). Larger pushes throw; legacy scripts should stay under 520 bytes per push.

Byte helpers (./core/bytes.ts)

ensureHex, hexToBytes, bytesToHex, concatBytes, bytesEqual.


Standard scripts

All encoders return raw Uint8Array bytes. Caller supplies the pre-hashed inputs where applicable (we don't bake a crypto dep into this library).

P2PKH — encodeP2PKHScriptPubKey(pkh20)

import { encodeP2PKHScriptPubKey } from '@neuraiproject/neurai-scripts';
// OP_DUP OP_HASH160 0x14 <pkh> OP_EQUALVERIFY OP_CHECKSIG (25 bytes)
const spk = encodeP2PKHScriptPubKey(pubKeyHash20);

P2WPKH — encodeP2WPKHScriptPubKey(pkh20)

// OP_0 0x14 <pkh> (22 bytes)
const spk = encodeP2WPKHScriptPubKey(pubKeyHash20);

P2WSH — encodeP2WSHScriptPubKey(witnessScriptSha256)

// OP_0 0x20 <SHA256(witnessScript)> (34 bytes)
// Compute SHA256 via your crypto stack (not bundled here).
const spk = encodeP2WSHScriptPubKey(sha256(witnessScript));

AuthScript — encodeAuthScriptScriptPubKey(program32, witnessVersion?) + witness builders

scriptPubKey layout: OP_n 0x20 <32-byte program>, with the witness version of the address family:

| Witness | Opcode | Address | Spend | |---|---|---|---| | v1 (default) | OP_1 | nc1p… / tnc1p… | generic: any auth type, any witnessScript (covenants) | | v2 | OP_2 | pq1z… / tpq1z… | strict PQ: exactly [0x01, sig, pubKey, OP_TRUE] | | v3 | OP_3 | nq1r… / tnq1r… | strict ECDSA: exactly [0x02, sig, pubKey33, OP_TRUE] |

buildStrictWitnessPQ({ signature, pubKey }) and buildStrictWitnessECDSA({ signature, pubKey }) build the fixed 4-item stacks of the strict families (the PQ key must be the 1313-byte 0x05-prefixed key, the ECDSA key must be compressed). All three families activate on the reset testnet at block 10 and on regtest at block 1.

Generic AuthScript v1 spend modes, selected by a 1-byte auth_type in the witness stack:

Four spend modes, selected by a 1-byte auth_type in the witness stack:

| Constant | Value | Meaning | |---|---|---| | AUTHSCRIPT_NOAUTH | 0x00 | No signature — gated by witnessScript alone | | AUTHSCRIPT_PQ | 0x01 | ML-DSA-44 (post-quantum) | | AUTHSCRIPT_LEGACY | 0x02 | secp256k1 ECDSA | | AUTHSCRIPT_REF | 0x03 | NIP-015 reference-script spend (future) |

Witness-stack builders return Uint8Array[] — the array of raw stack elements. Serialization into the transaction witness (compact-size + length-prefixes) is done at tx-assembly time by neurai-create-transaction.

import {
  encodeAuthScriptScriptPubKey,
  buildAuthScriptWitnessLegacy,
  buildAuthScriptWitnessPQ,
  buildAuthScriptWitnessNoAuth,
  buildAuthScriptWitnessRef
} from '@neuraiproject/neurai-scripts';

// Output spk
const spk = encodeAuthScriptScriptPubKey(program32);

// Legacy spend: [0x02, sig, pubkey, ...args, witnessScript]
const stackLegacy = buildAuthScriptWitnessLegacy({
  signature: derSigWithSighash,
  pubKey: compressedSecp256k1,
  args: [],
  witnessScript
});

// PQ spend (requires NIP-18): [0x01, sig(~2421 B), pubkey(~1313 B), ...args, witnessScript]
const stackPQ = buildAuthScriptWitnessPQ({
  signature: mlDsa44SigWithSighash,
  pubKey: versionedPqPubKey,
  args: [],
  witnessScript
});

// NoAuth (pure covenant): [0x00, ...args, witnessScript]
const stackNoAuth = buildAuthScriptWitnessNoAuth({ args, witnessScript });

// NIP-015 reference-script spend: [0x03, ...args, uint32LE(refIndex)]
const stackRef = buildAuthScriptWitnessRef({ refIndex: 2, args });

Null-data (OP_RETURN) — encodeNullDataScript(payload, options?)

import { encodeNullDataScript } from '@neuraiproject/neurai-scripts';

// OP_RETURN <push>
const spk = encodeNullDataScript(someBytes);

// Multi-push payload
const spk2 = encodeNullDataScript([tagBytes, messageBytes]);

// Oversize (> 80 B) needs an explicit opt-in
const big = encodeNullDataScript(huge, { allowNonStandard: true });

NULLDATA_STANDARD_MAX_SIZE = 80 (Neurai's mempool-relay cap inherited from Bitcoin Core). Outputs over that are consensus-valid but not relayed unless nodes run with -acceptnonstdtxn=1.

Multisig — encodeMultisigRedeemScript + encodeP2SHScriptPubKey

Classic bare multisig is consensus-valid but non-standard for relay in Neurai today. Typical usage wraps the redeem script in P2SH.

import {
  encodeMultisigRedeemScript,
  encodeP2SHScriptPubKey,
  MULTISIG_MAX_PUBKEYS  // 20
} from '@neuraiproject/neurai-scripts';

const redeemScript = encodeMultisigRedeemScript({
  m: 2,
  pubKeys: [pk1, pk2, pk3]     // each 33 or 65 bytes
});
// <2> <pk1> <pk2> <pk3> <3> OP_CHECKMULTISIG

// Wrap in P2SH (callers compute HASH160 externally):
const scriptPubKey = encodeP2SHScriptPubKey(hash160(redeemScript));
// OP_HASH160 0x14 <h160> OP_EQUAL

Covenants

Partial-Fill Sell Order

A partial-fill sell order lets a seller publish a lot of tokens at a fixed price and lets any number of buyers take arbitrary fractions of that lot without the seller having to come back online for each sale. The covenant that guards the seller's output is the only rulebook: the spending transaction is valid only if it pays the seller the correct XNA amount, delivers the right asset quantity to the buyer, and forwards the remainder into a new UTXO carrying the same covenant script. No custodial DEX, no off-chain matching, no second signature from the seller.

The covenant is stateful via UTXO continuity. The output at vout[2] of every fill must carry the same AuthScript v1 commitment as the input being spent (enforced on the fly by OP_OUTPUTAUTHCOMMITMENT == OP_TXFIELD 0x02, NIP-023), so the covenant self-replicates without hardcoding its own hash. Earlier drafts compared full scriptPubKeys via OP_OUTPUTSCRIPT, but that is unsatisfiable on asset-wrapped covenant UTXOs because the remainder's asset wrapper encodes a different amountRaw than the spent UTXO's. The remaining asset quantity is read directly from the input via OP_INPUTASSETFIELD and the subtraction inputAmount − N is verified, so the same script works for any remainder as the lot drains. Asset name, buyer amount, payment destination script and payment value are all checked against the covenant's hardcoded parameters using OP_OUTPUTASSETFIELD, OP_OUTPUTSCRIPT and OP_OUTPUTVALUE. Consensus enforces every rule; there is no trusted off-chain layer.

Three spending branches are selected by the top of the unlock stack:

  • OP_IF (unlock pushed 1) — Cancel. The seller signs and recovers the remainder. The legacy variant uses ECDSA (OP_DUP OP_HASH160 <PKH> OP_EQUALVERIFY OP_CHECKSIG); the PQ variant swaps this for OP_CHECKSIGFROMSTACK against an ML-DSA-44 signature (see PQ variant below).
  • OP_ELSE OP_IF (unlock pushed 1 0) — Full fill. Any buyer can drain the entire covenant in one transaction. The tx has two constrained outputs (vout[0] pays the seller, vout[1] delivers the asset to the buyer) and no vout[2] continuation — the covenant is fully consumed. Required because consensus rejects asset transfers with amount == 0, so a partial fill that would drain the lot is structurally impossible.
  • OP_ELSE OP_ELSE (unlock pushed N 0 0) — Partial fill. Any buyer can take N < total by providing the fill amount; the covenant's vout[2] re-locks the remainder (total − N units) into an identical covenant UTXO. No seller signature is involved.

This maps naturally onto Neurai's mempool: multiple buyers can submit their fill transactions in sequence, each one building on the previous fill's vout[2] before confirmation. Neurai's default descendant limit of 500 allows long chains of pending fills against a single published order, and the whole chain confirms together when the next block is mined. If the seller wants to stop the order at any point, she spends the current live UTXO via the cancel branch.

Orders can also carry an optional expiry. When expiration is set, both buyer fill branches start with:

<expiration.value> <HEIGHT|MTP> OP_CHAINCONTEXT OP_GREATERTHAN OP_VERIFY

So fills are valid only while the configured height or median-time-past deadline is still greater than the chain context. At the deadline block/time and after it, buyer fills fail and the seller can recover the live order via the cancel branch.

Alice / Bob / Carol flow

  1. Alice locks 100 CAT in a covenant UTXO (buildPartialFillScript). Price 1 XNA / CAT, P2PKH as payment destination.
  2. Bob spends the covenant pushing <5> <0> as scriptSig and his XNA input. The script enforces:
    • output[0] pays Alice ≥ 5 * unitPriceSats XNA
    • output[1] is a transfer of 5 CAT to Bob
    • output[2] is a new covenant UTXO with the same scriptPubKey and 95 CAT
  3. Carol does the same on the new UTXO, leaving 90 CAT behind.
  4. Alice signs the cancel branch on the latest UTXO and recovers her remaining 90 CAT. She has already received 10 XNA across Bob's and Carol's fills.

No second signature from Alice, no custodial server: the covenant script is the rulebook.

Usage (legacy ECDSA cancel)

import {
  buildPartialFillScriptHex,
  buildFillScriptSigHex,
  buildCancelScriptSigHex,
  parsePartialFillScript
} from '@neuraiproject/neurai-scripts';

// 1) Alice publishes an order for 100 CAT at 1 XNA per CAT.
const scriptPubKeyHex = buildPartialFillScriptHex({
  sellerAddress: aliceP2PKHAddress,  // "t..." on testnet / "N..." on mainnet
  tokenId: 'CAT',
  unitPriceSats: 100_000_000n,       // 1 XNA = 1e8 sats per indivisible unit
  expiration: {
    mode: 'height',                  // or 'mtp' for median-time-past seconds
    value: 1_250_000n                // fills are rejected at/after this value
  }
});
// Throws if the address is not a legacy P2PKH — the legacy covenant uses
// OP_HASH160 + OP_CHECKSIG on the cancel branch and cannot commit to an
// AuthScript program. Use `buildPartialFillScriptPQ` for bech32m / PQ
// destinations.

// 2) Bob spends 5 CAT from the covenant.
const bobScriptSigHex = buildFillScriptSigHex(5n);
// Fill-tx layout (built with neurai-create-transaction):
//   vin[0]   = covenant UTXO, scriptSig = bobScriptSigHex
//   vin[1+]  = buyer's XNA inputs (standard P2PKH, SIGHASH_ALL)
//   vout[0]  = unitPriceSats * 5 XNA to Alice
//   vout[1]  = 5 CAT to Bob
//   vout[2]  = 95 CAT back to the SAME covenant scriptPubKey
//   vout[3+] = buyer change (optional)

// 3) Alice cancels on the latest remainder UTXO.
const aliceCancelSig = /* produced by neurai-sign-transaction, SIGHASH_ALL */;
const cancelScriptSigHex = buildCancelScriptSigHex(aliceCancelSig, alicePubKey);

// 4) The DEX backend indexes active orders by parsing scriptPubKeys.
for (const utxo of candidateUtxos) {
  try {
    const order = parsePartialFillScript(utxo.scriptPubKeyHex, 'xna-test');
    // order.tokenId, order.unitPriceSats, order.sellerPubKeyHash,
    // order.expiration, order.network
    // The parser returns the 20-byte PKH; base58check-encode with the
    // network's legacy prefix (0x35 mainnet / 0x7f testnet) if you need
    // to display the seller's address.
  } catch {
    // not a partial-fill covenant, skip
  }
}

Script layout (legacy, three-branch)

OP_IF                                                              // cancel branch
  OP_DUP OP_HASH160 <sellerPKH> OP_EQUALVERIFY OP_CHECKSIG
OP_ELSE
  OP_IF                                                            // full-fill branch
    # Stack entering: [ ]  (scriptSig pushed <1> <0>)
    # Optional expiry: <deadline> <HEIGHT|MTP> OP_CHAINCONTEXT OP_GREATERTHAN OP_VERIFY
    <0> <0x02> OP_INPUTASSETFIELD                                  // N = inputAmount
    OP_DUP <unitPriceSats> OP_MUL
        <0> OP_OUTPUTVALUE OP_SWAP OP_GREATERTHANOREQUAL OP_VERIFY // payment value
    <0> OP_OUTPUTSCRIPT <sellerP2PKH> OP_EQUALVERIFY               // payment dest
    OP_DUP <1> <0x02> OP_OUTPUTASSETFIELD OP_EQUALVERIFY           // buyer amount == N
    <1> <0x01> OP_OUTPUTASSETFIELD <tokenId> OP_EQUALVERIFY        // buyer name
    OP_DROP OP_1                                                   // no vout[2] required
  OP_ELSE                                                          // partial-fill branch
    # Stack entering: [ N ]  (scriptSig pushed <N> <0> <0>)
    # Optional expiry: <deadline> <HEIGHT|MTP> OP_CHAINCONTEXT OP_GREATERTHAN OP_VERIFY
    OP_DUP <unitPriceSats> OP_MUL
        <0> OP_OUTPUTVALUE OP_SWAP OP_GREATERTHANOREQUAL OP_VERIFY // payment value
    <0> OP_OUTPUTSCRIPT <sellerP2PKH> OP_EQUALVERIFY               // payment dest
    OP_DUP <1> <0x02> OP_OUTPUTASSETFIELD OP_EQUALVERIFY           // buyer amount == N
    <1> <0x01> OP_OUTPUTASSETFIELD <tokenId> OP_EQUALVERIFY        // buyer name
    <2> OP_OUTPUTAUTHCOMMITMENT <0x02> OP_TXFIELD OP_EQUALVERIFY   // continuity (NIP-023)
    <2> <0x01> OP_OUTPUTASSETFIELD <tokenId> OP_EQUALVERIFY        // remainder name
    <2> <0x02> OP_OUTPUTASSETFIELD OP_OVER
        <0> <0x02> OP_INPUTASSETFIELD OP_SWAP OP_SUB OP_EQUALVERIFY // remainder = in - N
    OP_DROP OP_1
  OP_ENDIF
OP_ENDIF

Limitations of v0.1

  • Payment only in XNA. A PaymentAsset selector is planned for v0.2.
  • No MinFill / MaxFill / Expiry. Planned for v0.2.
  • Fixed output layout (vout[0..2]). The covenant rejects reorderings. Buyer change must go to vout[3+].
  • AMM is out of scope. Covered by a later phase of the NIP roadmap.

Partial-Fill PQ variant (post-quantum seller, NIP-18)

With OP_CHECKSIGFROMSTACK (CSFS) and OP_TXHASH active, the cancel branch can validate an ML-DSA-44 signature in place of the classic ECDSA OP_CHECKSIG. NIP-18 raises the per-element push cap to 3072 B so the ~2421 B signature and ~1313 B pubkey fit as stack items. All three are active on DePIN-Test testnet.

| | Legacy | PQ | |---|---|---| | Cancel sig scheme | ECDSA secp256k1 | ML-DSA-44 (~2421 B sig, ~1313 B pubkey) | | Cancel verifier | OP_DUP OP_HASH160 <PKH> OP_EQUALVERIFY OP_CHECKSIG | OP_DUP OP_SHA256 <commitment> OP_EQUALVERIFY <selector> OP_TXHASH OP_SWAP OP_CHECKSIGFROMSTACK | | What Alice signs | tx sighash (standard) | SHA256(OP_TXHASH(selector)) — CSFS single-SHA256s the message | | Payment destination | P2PKH only | P2PKH or any AuthScript bech32m address — nc1p v1, pq1z v2, nq1r v3 (scriptPubKey bytes hardcoded, OP_1/OP_2/OP_3 included) |

import {
  buildPartialFillScriptPQHex,
  buildCancelScriptSigPQHex,
  parsePartialFillScriptPQ,
  isPartialFillScriptPQ,
  DEFAULT_PQ_TXHASH_SELECTOR
} from '@neuraiproject/neurai-scripts';

const scriptPubKeyHex = buildPartialFillScriptPQHex({
  network: 'xna-test',
  paymentAddress: 'tpq1z...',              // tnc1p… / tpq1z… / tnq1r… bech32m, or legacy "t..." P2PKH
  pubKeyCommitment: sha256(alicePQPubKey), // 32 bytes
  tokenId: 'CAT',
  unitPriceSats: 100_000_000n,
  txHashSelector: DEFAULT_PQ_TXHASH_SELECTOR  // 0x1ff = all nine fields
});

const cancelScriptSigHex = buildCancelScriptSigPQHex(mlDsa44SigWithSighash, alicePQPubKey);

// Indexers can tell variants apart:
if (isPartialFillScriptPQ(utxo.scriptPubKeyHex)) {
  const order = parsePartialFillScriptPQ(utxo.scriptPubKeyHex);
}

PQ cancel-branch layout

OP_IF                                // scriptSig pushed <sig> <pubKey> OP_1
  OP_DUP OP_SHA256
  <pubKeyCommitment 32B>
  OP_EQUALVERIFY
  <txHashSelector uint16LE, exactly 2 bytes>
  OP_TXHASH                          // msg = tagged NeuraiTxHash(mask || fields)
  OP_SWAP
  OP_CHECKSIGFROMSTACK               // verifies sig over SHA256(msg) for pubKey
OP_ELSE
  ... partial-fill branch (identical to legacy) ...
OP_ENDIF

Network handling

Builders take address strings (whose prefix already encodes the network: t.../N... for P2PKH; tnc1p/nc1p (v1), tpq1z/pq1z (v2) and tnq1r/nq1r (v3) for AuthScript bech32m) and validate them via decodeAddress from neurai-create-transaction. No builder accepts a separate network parameter — it would be redundant with the address and invite silent mismatches.

Parsers, on the other hand, do take a network argument (default 'xna-test'). The covenant script bytes themselves do not encode network: a legacy covenant is opcodes plus a 20-byte PKH, a PQ covenant is opcodes plus a 32-byte commitment and raw payment-scriptPubKey bytes, and neither payload differs between mainnet and testnet. The parser receives network purely so the caller can later base58-encode sellerPubKeyHash against the right version byte (a Legacy address, neurai-key xna-legacy / xna-legacy-test), or interpret paymentScriptPubKey in the correct bech32m/base58 context.

Keep this split intact when adding new covenants: builders reject a network field, parsers echo it through.


Consensus / policy notes

  • DePIN-Test opcodes are consensus-gated. Each one is behind an individual flag (nCTVEnabled, nCSFSEnabled, nTXHASHEnabled, nTXFIELDEnabled, nOUTPUTVALUEEnabled, nOUTPUTSCRIPTEnabled, nOUTPUTASSETFIELDEnabled, nINPUTASSETFIELDEnabled, nINPUTOUTPUTCOUNTEnabled, nREFINPUTSEnabled, nCATEnabled, nSPLITEnabled, nREVERSEBYTESEnabled, n64BitIntegersEnabled, nTXLOCKTIMEEnabled, nPQWitnessEnabled). All are true on testnet and regtest, false on mainnet until each NIP's fork process lands.
  • NIP-18 per-element cap (3072 B) is also a mempool-policy win. Neurai's MAX_CSFS_STANDARD_P2WSH_STACK_ITEM_SIZE equals the consensus cap whenever CSFS is active, so PQ witness items are standard on the testnet today — no -acceptnonstdtxn=1 required. The legacy 80 B cap only applies when CSFS is off.
  • Commitment choice (PQ). The PQ covenant commits to SHA256(pubKey), not the bech32m AuthScript program. That keeps the covenant decoupled from AuthScript address derivation and lets the caller use any PQ key whose SHA256 they can compute.

Adding new scripts to this library

The layout under src/standard/ and src/covenants/ is designed for expansion. When adding a new primitive:

  • Standard scripts (P2*, OP_RETURN, new address formats, …) go under src/standard/. Keep one file per scheme and export from src/standard/index.ts.
  • Covenants (batch-fill, shared-claim, HTLC, vaults, AMM, …) go under src/covenants/<name>/ with the same script.ts / spend.ts / parse.ts split the partial-fill module uses. Re-export from src/covenants/index.ts.
  • Reference-script carriers / NIP-015 helpers will land in a future src/reference-scripts/ directory when the NIP activates.

The top-level src/index.ts barrel should re-export everything publicly consumable; callers import directly from the package root.


Version notes

0.9.2

Requires @neuraiproject/neurai-create-transaction ^0.9.2 so new installations use the reset-testnet activation height and network metadata. The asset-wrapper fixtures from the previous testnet remain historical codec examples; live transactions must use the asset marker returned by the node for the candidate block. NIP-041/043 and NIP-018 opcode constants and selectors match the reset node. PQ cancel covenants now push a two-byte NIP-042 mask; the default 0x1ff also commits reference inputs. Historical one-byte masks are rejected by the PQ covenant parser because they cannot execute under the current OP_TXHASH rule.

0.9.1

TypeScript declarations for CommonJS. require() loads dist/index.cjs, but the package only published ESM declarations, so a CommonJS file compiled with moduleResolution: "node16" got TS1471 and no types. Each condition of exports now has its own declarations (import → dist/index.d.ts, require → dist/index.d.cts). No runtime or API change. npm run test:types compiles ESM, CommonJS and browser consumers against the built declarations (NodeNext, Node16, Bundler; skipLibCheck: false) and npm run test:package checks the packed tarball in a clean project, with TypeScript 4.7 too.

0.9.0

Address types of neurai-key 5 / neurai-create-transaction 0.9.0.

  • encodeSellerScriptPubKey keeps the witness version of the address: pq1z… payment addresses are hardcoded as OP_2 <32B> and nq1r… as OP_3 <32B> (a same-program OP_1 script would be another destination). SellerAddressKind gains 'pq' and 'ecdsa', and the result carries witnessVersion. Code that switched on kind === 'authscript' to mean "any bech32m destination" must handle the new kinds.
  • Generic AuthScript v1 addresses are nc1p… / tnc1p…. The pre-5.0 nq1p… / tnq1p… strings are rejected (by create-transaction's decodeAddress), like in the node.
  • encodeAuthScriptScriptPubKey(program, witnessVersion = 1); new buildStrictWitnessPQ / buildStrictWitnessECDSA for the strict families.
  • Requires @neuraiproject/neurai-create-transaction ^0.9.0 (breaking release, not matched by the previous ^0.8.1 range).

License

MIT — same terms as the rest of the @neuraiproject/* libraries.

Large numeric script values

Use bigint for ScriptNum values above Number.MAX_SAFE_INTEGER. Numeric inputs must be safe integers; unsafe numbers are rejected before encoding. Covenant amounts and prices remain bigint. The script interpreter's own size and arithmetic limits still apply; encoding an integer does not guarantee a script will pass consensus checks.