@bsv/gasp

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

Graph Aware Sync Protocol

原始使用文档
文档目录18 节
  1. Table of Contents
  2. Key Features
  3. How it Works
  4. Installation
  5. Quick Start
  6. 1. Implement the GASPStorage Interface
  7. 2. Implement (or Obtain) a GASPRemote
  8. 3. Initialize and Sync
  9. Examples
  10. Minimal Example
  11. Advanced Example: sequential and Log Levels
  12. Unidirectional Pull-Only Sync
  13. Dealing With “Deep” Transactions and Metadata
  14. Useful Links
  15. Development and Distribution
  16. FAQ
  17. License
  18. SDK4 hotfix peer requirement

GASP — Graph Aware Sync Protocol

Patch candidate 1.3.10 updates packed first-party runtime or development dependency ranges. This metadata-only patch preserves public APIs, peer ranges, wire formats and defaults. No additional consumer or data migration is required; earlier migration guidance still applies.

The Graph Aware Sync Protocol (GASP) is a powerful protocol for synchronizing BSV transaction data between two or more parties. Unlike simplistic “UTXO list” or “transaction pushing” mechanisms, GASP allows each participant to incrementally build a graph of transaction ancestors and descendants. This ensures:

  1. Legitimacy: Parties only finalize data they can validate, using Merkle proofs, script evaluation, and the other rules of SPV.
  2. Completeness: Recursively, each party pulls in the inputs needed to prove correctness—avoiding partial or “broken” transaction data.
  3. Efficiency: Each participant only fetches and transmits data it doesn’t already have, minimizing bandwidth.
  4. Flexibility: Custom storage, custom remote mechanisms, unidirectional sync, concurrency options, and more.

Table of Contents


Key Features

  • Recursive Sync: Fetches only the needed transaction outputs and recursively fetches input data on demand.
  • Metadata Support: Optionally exchange metadata (e.g., invoice data, descriptions, basket or topical membership, etc.) for each transaction or output.
  • Proof Anchoring: Merkle proofs can be attached to each transaction, ensuring on-chain verifiability.
  • Unidirectional: If desired, you can configure “pull only” mode—where you fetch data from a remote but never push your own.
  • Selective Concurrency: Use bounded parallel fetches or sequential fetches (one at a time) to avoid potential DB locking.
  • Flexible Integration: The GASPStorage and GASPRemote interfaces let you integrate with your own storage logic or remote transport.

How it Works

  1. Initial Request: One peer initiates a request, including a timestamp for when the two parties last synchronized.
  2. Initial Response: The other peer returns a set of UTXOs that it has observed since that timestamp, plus a “since” timestamp for a potential “reply.”
  3. Recursive Graph Building:
    • Each side requests the transaction data (optionally including metadata) for each unknown UTXO.
    • Each newly-received transaction might contain additional unknown inputs, which triggers further fetches.
  4. Graph Finalization: Once all required inputs are fetched, each peer finalizes the newly-validated transaction data into its own store.
  5. Optional “Reply”: In a bidirectional scenario, the second peer then does the same, ensuring both end up with a consistent set of data.

If you set GASP to unidirectional, step 5 is skipped: your local node simply pulls data from the remote, but never sends data back.

Bidirectional parent assumption: the v1 submitNode(node) message does not carry the spentBy parent outpoint. A receiver of a pushed child must therefore already have the parent needed to validate that edge. If it does not, it must request the parent in a subsequent synchronization round or reject the graph. Pull-only mode avoids that assumption because the requester already knows the exact parent input it requested. Never accept an unknown parent edge merely because a peer submitted the child.


Installation

npm i @bsv/gasp

Or, if you use yarn:

yarn add @bsv/gasp

Quick Start

Below is a bare-bones recipe to get GASP up and running.

1. Implement the GASPStorage Interface

The GASPStorage interface is your local “database layer.” It controls how you store UTXOs, how you retrieve them, and how you handle partial transaction graphs.

import { GASPNode, GASPNodeResponse, GASPStorage } from '@bsv/gasp'

export class MyCustomStorage implements GASPStorage {
  async findKnownUTXOs(since: number) {
    /* return an array of unspent TXID-outputIndices since `since` timestamp */
  }
  async hydrateGASPNode(graphID: string, txid: string, outputIndex: number, metadata: boolean) {
    /* return the GASPNode with rawTx, proof, metadata, etc. */
  }
  async findNeededInputs(tx: GASPNode): Promise<GASPNodeResponse | void> {
    /* optionally request more inputs if needed*/
  }
  async appendToGraph(tx: GASPNode, spentBy?: string) {
    /* store the node in some temporary graph structure*/
  }
  async validateGraphAnchor(graphID: string) {
    /* confirm the graph is anchored in the blockchain or otherwise valid*/
  }
  async discardGraph(graphID: string) {
    /* if invalid, discard it */
  }
  async finalizeGraph(graphID: string) {
    /* finalize the validated graph into local storage*/
  }
}

2. Implement (or Obtain) a GASPRemote

A GASPRemote is how you communicate with a remote GASP peer. You can implement your own HTTP fetch logic, use a WebSocket-based approach, or even run everything in the same process for testing.

import { GASPRemote, GASPNode, GASPInitialRequest, GASPInitialResponse } from '@bsv/gasp'

export class MyRemote implements GASPRemote {
  async getInitialResponse(request: GASPInitialRequest): Promise<GASPInitialResponse> {
    // Call remote peer and return their data
    // ...
  }
  async getInitialReply(response: GASPInitialResponse) {
    // Only needed if doing bidirectional sync
    // ...
  }
  async requestNode(
    graphID: string,
    txid: string,
    outputIndex: number,
    metadata: boolean
  ): Promise<GASPNode> {
    // Request a node from the remote
    // ...
  }
  async submitNode(node: GASPNode) {
    // In a bidirectional sync, we push our node to the remote
    // ...
  }
}

3. Initialize and Sync

Finally, create the GASP instance, and call sync():

import { GASP, LogLevel } from '@bsv/gasp'

// 1. Create your storage
const myStorage = new MyCustomStorage()

// 2. Create your remote (or re-use an existing GASP instance as the remote)
const myRemote = new MyRemote()

// 3. Instantiate GASP
const gasp = new GASP(
  myStorage,
  myRemote,
  /* lastInteraction= */ 0,
  /* logPrefix= */ '[GASP] ',
  /* log= */ false, // legacy logging toggle
  /* unidirectional= */ true, // choose pull-only when the receiver will not already have parents
  /* logLevel= */ LogLevel.INFO,
  /* sequential= */ false // if true, tasks run one-at-a-time rather than in parallel
)

// 4. Trigger the sync
gasp
  .sync()
  .then(() => console.log('GASP sync complete!'))
  .catch(err => console.error('GASP sync error:', err))

Examples

Minimal Example

If you just want a quick demonstration of pulling data from a remote, you can see a short example in our tests. This code snippet demonstrates a super-simplified approach:

import { GASP } from '@bsv/gasp'

// ...Suppose we have minimal storage and remote classes from above...
const aliceStorage = new MyCustomStorage()
const bobRemote = new MyRemote()

// Create GASP instance
const aliceGASP = new GASP(aliceStorage, bobRemote, 0, '[GASP] ', false, true)

// Run the sync
await aliceGASP.sync()

Advanced Example: sequential and Log Levels

Sometimes, performing too many concurrent operations (e.g., writes to a database) can lead to locking issues. Also, you might want to control the verbosity of logs.

import { GASP, LogLevel } from '@bsv/gasp'

const gasp = new GASP(
  myStorage, // Implementation of GASPStorage
  myRemote, // Implementation of GASPRemote
  0, // lastInteraction timestamp
  '[GASP Demo] ', // logPrefix
  false, // old boolean log toggle, for backwards-compat
  true, // choose pull-only when the receiver cannot assume parent availability
  LogLevel.DEBUG, // Use DEBUG or WARN/ERROR
  true // sequential? If true, GASP will do tasks in sequence
)
await gasp.sync()

Unidirectional Pull-Only Sync

GASP v1 supports pull-only operation, which is common in SPV-client use cases. Pass true explicitly when the receiver must not rely on already having every parent for nodes pushed in the reply half. Bidirectional users must apply the parent assumption described above.

// Alice sets unidirectional = true
// She only wants to receive data from Bob, not share her own data
const gaspAlice = new GASP(
  aliceStorage,
  bobRemote,
  0,
  '[GASP-Alice] ',
  false,
  true // unidirectional is set to true
)
await gaspAlice.sync()

// Alice's local store is updated with Bob's UTXOs, but Bob sees no new data from Alice

Dealing With “Deep” Transactions and Metadata

When a new transaction is received, GASP calls your findNeededInputs(...) method. If you require further data (e.g., to verify a scriptSig or a custom metadata field), simply return a requestedInputs object indicating which inputs you need. GASP will request them from the remote automatically.

async findNeededInputs(tx: GASPNode): Promise<GASPNodeResponse | void> {
  // Suppose any transaction with "magic" in the outputMetadata needs further inputs
  if (tx.outputMetadata?.includes('magic')) {
    return {
      requestedInputs: {
        // outpoint -> { metadata: boolean }
        'some_txid.0': { metadata: true },
        'some_txid.1': { metadata: false }
      }
    }
  }
  // If none needed, return undefined
}

Your remote peer’s requestNode(...) method will deliver these missing pieces, if they exist, ensuring a “deep” transaction graph is built.

Treat every remote response as untrusted. A storage/transport implementation must bind each node's raw transaction ID and output index to the request, verify that every child is an actual input of its requested parent, isolate temporary graphs, bound graph bytes and node count, reject cycles/conflicting spends, and finalize only after an affirmative anchor and topic-policy verdict. HTTP transports must also use authenticated public HTTPS, refuse redirects, and stream responses under byte and time limits.



Development and Distribution

Run package work from the ts-stack repository root with Node.js 24.11 or newer and pnpm 10:

pnpm install
pnpm --filter @bsv/gasp format:check
pnpm --filter @bsv/gasp lint
pnpm --filter @bsv/gasp typecheck
pnpm --filter @bsv/gasp test:coverage
pnpm --filter @bsv/gasp pack:check

The coverage gate ratchets statements, branches, functions, and lines. pack:check builds and installs the exact npm tarball in ESM and CommonJS consumer projects, then verifies exports and conditional types. Publishing is owned by the repository release workflow; local checks do not change versions or publish.


FAQ

  1. Does GASP handle conflicting transactions?
    GASP is agnostic about conflicts. It’s up to your GASPStorage implementation to decide how to handle double spends or conflicting states.

  2. How do I do only pure “SPV proof” validation? GASP includes optional Merkle proofs via the proof field. If your validateGraphAnchor(...) checks them, you effectively get SPV-level validation.

  3. What about specialized metadata or policies? GASP was built for that. Use txMetadata, outputMetadata, or inputs to store and propagate any custom data. Your code can then gather additional inputs if needed.

  4. What if a remote fails to provide data? GASP’s recursion stops. If you never receive inputs you request, you never finalize that transaction. This ensures consistent partial or full finalization.


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/.

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/gasp 共发布 18 个版本,首次发布于 2024/06/26;过去 12 个月发布了 11 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.3.10latest2026/10/2811 天前
1.3.92026/10/2415 天前
1.3.82026/10/031 个月前
1.3.72026/09/231 个月前
1.3.62026/08/282 个月前
1.3.52026/07/313 个月前

关于 @bsv/gasp 的常见问题

@bsv/gasp 是什么?

Graph Aware Sync Protocol

如何安装 @bsv/gasp?

在项目目录运行 npm install @bsv/gasp,或使用 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/gasp 支持 TypeScript 吗?

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

@bsv/gasp 支持 ESM 还是 CommonJS?

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

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

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

@bsv/gasp 可以商用吗?

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

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

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

@bsv/gasp 还在维护吗?

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