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

@bsv/templates

v2.2.0

Published

BSV Blockchain Script Templates

Readme

BSV Script Templates

Candidate 2.2.0 adds the 1Color attribute reader (readOneColorAttributes, reqMatchesDeploy), OneColor.lockRemainder and the 36-byte-aware oneColorTokenId* helpers on top of published 2.1.1 (the OneColor BRC-162 template and SDK4 peer support). Existing templates, wire formats and defaults remain supported. The additive APIs and migration guidance are documented below.

BSV BLOCKCHAIN | Script Templates

A collection of script templates for use with the official BSV TypeScript SDK

Overview

The goal of this repository is to provide a place where developers from around the ecosystem can publish all manner of script templates, without needing to update the core library. We're generally neutral and unbiased about what people contribute, so feel free to contribute and see what people do with your cool idea!

Using

You can write code like this:

import { Transaction } from '@bsv/sdk'
import { OpReturn } from '@bsv/templates'

// Then, just use your template with the SDK!
const instance = new OpReturn()
const tx = new Transaction()
tx.addOutput({
  lockingScript: OpReturn.lock(...),
  satoshis: ...
})

Current Templates

| Name | Description | | --------------------------------------- | ----------------------------------------------------- | | Bsv21Binary | Lock and decode BRC-162 (BSV-21 binary) token outputs | | OneColor | Lock, decode and read BRC-162 1Color token outputs | | OpReturn | Tag data in a non-spendable script | | Metant | Create transactions that follow the Metanet protocol | | MultiPushDrop | Create data tokens with multiple trusted owners | | P2MSKH | Spend with an M-of-N public-key threshold | | R1K1Wallet | Use P-256 hardware normally and a K1 recovery key |

Signing trust boundary

Signing templates validate the source transaction ID, output index, satoshi amount, locking script, sequence, and signature scope before requesting a wallet signature. When both a complete source transaction and explicit source metadata are supplied, they must agree exactly. Treat validation failures as a source-provenance or transaction-construction error; do not retry by discarding one of the conflicting values.

MultiPushDrop signs only its complete canonical contract shape and supports at most 120 distinct compressed locking keys. Its documented trusted-owner model still applies: any one owner can spend or destroy the token.

P2MSKH signs only when the ordered public-key list hashes to the commitment in the source locking script and the wallet-derived signing key belongs to that list. Preserve the same ordered list while gathering incremental signatures; an address, key list, or partial unlocking script from an untrusted party is validated but is not itself proof that the intended payment policy is safe.

BRC-162 tokens: Bsv21Binary

OneColor is the BRC-162 1Color template. It reads and writes the same outputs as Bsv21Binary and also the 36-byte token id of an asset first deployed under BRC-161 at a nonzero output (<txid>_<vout>); a 36-byte id with vout 0 is rejected. Bsv21Binary is unchanged and still accepts only 32-byte ids.

Bsv21Binary builds and reads BRC-162 (BSV-21 binary) token outputs. A token output is a fixed prefix on an ordinary locking script:

<token id | OP_0> <amount> OP_2DROP [<payload> OP_DROP] OP_DUP OP_HASH160 <pkh> OP_EQUALVERIFY OP_CHECKSIG
import { PrivateKey } from '@bsv/sdk'
import { Bsv21Binary, encodeStrictCbor, tokenIdFromString, tokenIdToString } from '@bsv/templates'

const owner = PrivateKey.fromRandom()
const pubKeyHash = owner.toPublicKey().toHash() as number[]

// A deploy output has a null token id; its payload is a strict-CBOR map.
const template = new Bsv21Binary()
const deployScript = template.lock(null, 0n, pubKeyHash, encodeStrictCbor({ sym: 'USD', dec: 2n }))
console.log(Bsv21Binary.decode(deployScript).role) // 'deploy'

// Later outputs name the token by `<deploy txid, 64 lowercase hex>_0`.
const tokenId = `${'ab'.repeat(32)}_0`
const lockingScript = template.lock(tokenId, 1_000_000n, pubKeyHash) // amounts are bigint

const decoded = Bsv21Binary.decode(lockingScript)
console.log(decoded.role) // 'value' ('authority' when the amount is 0n)
console.log(decoded.amount) // 1000000n
console.log(decoded.tokenId !== undefined && tokenIdToString(decoded.tokenId) === tokenId) // true
console.log(tokenIdFromString(tokenId).length) // 32 wire bytes, natural order

Every value has exactly one accepted encoding, so every engine that reads the same bytes reads the same token output:

  • Token id: a direct push of exactly 32 bytes (the deploy txid in natural byte order), or OP_0 on a deploy. OP_PUSHDATA* is not accepted.
  • Amount: bigint, 0 to 2^64 - 1. 0 is OP_0, 1 to 16 are OP_1 to OP_16, anything larger is a direct push (0x01 to 0x09) of the minimal little-endian script number. Policy caps below 2^64 - 1, aggregate arithmetic and conservation across inputs and outputs belong to the caller.
  • Role: deploy (no id), authority (id, amount 0) or value.
  • Invalid vs. not a token: isTokenShaped(script) tells a script that starts <push> <push> OP_2DROP from any other script. Bsv21Binary.decode throws Bsv21BinaryError for a token-shaped script that is not canonical (for example amount 5 pushed as 01 05), so it is never silently read as "not a token".

1Color payload attributes

readOneColorAttributes(payload) reads the payload of a 1Color output under the BRC-207 wallet rules. It is pure and never throws, whatever it is given. payload says what the output carries:

  • 'absent': no payload push;
  • 'empty': a zero-byte payload (OP_0 OP_DROP);
  • 'opaque': nonzero bytes that are not a strictly valid DAG-CBOR map (trailing bytes, unsorted or duplicate keys, a non-map, bytes outside 0..255). Nothing is recovered from it;
  • 'map': a strictly valid DAG-CBOR map, also returned as map.

Each reserved field (dec, sym, icon, req, ext) is { status: 'absent' }, { status: 'valid', value } or { status: 'malformed', reason }. Every field is 'absent' unless payload is 'map'. In a map, a reserved key that is present but ill-shaped is 'malformed', never 'absent':

  • dec: a CBOR unsigned integer 0 to 18. A float, even 2.0, is malformed;
  • sym: text;
  • icon: exactly [mediaType text, bytes]. A display treats a malformed icon as absent;
  • req: a map from processing-profile ids (isProfileId) to exactly { params, version }, version a CBOR unsigned integer 1 to 2^32 - 1 and params any value, including null. reqBytes is the exact wire span of the req value, set only when req is valid;
  • ext: a map from processing-profile ids to any value.

reqMatchesDeploy(deploy, later) checks that a later output repeats the deploy's req. It fails closed, in this order: a malformed deploy req gives false for every later output; a later output without req gives true; a malformed later req gives false; otherwise the canonical DAG-CBOR bytes must be equal, and a deploy without req counts as the empty map a0. Bytes are compared as written, so params: 1.0 never matches params: 1.

OneColor.lockRemainder(tokenId, amount, remainder, payload?) puts the 1Color prefix in front of any locking script. With no payload and a remainder that starts with <push> OP_DROP, it writes the empty-payload marker OP_0 OP_DROP first (BRC-162), so the remainder's first push is never read as a payload. A push here is OP_0, OP_1NEGATE, OP_1 to OP_16 or any data push. needsEmptyPayloadMarker(remainder.chunks) tells whether it will.

New 1Color code should prefer oneColorTokenIdFromString and oneColorTokenIdToString, which accept 32- and 36-byte ids. tokenIdFromString and tokenIdToString accept 32-byte (_0) ids only.

import { PrivateKey } from '@bsv/sdk'
import {
  OneColor,
  encodeStrictCbor,
  oneColorTokenIdFromString,
  readOneColorAttributes,
  reqMatchesDeploy
} from '@bsv/templates'

const pubKeyHash = PrivateKey.fromRandom().toPublicKey().toHash() as number[]
const template = new OneColor()

const deployScript = template.lock(null, 0n, pubKeyHash, encodeStrictCbor({ sym: 'USD', dec: 2n }))
const deploy = readOneColorAttributes(OneColor.decode(deployScript).payload)
if (deploy.dec.status === 'valid') console.log(deploy.dec.value) // 2

const tokenId = `${'ab'.repeat(32)}_1` // a BRC-161 asset deployed at output 1
console.log(oneColorTokenIdFromString(tokenId).length) // 36
const later = readOneColorAttributes(
  OneColor.decode(template.lock(tokenId, 5n, pubKeyHash)).payload
)
console.log(later.payload) // 'absent'
console.log(reqMatchesDeploy(deploy, later)) // true: the later output omits req

Strict CBOR payloads

Payload attributes are read from a small, dependency-free subset of DAG-CBOR, so two decoders cannot read different attributes from the same bytes. encodeStrictCbor writes it, decodeStrictCbor throws StrictCborError and tryDecodeStrictCbor returns undefined for anything outside it:

  • the top level is a map with text keys, strictly increasing by encoded bytes (no duplicates);
  • values are unsigned integers up to 2^64 - 1 (bigint), byte strings (Uint8Array), strict UTF-8 text, null, booleans and nested maps;
  • headers are definite and minimal-length;
  • nesting depth is at most STRICT_CBOR_MAX_DEPTH (4), the encoding is at most STRICT_CBOR_MAX_BYTES (4096) and there are no trailing bytes.

Floats, tags (including 42), negative integers, arrays and indefinite lengths are refused.

Version 2.0.0 removes MandalaToken, MandalaAdmin and ADMIN_PROTOCOL; BRC-162 Bsv21Binary outputs replace them and are not wire-compatible.

R1-K1 hardware wallet

R1K1Wallet commits to HASH160(compressedR1PublicKey || privateSalt) and a separate secp256k1 public-key hash. The salt hides reuse of a PIV public key until an R1 spend reveals both values. Keep every 32-byte salt backed up with the wallet metadata; losing it disables that output's R1 path but not K1 recovery.

import { Hash, type PrivateKey, Utils } from '@bsv/sdk'
import { R1K1Wallet } from '@bsv/templates'

declare const compressedP256PublicKeyHex: string
declare const k1RecoveryPrivateKey: PrivateKey
declare const signWithYubiKeyPiv: (digest: Uint8Array) => Promise<Uint8Array>

const template = new R1K1Wallet()
const r1PublicKey = Utils.toArray(compressedP256PublicKeyHex, 'hex')
const salt = crypto.getRandomValues(new Uint8Array(32))
const lockingScript = await template.lock(
  Hash.hash160([...r1PublicKey, ...salt]),
  Hash.hash160(k1RecoveryPrivateKey.toPublicKey().encode(true) as number[])
)

const normalSpend = template.unlock({
  path: 'r1',
  publicKey: r1PublicKey,
  salt,
  // Submit this 32-byte digest unchanged to PIV GENERAL AUTHENTICATE.
  // Return the YubiKey DER ECDSA signature (or raw 64-byte r || s).
  signDigest: digest => signWithYubiKeyPiv(digest)
})

const recoverySpend = template.unlock({
  path: 'k1',
  privateKey: k1RecoveryPrivateKey
})

The synthesized P-256 verifier produces a 959,632-byte locking script after constructor commitments are baked. This exceeds common 500 KB miner policy; confirm the target miner's limits before funding an output.

An R1 unlocking script also pushes the BIP-143 preimage, whose scriptCode contains roughly 960 KB of the contract after OP_CODESEPARATOR. The R1 path therefore involves about 2 MB of locking-plus-unlocking script material, and the witness alone adds roughly 960 KB to the spending transaction. Account for the resulting fees and confirm any maximum-transaction policy; estimateLength includes this preimage push. The K1 unlocking script remains small.

PIV proves that the hardware key signed the supplied digest, but a YubiKey does not display or independently validate the Bitcoin transaction. A PIN or touch policy protects key use, not transaction intent; review transactions on a trusted host before approving them.

Contribution Guidelines

We're always looking for contributors to add the coolest new templates. Whatever kinds of scripts you come up with - all contributions are welcome.

  1. Fork & Clone: Fork this repository and clone it to your local machine.
  2. Set Up: Run npm i to install all dependencies.
  3. Make Changes: Create a new branch and make your changes.
  4. Test: Ensure all tests pass by running npm test.
  5. Commit: Commit your changes and push to your fork.
  6. Pull Request: Open a pull request from your fork to this repository. For more details, check the repository contribution guidelines.

For information on past releases, check out the changelog. For future plans, check the roadmap!

Support & Contacts

Project Owners: Ty Everett

Development Team Lead: Ty Everett

For questions, bug reports, or feature requests, please open an issue on GitHub or contact us directly.

License

Current TS Stack changes are licensed under the Open BSV License Version 6; see LICENSE.txt. This package also retains pre-uniformization code under the Open BSV License Version 4. Redistributors must preserve THIRD_PARTY_NOTICES.md and the applicable text in LICENSES/.

Thank you for being a part of the BSV Blockchain Script Templates Project. Let's build the future of BSV Blockchain together!

SDK4 hotfix peer requirement

The SDK4 peer alternative starts at @bsv/sdk ^4.0.1, which restores numeric wallet results and legacy wire compatibility. Existing SDK2/3 alternatives and lower bounds remain unchanged. SDK4 users should install the 4.0.1 hotfix; no application-wallet wire or data migration is required.