
Security News
Crates.io Users Targeted by Phishing Emails
The Rust Security Response WG is warning of phishing emails from rustfoundation.dev targeting crates.io users.
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:
Promise.all
) or sequential fetches (one at a time) to avoid potential DB locking.GASPStorage
and GASPRemote
interfaces let you integrate with your own storage logic or remote transport.If you set GASP to unidirectional, step 5 is skipped: your local node simply pulls data from the remote, but never sends data back.
npm i @bsv/gasp
Or, if you use yarn
:
yarn add @bsv/gasp
Below is a bare-bones recipe to get GASP up and running.
GASPStorage
InterfaceThe 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*/ }
}
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
// ...
}
}
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= */ false, // if true, we only fetch from the remote, never push data
/* 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))
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)
// Run the sync
await aliceGASP.sync()
sequential
and Log LevelsSometimes, 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
false, // unidirectional? No, do full sync
LogLevel.DEBUG, // Use DEBUG or WARN/ERROR
true // sequential? If true, GASP will do tasks in sequence
)
await gasp.sync()
You may only want to “pull” data from a remote server without uploading your own. This is common in “SPV client” use-cases.
// 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
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.
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.
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.
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.
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.
The license for the code in this repository is the Open BSV License.
FAQs
Graph Aware Sync Protocol
We found that @bsv/gasp demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
The Rust Security Response WG is warning of phishing emails from rustfoundation.dev targeting crates.io users.
Product
Socket now lets you customize pull request alert headers, helping security teams share clear guidance right in PRs to speed reviews and reduce back-and-forth.
Product
Socket's Rust support is moving to Beta: all users can scan Cargo projects and generate SBOMs, including Cargo.toml-only crates, with Rust-aware supply chain checks.