
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/x402
Advanced tools
Pay HTTP 402 (x402) invoices from a Warda grant on Kaspa, so an agent's spending limits are enforced by consensus rather than by the process holding the key.
Pay HTTP 402 invoices out of a Warda grant, so an agent's spending limits are enforced by consensus instead of by the process holding the key.
npm install @warda_protocol/x402
The usual way to let an agent pay per call is a hot wallet plus a counter. The
x402 documentation describes the standard control: the client "applies a $1 USD
spend cap unless you override spendControls"
(source).
The ceiling is a default, and the code that pays is the code that can raise it.
It is bypassed by a redeploy with a different value, a second instance, or
anyone who can read the key out of env. The wallet
balance is the only limit that actually holds.
Backed by a grant, the same agent cannot exceed its budget even if the key is stolen outright — the thief inherits the limits, because the limits are in the script that unlocks the coin.
They answer different questions and meet at exactly one point.
x402 says how to pay for one call: here is the price, here is the address, come back with proof.
Warda says what this agent may pay — in total, per call, per epoch, and to whom.
The join is the payment step. A stock x402 client builds a plain transfer from a key it was handed; this builds a covenant spend from a grant. Everything above and below is unchanged. The vendor sees an ordinary Kaspa payment and never learns the difference.
kaspa-x402 has moved to v2, and v2 is not a superset of v1. The header names
changed, amountSompi became amount, the per-invoice nonce was replaced by a
binding to the request itself, and the payment is now an authorization signed
over a digest rather than a bare txid. A server speaking one does not
understand the other.
Both are implemented. dialect() reads which one a server speaks from what the
server actually answered, because the answer is in the response and asking an
operator to configure it is how a client ends up wrong against half the
network.
import { wardaFetchV2 } from "@warda_protocol/x402";
const res = await wardaFetchV2("https://vendor/infer", {
method: "POST",
body: JSON.stringify({ prompt: "..." }),
}, { payer });
The v2 path depends on @kaspa-x402/core
rather than reimplementing it. Every value on that wire is a hash over a
canonical JSON encoding, and a plausible reimplementation of a canonical
encoder is one that passes its own tests and is rejected by somebody else's
facilitator over a key ordering. Their package is MIT, pure, and imports only
node:crypto, so the encoder, the schema validators and the digest preimages
are theirs; the covenant spend and the agent's signature are ours.
v2's payload carries the whole signed transaction, and their server interface
has a sendTransaction. It is tempting to read that as "the vendor
broadcasts", and this adapter did, and it was wrong: their verifier requires
the payment to have reached the finality the quote names — accepted, in the
quote their own demo serves — and nothing can require accepted finality of a
transaction it is about to submit itself. Their sendTransaction is a
rebroadcast for a payment already verified.
Presenting a payment that has not been broadcast earns
invalid_transaction_state, which is what it looked like from the other side:
a payment that exists only in your process is a payment that does not exist.
So the payer broadcasts, waits for the network to accept it, and only then presents it. The transaction in the payload is evidence, not instruction.
A v2 payment still holds the grant between building and settling. buildPaymentV2 returns a PendingPayment
and the payer refuses to build another until it is told what happened:
const pending = await payer.buildPaymentV2({ accepted, request });
// ... hand pending.header to the vendor as PAYMENT-SIGNATURE ...
payer.settledV2(); // they confirmed: advance the grant
payer.abandonedV2(); // they did not: stop, and resolve against the chain
abandonedV2() does not roll back. Once a spend is broadcast the grant has
moved whether or not the vendor served the request, so the payer marks itself
unresolved and names the two addresses the grant can be at. One query per
candidate settles it — sdk/tools/follow-grant.ts --subsets does exactly
that.
Rolling back optimistically would mean spending from a coin that is already gone, which surfaces much later as an inexplicable chain error. Stopping is more disruptive and it is the only honest option.
import { NodeClient, RecipientSet } from "@warda_protocol/kaspa";
import { WardaPayer, wardaFetch } from "@warda_protocol/x402";
import template from "@warda_protocol/kaspa/covenant-template.json" with { type: "json" };
const payer = new WardaPayer({
grant: {
template,
authority: { principalKey, revocationKey },
state, // the grant as it stands now
recipients: new RecipientSet(vendors), // the full list, not just the root
},
node: await NodeClient.connect({ url: "ws://127.0.0.1:18210" }),
sign: agentSecret, // or a signer function
prefix: "kaspatest",
});
const res = await wardaFetch("https://vendor.example/compute", {
method: "POST",
body: JSON.stringify({ prompt: "explain GHOSTDAG" }),
}, { payer });
const result = await res.json();
That is the whole surface. Call a paid endpoint as you would a free one; the 402, the payment and the proof are handled underneath.
sign takes a function as readily as bytes, so the key can live in an HSM, a
remote signer, or another process:
sign: async (digest) => myRemoteSigner.sign(digest), // returns 65 bytes
A grant can only pay a payee it committed to at genesis, and the covenant
requires that payee output to be P2PK. So a vendor's payTo must be a P2PK
address whose key is on the grant's allowlist.
Neither is a limitation of this adapter — they are the authority model working — but both would otherwise fail at broadcast as an opaque script error. Both are checked before anything is signed, and reported in words:
const why = payer.refusalFor(requirement);
// "kaspatest:qq… is not on this grant's allowlist, so no inclusion proof
// places it in the recipients tree. There is no valid transaction that pays
// them — not one the network would reject, none at all."
In practice the allowlist is the vendor list. A marketplace that validates services before listing them is describing the same set.
It never pays twice. When a server answers 402 a second time it means the payment is still settling, and the spec says re-present the same proof. This does exactly that. If the server never settles, it reports that the money is spent rather than paying again — the one bug in this design capable of draining a budget through nobody's fault.
Payments are serialised. A grant is a single UTXO, so two concurrent spends would build on the same coin and one would be rejected as a double spend. The payer queues them rather than letting that surface as a confusing chain error. If you need real concurrency, delegate: N children are N independent UTXOs and therefore N parallel lanes.
The grant moves after every payment. A grant's address is its state, so
spending changes where it lives. The payer owns and advances that state; read
payer.state if the process may restart and you need to resume.
maxAmountSompi is not a security control. It refuses a call quoted above a
ceiling, which is useful against a mis-typed price. But it lives in your
process, which is precisely the kind of limit Warda exists to replace. The
grant's maxPerSpend is the one that holds.
exact vendor directlyx402's exact scheme requires a version-0 transaction whose every input is a
bare P2PK unlocked by a single 66-byte Schnorr push, with no covenant on any
output. A Warda spend is none of those things under any version, and no redeem
script gets through — their verifier reads the public key out of a P2PK script
or throws. So the paying transaction can never be the covenant spend.
It is two transactions instead. The covenant spend pays a relay key the agent holds, for exactly the invoice plus the second transaction's fee; the second is an ordinary wallet-shaped payment from that key to the merchant. Both are built here:
import { relayFeeFor, relayFunding, buildRelayPayment } from "@warda_protocol/x402";
const fee = relayFeeFor(invoiceSompi, agentKey, payeeAddress); // measured, not assumed
const spend = relayFunding(invoiceSompi, fee); // what the covenant releases
const txB = buildRelayPayment({ /* … */ }); // what the merchant accepts
What this costs is stated rather than buried: the relay key is the agent's own,
so for that one hop the chain no longer constrains who is ultimately paid. Every
other limit still binds — budget, per-payment cap, epoch limit, window,
delegation depth — and @warda_protocol/router computes exactly which claims
survive a route with a relay in it. RELAY.md has the transaction
shapes, the fee derivation and the pair of transaction ids from the day it was
proven against a vendor nobody here controls.
GRANT_PROOF_HEADER is the other half: a provider never sees a covenant, so
the payer sends an envelope describing the grant it paid from, and
@warda_protocol/provider checks it against the chain.
It does not verify the facilitator's signature on the 402, and it does not
implement any scheme other than exact. It also does not decide whether a spend
is allowed — the covenant does that, on chain, and it is the only thing that
can. Everything here refuses earlier than the chain would, never later, and
never permits something the chain would not.
MIT
FAQs
Pay HTTP 402 (x402) invoices from a Warda grant on Kaspa, so an agent's spending limits are enforced by consensus rather than by the process holding the key.
The npm package @warda_protocol/x402 receives a total of 161 weekly downloads. As such, @warda_protocol/x402 popularity was classified as not popular.
We found that @warda_protocol/x402 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.