Kaspa x402

Kaspa x402 Exact Binding v2

Status: alpha.8 draft

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

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.

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

Finality and reorgs

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

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.

Initial alpha.8 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