
Company News
Socket Joins New OpenJS Program to Fund Node.js Security Work
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.
@warda_protocol/borsh
Advanced tools
Kaspa over borsh wRPC — the encoding the public resolvers actually serve. Reads the UTXO set, and submits covenant-carrying transactions through a build that has them, so neither side of a Warda payment needs a node.
Kaspa over borsh wRPC — the encoding the public resolvers actually serve — so that neither side of a Warda payment needs a node.
# a seller, who only reads
npm install @warda_protocol/borsh kaspa-wasm32-sdk
# a buyer, who also spends
npm install @warda_protocol/borsh @kluster/kaspa-wasm
import { BorshReader } from "@warda_protocol/borsh";
import { inspect, formatHealth } from "@warda_protocol/kaspa";
const reader = await BorshReader.open({ networkId: "testnet-10" });
console.log(formatHealth(await inspect(reader, { networkId: "testnet-10", tolerate: true })));
console.log(reader.canSubmit ? "can spend" : "read-only build");
const coins = await reader.getUtxosByAddresses([myVendorAddress]);
await reader.close();
kaspad's wRPC speaks two encodings. Borsh is the default, and the only one
the sixteen resolvers in rusty-kaspa's Resolvers.toml serve. JSON exists
only when an operator passes --rpclisten-json=, which almost nobody does.
Measured, not assumed:
GET {resolver}/v2/kaspa/testnet-10/wrpc/json 404
GET {resolver}/v2/kaspa/testnet-10/wrpc/borsh 200
Which meant, until this package, that every Warda tool needed a node run by the person running the tool. "Run a DAG node" is a real thing to ask of someone who just wants to sell an API call for a fifth of a cent.
Not the argument — the premise.
Borsh is positional. There are no field names on the wire, so a struct the
encoder does not know about is not an unknown field; it is an absent one, and
absence shifts everything after it. kaspa-wasm32-sdk@0.15.2 was built before
covenants — its ITransactionOutput is {value, scriptPublicKey} and the
string covenant occurs zero times in the package. Hand it a grant spend and
it serializes a transaction that is well-formed, correctly signed, and bound to
nothing: accepted by the encoder, meaningless on chain. Refusing to submit
was the only honest option available.
But rusty-kaspa's own wasm bindings have carried covenants since Toccata —
CovenantBinding has a constructor, TransactionOutput takes one, UtxoEntry
exposes covenantId — and builds from those revisions are published.
@kluster/kaspa-wasm is
one.
So the buyer's node requirement was never a protocol limit. It was a packaging limit.
It would be a poor bargain to remove "run your own node" by adding "trust a stranger's wasm with your transactions". This package does not ask you to.
The covenant binding is part of the sighash. That is exactly why a covenant-blind wallet cannot sign a Warda spend — and it means any difference between what was signed and what the encoder produced shows up in the transaction id. So every submit computes the id twice, once through this SDK and once through the WASM encoder, and refuses to broadcast unless they match byte for byte:
SerialisationDisagreement: the WASM encoder produced a different transaction
than the one that was signed.
signed b488881787b3727b5a627c849d33f5c5d074694b380e2a654f375dc592b97200
encoded 4f1e1c0a…
Nothing was broadcast.
A tampered build, a stale one, one from a revision where a field moved — all caught before the network sees anything, every time. The dependency can break this path. It cannot redirect money through it.
Check your own build before you fund anything:
node --experimental-strip-types tools/agree.ts /tmp/spend.json /tmp/genesis.json /tmp/delegate.json
build @kluster/kaspa-wasm
covenants yes
spend.json 2 outputs, 1 carrying a covenant agree
genesis.json 2 outputs, 1 carrying a covenant agree
delegate.json 2 outputs, 2 carrying a covenant agree
All 3 agree byte for byte.
Support is detected by constructing a CovenantBinding, never by reading a
version or a package name — so the day upstream ships a covenant-carrying
kaspa-wasm32-sdk, this starts accepting it with no change here.
| package | calls | needs a covenant-carrying build |
|---|---|---|
@warda_protocol/vendor | getUtxosByAddresses | no |
@warda_protocol/verify | + getBlockDagInfo | to read grants, yes |
the buyer (warda pay --borsh) | + submitTransaction | yes |
The coin a vendor is paid with is an ordinary P2PK output to the vendor's own address. Nothing about it is covenant-carrying, so "is the coin I was promised actually in the UTXO set" can be read from any resolver with any build.
Serving paid API calls, therefore, needs no node and no covenant build:
import { priced } from "@warda_protocol/vendor";
import { BorshReader } from "@warda_protocol/borsh";
const paid = priced({
price: "0.02",
payTo: myVendorAddress,
secret: process.env.WARDA_VENDOR_SECRET!,
openNode: async () => {
const client = await BorshReader.open({ networkId: "testnet-10" });
return { client, readFrom: client.url };
},
});
assertCovenantAware resolves a real ambiguity: an absent covenantId means
either no covenant here or this node is too old to say, and it tells them
apart by asking an address where the field must be present.
With a covenant-blind build there is a third cause — the deserializer never
carried the field — and one observation cannot separate three causes, so it
throws CovenantUnanswerable rather than guessing. Reporting a perfectly
modern node as pre-covenant would send an operator to replace a node that is
fine.
With a covenant-carrying build the third cause is gone and the check runs for real, naming the node rather than the ambiguity.
BorshReader implements Inspectable, so the same four checks run against it.
That matters more on a resolver-chosen stranger than on your own node:
three of the four ways a node can be wrong produce a plausible answer rather
than an error, and the plausible answer always reads as a problem with your
grant.
Both WASM packages are optional peer dependencies. Each carries a
multi-megabyte binary, and nobody needs two — install
@kluster/kaspa-wasm to spend, kaspa-wasm32-sdk if you only read and already
have it.
The right home for a covenant-carrying build is kaspa-wasm32-sdk itself, so
that this does not rest on a third party. Until then, the feature detection
above means adopting it will be an install and nothing else.
MIT
FAQs
Kaspa over borsh wRPC — the encoding the public resolvers actually serve. Reads the UTXO set, and submits covenant-carrying transactions through a build that has them, so neither side of a Warda payment needs a node.
We found that @warda_protocol/borsh demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Company News
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.

Security News
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.

Research
/Security News
A malicious Firefox extension fetches its payload after installation to evade detection, steal Google session cookies, and automate account takeover.