@bsv/templates

中文文档
2.2.0发布于 2026/10/30SEE LICENSE IN LICENSE.txtTypeScriptESM + CJS零依赖构建来源已验证近 30 天下载 2338

BSV Blockchain Script Templates

原始使用文档
文档目录12 节
  1. Overview
  2. Using
  3. Current Templates
  4. Signing trust boundary
  5. BRC-162 tokens: Bsv21Binary
  6. 1Color payload attributes
  7. Strict CBOR payloads
  8. R1-K1 hardware wallet
  9. Contribution Guidelines
  10. Support & Contacts
  11. License
  12. SDK4 hotfix peer requirement

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

NameDescription
Bsv21BinaryLock and decode BRC-162 (BSV-21 binary) token outputs
OneColorLock, decode and read BRC-162 1Color token outputs
OpReturnTag data in a non-spendable script
MetantCreate transactions that follow the Metanet protocol
MultiPushDropCreate data tokens with multiple trusted owners
P2MSKHSpend with an M-of-N public-key threshold
R1K1WalletUse 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.

版本与发布记录

@bsv/templates 共发布 28 个版本,首次发布于 2024/05/30;过去 12 个月发布了 21 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
2.2.0latest2026/10/309 天前
2.1.12026/10/2811 天前
2.1.02026/10/2416 天前
2.0.02026/10/031 个月前
1.10.32026/09/241 个月前
1.10.22026/09/231 个月前

关于 @bsv/templates 的常见问题

@bsv/templates 是什么?

BSV Blockchain Script Templates

如何安装 @bsv/templates?

在项目目录运行 npm install @bsv/templates,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@bsv/sdk(^2.1.6 || ^3.0.0 || ^4.0.1)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@bsv/templates 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bsv/templates 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@bsv/templates 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@bsv/templates 可以商用吗?

@bsv/templates 使用 SEE LICENSE IN LICENSE.txt 许可。许可条款写在包内的独立文件中,请阅读该文件确认可否商用。以上为概括,不构成法律意见,请以许可原文为准。

@bsv/templates 的下载量有多少?

根据 npm 官方下载统计,@bsv/templates 在 2026/10/09 – 2026/11/07 这 30 天内被下载 2,338 次,日均约 78 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bsv/templates 还在维护吗?

最新版本 2.2.0 发布于 2026/10/30(9 天前);至今共发布 28 个版本,其中过去 12 个月发布了 21 个;首次发布于 2024/05/30。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。