Kaspa x402

Kaspa x402 Exact Binding v2

Status: active Alpha.10 exact binding, unchanged from Alpha.9

This document defines the Kaspa network binding for x402 v2 exact. It supersedes kaspa-exact-v1 for new implementations while preserving the v1 document and immutable alpha.7 release evidence as historical records.

Summary

exact is for a fixed-price one-shot purchase. The merchant knows the price before protected work begins, and successful settlement transfers precisely that advertised amount of native KAS.

This binding defines two profiles:

ProfileStatusPayment
standard-nativedefaultOne standard native-KAS merchant output equals the advertised amount.
additiveoptionalA merchant KIP-10 head successor increases by exactly the advertised amount.

Use batch-settlement instead for repeated or variable-cost requests where off-chain cumulative vouchers should amortize on-chain settlement.

Identifiers

Every v2 exact requirement uses:

{
  "scheme": "exact",
  "network": "kaspa:testnet-10",
  "asset": "KAS",
  "extra": {
    "binding": "kaspa-exact-v2",
    "profile": "standard-native"
  }
}

Recognized draft network identifiers are:

kaspa:mainnet
kaspa:testnet-10

The kaspa CAIP namespace is proposed, not registered. kaspa:testnet-10 is the Alpha.10 validation target. kaspa:mainnet is a reserved profile name and does not imply mainnet, custody, or production readiness.

The binding settles native KAS only.

Common PaymentRequirements

Both profiles include:

{
  "scheme": "exact",
  "network": "kaspa:testnet-10",
  "amount": "20000000",
  "asset": "KAS",
  "payTo": "kaspatest:...",
  "maxTimeoutSeconds": 60,
  "extra": {
    "binding": "kaspa-exact-v2",
    "profile": "standard-native",
    "finality": "accepted",
    "transactionEncoding": "kaspa-sdk-safe-json-v2.0.0",
    "payToScriptPublicKey": "0000..."
  }
}
FieldRule
schemeMUST equal exact.
networkMUST be one of the recognized Kaspa network identifiers.
amountMUST be a canonical positive uint64 sompi string. It is the entire merchant gain for this request.
assetMUST equal KAS.
payToMUST be a valid recipient address for network.
maxTimeoutSecondsMUST be a positive uint32. It bounds payment authorization validity as defined under Expiry.
extra.bindingMUST equal kaspa-exact-v2.
extra.profileMUST equal standard-native or additive.
extra.finalityMUST equal accepted or confirmed in the reference alpha. A stricter server policy is allowed.
extra.transactionEncodingMUST equal kaspa-sdk-safe-json-v2.0.0 in this draft.
extra.payToScriptPublicKeyMUST be the canonical serialized script public key derived independently from payTo.

mempool is not sufficient hosted settlement finality for this alpha. It may remain an internal diagnostic state, but a successful protected request MUST use accepted or stronger finality.

Kaspa storage mass depends on the complete populated transaction. This binding does not define a universal 10000000 sompi dust or payment floor. A client or operator MAY enforce a local minimum and MUST label it as application policy.

Standard-native profile

Requirements

The common requirements are complete when:

{
  "extra": {
    "binding": "kaspa-exact-v2",
    "profile": "standard-native",
    "finality": "accepted",
    "transactionEncoding": "kaspa-sdk-safe-json-v2.0.0",
    "payToScriptPublicKey": "0000..."
  }
}

Additive head or challenge fields MUST be absent.

Canonical transaction

The reference alpha uses a version-0 native Kaspa transaction:

input[0+] = payer standard Schnorr P2PK funding inputs

output[paymentOutputIndex] = merchant payTo script, value = amount
one other output optional  = payer change

The transaction MUST:

The client pays amount + fee and receives any change. The merchant receives exactly amount.

Additive profile

Relationship to KIP-10

KIP-10 introduces transaction introspection opcodes. It does not define this x402 wire profile, recipient binding, exact-price equality, challenge state, fee limits, replay policy, or reconciliation. This document defines those application rules.

Requirements

An additive offer includes:

{
  "scheme": "exact",
  "network": "kaspa:testnet-10",
  "amount": "20000000",
  "asset": "KAS",
  "payTo": "kaspatest:<head-p2sh-address>",
  "maxTimeoutSeconds": 60,
  "extra": {
    "binding": "kaspa-exact-v2",
    "profile": "additive",
    "finality": "accepted",
    "transactionEncoding": "kaspa-sdk-safe-json-v2.0.0",
    "payToScriptPublicKey": "<head serialized P2SH script public key>",
    "templateId": "kaspa-x402-kip10-additive-v1",
    "headId": "<stable merchant head id>",
    "headVersion": "7",
    "expectedHeadOutpoint": {
      "txid": "<current head transaction id>",
      "index": 0
    },
    "headAmount": "100000000",
    "headScriptPublicKey": "<same serialized P2SH script public key>",
    "headRedeemScript": "<canonical KIP-10 additive redeem script>",
    "additiveThresholdSompi": "10000000",
    "challengeId": "<unique 32-byte challenge id>",
    "challengeExpiresAt": "2026-07-14T09:00:00.000Z"
  }
}
FieldRule
extra.templateIdMUST equal kaspa-x402-kip10-additive-v1.
extra.headIdMUST be a stable server-scoped 32-byte identifier for one additive chain.
extra.headVersionMUST be a canonical uint64 string that increases for every accepted successor.
extra.expectedHeadOutpointMUST be the exact current unspent head.
extra.headAmountMUST equal the trusted amount of the expected head.
extra.headScriptPublicKeyMUST equal payToScriptPublicKey and the P2SH script derived from headRedeemScript.
extra.headRedeemScriptMUST be the canonical additive script for the advertised owner key and threshold.
extra.additiveThresholdSompiMUST be a positive canonical uint64 application minimum.
extra.challengeIdMUST identify the server-issued terms and normalized request fingerprint. It is not an exclusive head reservation.
extra.challengeExpiresAtMUST be a valid future ISO-8601 timestamp when the paid retry is verified.

amount MUST be greater than or equal to additiveThresholdSompi. The threshold is an anti-churn minimum, not an additional merchant payment.

payTo MUST encode the head P2SH script. This makes the exact successor delta the value transferred to the advertised merchant recipient.

Canonical transaction

The reference alpha uses a version-1 native transaction:

input[0]  = expected current merchant KIP-10 head
input[1+] = payer standard Schnorr P2PK funding inputs

output[0] = successor head:
            same script as input[0]
            value = headAmount + amount

output[1] = optional payer change

The transaction MUST:

KIP-10 enforces a lower-bound successor increase. The x402 verifier MUST enforce the exact equality above. A larger successor delta is an overpayment and MUST be rejected.

The merchant receives exactly amount through the successor increase. The payer spends exactly amount + fee. The previous alpha.7 construction that combined a threshold top-up with a separate merchant output is not valid under this binding.

Transaction interchange and identifiers

kaspa-sdk-safe-json-v2.0.0

PaymentPayload.payload.transaction is a UTF-8 JSON string containing this bounded transaction projection:

{
  "id": "<32-byte transaction id hex>",
  "version": 0,
  "inputs": [
    {
      "previousOutpoint": { "transactionId": "<32-byte hex>", "index": 0 },
      "sequence": "18446744073709551615",
      "sigOpCount": 1,
      "signatureScript": "<hex>",
      "utxo": { "amount": "50000000", "scriptPublicKey": "<serialized hex>" }
    }
  ],
  "outputs": [
    {
      "value": "20000000",
      "scriptPublicKey": "<serialized hex>",
      "covenant": null
    }
  ],
  "lockTime": "0",
  "subnetworkId": "0000000000000000000000000000000000000000",
  "gas": "0",
  "payload": "",
  "storageMass": "63557"
}

Version 1 inputs replace the version-0 sigOpCount commitment with the computeBudget field required by consensus. The safe projection carries sigOpCount: 0 for those inputs because the JSON source type exposes both fields. Verifiers MUST use computeBudget for version 1 and MUST NOT interpret that zero as a version-0 sigop commitment.

Rules:

The transaction identifier is computed from the normalized consensus fields, not by hashing the JSON string.

Integer and byte serialization

The identifier preimages use the Rusty Kaspa consensus encoding:

Version 0 transaction id

For standard-native, construct this preimage:

u16(version)
u64(input count)
for each input:
  outpoint
  varbytes(empty signature script)
  u64(sequence)
u64(output count)
for each output:
  output
u64(lock time)
20-byte subnetwork id
u64(gas)
varbytes(payload)

The version-0 transaction id is BLAKE2b-256 keyed by the UTF-8 bytes TransactionID over that preimage. It excludes signature scripts, sigop counts, and storage mass.

Version 1 transaction id

For additive:

1. payloadDigest is keyed BLAKE3-256 with domain PayloadDigest over the raw payload bytes. 2. restPreimage uses the transaction serialization above with signature scripts and payload replaced by empty byte strings and with compute budgets and storage mass omitted. 3. restDigest is keyed BLAKE3-256 with domain TransactionRest over restPreimage. 4. The transaction-id preimage is payloadDigest || restDigest. 5. The transaction id is keyed BLAKE3-256 with domain TransactionV1Id over that 64-byte preimage.

For these BLAKE3 hashes, the 32-byte key is the ASCII domain copied into a zero-filled 32-byte array. The BLAKE2b domain is the native variable-length key. vectors/exact/interop-v1.json carries the complete preimages and digests for both profiles. The same transactions are independently constructed and validated by the pinned Rust consensus harness.

PaymentPayload

Both profiles use one bounded transaction-artifact envelope:

{
  "x402Version": 2,
  "accepted": { "...": "the selected exact requirements" },
  "payload": {
    "type": "exact-transaction",
    "profile": "standard-native",
    "payerAddress": "kaspatest:...",
    "transaction": "<signed bounded safe transaction JSON>",
    "transactionEncoding": "kaspa-sdk-safe-json-v2.0.0",
    "paymentOutputIndex": 0,
    "requestHash": "<normalized request hash>",
    "authorization": {
      "version": "kaspa-x402-exact-request-authorization-v1",
      "digest": "<32-byte digest>",
      "inputIndex": 0,
      "expiresAt": "2026-07-14T09:00:00.000Z",
      "signature": "<64-byte Schnorr signature>"
    }
  }
}

For additive, payload.profile MUST be additive, paymentOutputIndex MUST be 0, and the payload MUST also include the exact challengeId from the accepted requirements. For standard-native, challengeId MUST be absent.

payerAddress is receipt metadata only. Verified payer attribution comes from authoritative input UTXOs and valid signatures.

requestHash and authorization are mandatory for both profiles. The authorization digest binds the canonical transaction id, selected profile, payment output index, amount, payTo, canonical recipient script, accepted requirements hash, normalized request hash, additive challenge when present, authorizing payer input index, and expiry. The signer MUST be the public key proven by that authoritative standard P2PK funding input. The additive head input cannot authorize the payer request.

An on-chain transaction signature authorizes the value transfer; it does not by itself authorize which HTTP resource or MCP operation receives that payment. The request authorization closes that audience boundary. Removing or changing the request hash, route, requirements, profile, recipient, amount, transaction, input index, challenge, or expiry MUST invalidate the payment before protected work.

The artifact MUST include bounded representations of version, inputs, outpoints, input mass fields, signature scripts, outputs, output covenants, lock time, subnetwork, gas, payload, storage mass, and any embedded UTXO hints. Hints are not trusted chain evidence.

The verifier MUST derive the transaction id from the canonical transaction. A separate client-authoritative transaction id is forbidden. If the interchange format includes a convenience id, it MUST equal the independently recomputed identifier.

Canonical request authorization

Payment requirements hash

paymentRequirementsHash is SHA-256 over the UTF-8 bytes of the complete selected PaymentRequirements object after recursive canonical JSON serialization:

All documented field names are ASCII, so their ordering is identical in ordinary bytewise and Unicode-code-point sorts. Implementations accepting additional fields MUST still include them in this canonical object and hash.

Authorization digest

Construct this object, using lowercase hex for every hex field and explicit null for a missing standard-native challenge:

{
  "scope": "kaspa-x402-exact-request-authorization-v1",
  "network": "kaspa:testnet-10",
  "profile": "additive",
  "transactionId": "<lowercase transaction id>",
  "paymentOutputIndex": 0,
  "amount": "20000000",
  "payTo": "kaspatest:...",
  "payToScriptPublicKey": "<lowercase serialized script>",
  "paymentRequirementsHash": "<lowercase hash>",
  "requestHash": "<lowercase hash>",
  "challengeId": "<lowercase hash or null>",
  "inputIndex": 1,
  "expiresAt": "2099-01-01T00:00:00.000Z"
}

Apply the same canonical JSON serialization and SHA-256 the UTF-8 result. The 64-byte Schnorr signature in authorization.signature signs this 32-byte digest directly. Its public key MUST be the x-only public key committed by the authoritative standard P2PK UTXO at authorization.inputIndex.

The exact interoperability vector includes the selected requirements, both canonical JSON preimages, both SHA-256 results, signer public key, and valid signature. No TypeScript-specific serialization is needed to reproduce it.

Expiry

Let now be the verifier's current time:

The resource server or facilitator checks authorization ordering before protected work. It MUST re-evaluate expiry against the current time after any awaited transaction verification and before creating a new settlement. The additive head/challenge provider checks challenge liveness and head state. An adapter may repeat these checks but cannot weaken them.

Expiry prevents a new settlement or protected-handler execution. It does not invalidate an idempotent retry whose transaction and response were already durably accepted for the same request. An implementation MAY verify enough of an expired artifact to identify that exact stored result, but MUST NOT execute the protected handler or create a new settlement when no matching durable record exists.

The committed interoperability vector fixes a reference clock and supplies positive and negative expiry cases so results do not depend on the test runner's wall clock.

Verification

Before protected work executes, the server or facilitator MUST:

1. Validate x402 version, scheme, network, asset, amount, timeout, binding, profile, and every required profile field. 2. Re-derive payToScriptPublicKey from payTo and reject disagreement. 3. Enforce artifact byte, input, output, script, and metadata size limits before expensive parsing or node calls. 4. Canonically deserialize the transaction and recompute its identifier. 5. Resolve every input UTXO from a trusted node or trusted chain adapter. 6. Reject any disagreement with artifact-provided UTXO hints. 7. Verify every payer Schnorr signature and every P2SH witness under the active rules. 8. Enforce the selected profile's complete transaction shape and exact merchant gain. 9. Recompute input/output conservation, fee, compute mass, storage mass, script units, and compute commitments; apply configured bounds. 10. Validate the transaction in isolation and with the populated UTXO context using current Rusty Kaspa consensus behavior. 11. Verify the payer request authorization against an authoritative funding input, then enforce request binding, transaction replay, and payment-identifier policy. 12. For additive, validate the still-live challenge and atomically claim the exact expected head/version before protected work.

Facilitator /verify and /settle requests MUST carry the resource server's independently computed requestHash. A facilitator MUST NOT infer that value from PaymentPayload.payload.requestHash; the embedded value is evidence to compare, not an independent statement of the requested resource.

No public or adapter-supplied transaction identifier, UTXO value, UTXO script, mass, fee, or finality assertion is authoritative merely because it is present in JSON.

Settlement lifecycle

1. The client requests a protected resource without payment. 2. The server returns one or more supported exact entries. standard-native SHOULD be first and is the default. 3. An additive offer reads a healthy current head but does not reserve, consume, or retire it. 4. The client builds and signs a transaction for one selected profile without broadcasting it. 5. The server or facilitator performs full verification using trusted chain facts and verifies the payer's request authorization. 6. Transaction replay evidence is durably consumed. For additive, the expected head transition is also atomically claimed. 7. The server or facilitator broadcasts the exact verified transaction and observes required finality. Ambiguous outcomes remain consumed for trusted reconciliation. 8. Only after durable accepted state does the x402 resource handler run. 9. The handler result is persisted before the atomic payment/response commit. A retry resumes the same result without rerunning protected work. 10. Durable state advances to applied; the response reports the independently established transaction id and profile evidence.

If broadcast returns an ambiguous error, consumed evidence and a claimed head MUST remain pending until trusted reconciliation resolves the known transaction. They MUST NOT be released for another handler execution.

Host frameworks that run protected work between verification and settlement cannot make arbitrary application side effects atomic with chain broadcast. Applications with irreversible side effects MUST use an idempotent handler or an application outbox keyed by the durable payment/request identity.

If a handler starts but its result is uncertain, the SDK MUST fail closed with an explicit recovery-required state. It MUST NOT rerun the handler blindly. An operator may supply a known durable result through the recovery API; the identical authorized retry then completes the commit without repeating the application effect. The reference store limits the durable result to 256 KiB and 64 response headers; larger application output belongs in a durable outbox referenced by the bounded result.

Retry and signer policy

A corrective 402 is a new offer, not permission for a wallet to sign another payment automatically. The Alpha.10 clients accept maxPaymentRetries: 0 only. Every replacement exact transaction requires a fresh explicit caller or wallet authorization. Funding providers MUST expose an authorizeExactPayment boundary, and deployments SHOULD pin allowed origins, profiles, recipients, and a maximum amount before signing.

Additive concurrency and head recovery

Because anyone can satisfy the additive borrower branch, an external party can advance a head by the threshold. Standard-native remains the default and fallback. Operators SHOULD combine sensible thresholds, independent shards, trusted reconciliation, balance monitoring, and sweep/rotation policies.

This is an intentional liveness and wallet-UX tradeoff of a public borrower, not a merchant-funds-loss path. An external advance makes transactions against the previous head stale. With maxPaymentRetries: 0, each replacement requires a new explicit wallet authorization. Merely hiding or authenticating the 402 offer does not remove the on-chain public borrower. Restricting the borrower would require a different, pre-authorized head design and would no longer be the permissionless additive profile defined here.

Finality and reorgs

accepted means accepted under the configured trusted-node policy. confirmed means the stronger confirmation policy documented by the adapter.

The binding orders finality as mempool < accepted < confirmed. The effective requirement is the stronger of the offer and resource-server policies. accepted is the alpha interoperability baseline and means the trusted node reports the transaction in accepted chain state, not merely in its mempool. There is no universal numeric confirmation depth in this alpha. An adapter offering confirmed MUST document and consistently enforce its stronger depth, time, or virtual-chain policy. Other implementations MUST NOT assume a numeric depth from the word alone.

A reorg or node disagreement after response delivery is an operationally ambiguous settlement. The implementation MUST retain consumed evidence, mark the affected standard transaction or additive head for reconciliation, and MUST NOT treat the payment as reusable merely because one node stopped reporting it.

SettlementResponse

Standard-native success:

{
  "success": true,
  "transaction": "<recomputed transaction id>",
  "network": "kaspa:testnet-10",
  "payer": "kaspatest:...",
  "amount": "20000000",
  "extensions": {
    "kaspa": {
      "binding": "kaspa-exact-v2",
      "profile": "standard-native",
      "paymentOutputIndex": 0,
      "finality": "accepted",
      "transactionEncoding": "kaspa-sdk-safe-json-v2.0.0"
    }
  }
}

Additive success additionally includes:

{
  "extensions": {
    "kaspa": {
      "binding": "kaspa-exact-v2",
      "profile": "additive",
      "templateId": "kaspa-x402-kip10-additive-v1",
      "headId": "<head id>",
      "headVersion": "7",
      "challengeId": "<challenge id>",
      "previousHeadOutpoint": { "txid": "<old txid>", "index": 0 },
      "continuationOutpoint": { "txid": "<settlement txid>", "index": 0 },
      "paymentOutputIndex": 0,
      "finality": "accepted",
      "transactionEncoding": "kaspa-sdk-safe-json-v2.0.0"
    }
  }
}

On success, amount MUST equal the accepted requirement amount and transaction MUST be a non-empty canonical transaction identifier.

Idempotency and replay

Servers SHOULD require the x402 payment-identifier extension.

Resource bounds

Implementations MUST configure and test limits for:

Bounds MUST be checked as early as possible and failures MUST be fail-closed.

Current alpha exclusions

Diagnostics

Public errors use the x402-mapped reasons in errors.md. Local diagnostics may include:

unsupported_kaspa_exact_profile
invalid_kaspa_exact_transaction
invalid_kaspa_exact_transaction_id
invalid_kaspa_exact_payment_output
invalid_kaspa_exact_signature
invalid_kaspa_exact_utxo
invalid_kaspa_exact_fee
invalid_kaspa_exact_mass
invalid_kaspa_exact_replay
invalid_kaspa_exact_finality
invalid_kaspa_exact_challenge
stale_kaspa_exact_head
unavailable_kaspa_exact_head

References

Source: /spec/kaspa-exact-v2.md