Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
506 changes: 506 additions & 0 deletions specs/schemes/upto/scheme_upto_bsv.md

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions typescript/.changeset/add-bsv-upto-scheme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@x402/bsv': minor
---

Extend the BSV mechanism with the `upto` scheme. Clients create a reusable maximum-payment authorization, recipients determine the actual satoshi amount in a fully signed transaction, and one-shot or cumulative streaming workflows share the existing BRC-29, BEEF, and recipient-wallet settlement path. Add role-specific `@x402/bsv/upto/client`, `@x402/bsv/upto/server`, and `@x402/bsv/upto/facilitator` entrypoints.
84 changes: 82 additions & 2 deletions typescript/packages/mechanisms/bsv/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# @x402/bsv

BSV (Bitcoin SV) blockchain implementation of the x402 payment protocol using the **Exact** payment scheme with **BRC-29 / BRC-121 native satoshi payments**.
BSV (Bitcoin SV) blockchain implementation of the x402 payment protocol using the **Exact** and **Upto** payment schemes with **BRC-29 / BRC-121 native satoshi payments**.

## Installation

Expand All @@ -10,7 +10,14 @@ npm install @x402/bsv

## Overview

This package provides three components for handling x402 payments on BSV:
This package provides client, server, and facilitator components for handling x402 payments on BSV. `exact` fixes the payment amount when the client creates the transaction. `upto` extends that same path with a reusable maximum-payment signature and defers the actual amount until a fully signed transaction is selected.

| Scheme | Amount semantics | Client entrypoint | Server entrypoint | Facilitator entrypoint |
| --- | --- | --- | --- | --- |
| `exact` | One transaction for the required amount | `@x402/bsv/exact/client` | `@x402/bsv/exact/server` | `@x402/bsv/exact/facilitator` |
| `upto` | Maximum first, actual amount determined later | `@x402/bsv/upto/client` | `@x402/bsv/upto/server` | `@x402/bsv/upto/facilitator` |

For `exact`, the three roles behave as follows:

- **Client** — Derives a per-payment key from the recipient's identity key (BRC-42/BRC-29) and asks the client's BRC-100 wallet to create a fully-signed, fully-funded transaction paying it (BEEF format, SPV ancestry included)
- **Facilitator** — Wraps the _recipient's_ BRC-100 wallet: verifies payload structure, freshness, and exact amount, then settles by internalizing the payment output into the wallet (`internalizeAction`), which SPV-validates, takes custody, and rejects replays
Expand All @@ -27,6 +34,73 @@ Unlike account-based chains, a BSV payment output is locked to a key that by def
5. Settlement internalizes the output into the recipient wallet; replays are rejected via a facilitator txid dedup cache plus the wallet's merge signal (`isMerge` without newly internalized satoshis)
6. The wallet handles network propagation (e.g. via ARC)

## Upto Authorization and Settlement

`upto` preserves the existing BRC-29 identities, derivation metadata, BEEF transport, BSV asset rules, and recipient-wallet settlement path. It adds only the authorization needed to choose the actual amount later:

1. The server's `PaymentRequirements.amount` is the maximum authorized amount.
2. The recipient facilitator supplies a small control input. The payer signs its cap input with `SIGHASH_SINGLE | SIGHASH_FORKID`, binding its maximum debit and same-index floor output without selecting the actual amount.
3. The recipient signs a complete transaction version after the actual amount is known. The payer's charged amount is recomputed from its net input/output difference. The wire authorization and verifier permit multiple inputs and outputs; the default adapters construct the ordinary one-payer/one-recipient layout.
4. An application may stop after the first agreed transaction or retain successively higher cumulative versions while streaming. This is application behavior, not a signed `mode` or a separate stream settlement object.

The application supplies the transport for `UptoBsvControlProvider`, retains the signed authorization and transactions it needs, and configures the server's `getTransactionVersion` callback to select one at settlement. A transaction version carries only its `authorizationId` and exact BEEF bytes; amount, txid, `nSequence`, and cooperative close are derived during verification. Nodes only receive ordinary BSV transactions; conflicting versions are ordinary double spends, not an x402-specific node state machine.

The signed `validAfter`, `nLockTime`, and `deadline` fields are deliberately separate. Non-final versions settle only after `nLockTime`; every protocol operation remains inside `[validAfter, deadline)`. A cooperative close finalizes the control inputs and bypasses only `nLockTime`, never the deadline. The deadline is participant policy, not a script expiry.

```typescript
import { UptoBsvScheme as UptoBsvClientScheme } from "@x402/bsv/upto/client";
import { UptoBsvScheme as UptoBsvServerScheme } from "@x402/bsv/upto/server";
import {
UptoBsvScheme as UptoBsvFacilitatorScheme,
type UptoBsvSettlementStore,
} from "@x402/bsv/upto/facilitator";
import type { UptoBsvPayload, UptoBsvTransactionVersion } from "@x402/bsv";
```

The role-level flow is deliberately small:

```typescript
const facilitatorScheme = await UptoBsvFacilitatorScheme.create({
wallet: recipientWallet,
feeSatoshis: 1,
nonFinalDelaySeconds: 120,
// Required: validate the complete BEEF graph, scripts, and PoW/SPV anchors.
verifyBeef: verifyCompleteBeefAgainstHeaderChain,
// Required: an atomic shared store keyed by authorizationId. Its claims
// must survive facilitator restarts until their Unix-ms deleteAfterMs time.
settlementStore: sharedSettlementStore satisfies UptoBsvSettlementStore,
});

// In production this method is normally exposed through an authenticated,
// rate-limited application endpoint.
const clientScheme = new UptoBsvClientScheme(payerWallet, {
controlProvider: facilitatorScheme,
});
const created = await clientScheme.createPaymentPayload(2, maximumRequirements);
const payload = created.payload as UptoBsvPayload;
const actualAmount = "250";
let previousVersion: UptoBsvTransactionVersion | undefined;

await facilitatorScheme.verify(
{ x402Version: 2, accepted: maximumRequirements, payload },
maximumRequirements,
);
const selected = await facilitatorScheme.createTransactionVersion(payload, maximumRequirements, {
amount: actualAmount,
previous: previousVersion,
});

// The payer independently verifies the signed bytes before retaining them.
clientScheme.verifyTransactionVersion(payload, selected, previousVersion);
previousVersion = selected;

const serverScheme = new UptoBsvServerScheme({
getTransactionVersion: () => selected,
});
// Register serverScheme normally. At settlement, x402 Core attaches `selected`
// and the settlement override carries `actualAmount` in requirements.amount.
```

## Supported Assets

| Type | Symbol | Description | Decimals |
Expand Down Expand Up @@ -127,6 +201,11 @@ facilitator.register("bsv:mainnet", scheme);
- Freshness window: the timestamp encoded in `derivationSuffix` must be within ±30 s at verify time (configurable via `paymentWindowMs`); at settle time the window extends by `maxTimeoutSeconds`, the advertised settlement budget
- Replay protection: a facilitator-side txid dedup cache plus the wallet's merge signal (`isMerge` without newly internalized satoshis — both wallet-toolbox extensions to BRC-100); verification re-runs at settlement
- The subject transaction is resolved via the Atomic BEEF `atomicTxid`, matching what the wallet internalizes; SPV validity is enforced by the wallet during `internalizeAction` — settlement is the authoritative acceptance step
- An `upto` cap signature is a maximum-payment authorization, not a cryptographic expiry. `nLockTime` only constrains when a transaction becomes eligible for mining. Each party independently recognizes one fully signed version as its terminal transaction; there is no central `Final` state.
- `upto` fails closed without an application-supplied complete-BEEF and PoW/SPV verifier. This validates ancestry and anchors before resource work and again for the selected transaction; it does not prove an outpoint is unspent and does not imply data retention.
- Output `owner` values are signed accounting labels, not ownership proofs. The recipient independently derives its BRC-29 payment scripts and requires its real payment-output increase, adjusted for its control inputs and the fee, to cover the charged amount.
- `upto` requires an atomic `UptoBsvSettlementStore` shared by facilitator replicas and durable through each claim's Unix-ms `deleteAfterMs`; this consumes an `authorizationId` before wallet settlement. The store returns a per-acquisition token that must remain unique across replicas and restarts. A definitively rejected wallet attempt releases only that matching token, while success, replay, and indeterminate outcomes retain it. This settlement guard is separate from application delivery: non-idempotent handlers must also reserve `authorizationId` in shared durable storage before executing the business side effect.
- Cap and control sources are wallet `noSend` actions. Applications must release abandoned actions through their wallet's normal lifecycle. A remotely exposed control-proposal endpoint must authenticate, rate-limit, and deduplicate requests so callers cannot reserve recipient wallet funds without bound.

## References

Expand All @@ -137,3 +216,4 @@ facilitator.register("bsv:mainnet", scheme);
- [BRC-69: Revealing Key Linkages](https://bsv.brc.dev/key-derivation/0069) / [BRC-94: Verifiable Shared-Secret Revelation (Schnorr)](https://bsv.brc.dev/key-derivation/0094)
- [BRC-100: Wallet-to-Application Interface](https://bsv.brc.dev/wallet/0100)
- Spec: `specs/schemes/exact/scheme_exact_bsv.md`
- Upto spec: `specs/schemes/upto/scheme_upto_bsv.md`
30 changes: 30 additions & 0 deletions typescript/packages/mechanisms/bsv/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,36 @@
"types": "./dist/cjs/exact/facilitator/index.d.ts",
"default": "./dist/cjs/exact/facilitator/index.js"
}
},
"./upto/client": {
"import": {
"types": "./dist/esm/upto/client/index.d.mts",
"default": "./dist/esm/upto/client/index.mjs"
},
"require": {
"types": "./dist/cjs/upto/client/index.d.ts",
"default": "./dist/cjs/upto/client/index.js"
}
},
"./upto/server": {
"import": {
"types": "./dist/esm/upto/server/index.d.mts",
"default": "./dist/esm/upto/server/index.mjs"
},
"require": {
"types": "./dist/cjs/upto/server/index.d.ts",
"default": "./dist/cjs/upto/server/index.js"
}
},
"./upto/facilitator": {
"import": {
"types": "./dist/esm/upto/facilitator/index.d.mts",
"default": "./dist/esm/upto/facilitator/index.mjs"
},
"require": {
"types": "./dist/cjs/upto/facilitator/index.d.ts",
"default": "./dist/cjs/upto/facilitator/index.js"
}
}
},
"files": [
Expand Down
7 changes: 5 additions & 2 deletions typescript/packages/mechanisms/bsv/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
* x402/bsv
*
* BSV (Bitcoin SV) blockchain implementation of the x402 payment protocol
* using the `exact` payment scheme with BRC-29 / BRC-121 native satoshi
* payments internalized by the recipient's BRC-100 wallet.
* using the `exact` and `upto` payment schemes with BRC-29 / BRC-121 native
* satoshi payments internalized by the recipient's BRC-100 wallet.
*/

// Exact scheme exports
export { ExactBsvScheme } from "./exact";

// Upto client scheme export; server and facilitator use role-specific entrypoints
export { UptoBsvScheme } from "./upto/client/scheme";

// Types
export * from "./types";

Expand Down
116 changes: 115 additions & 1 deletion typescript/packages/mechanisms/bsv/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* BSV x402 V2 types.
* BSV x402 payload types.
*
* The payload mirrors the BRC-29 payment message (as used by BRC-121
* "Simple 402 Payments"): a fully-signed, fully-funded BSV transaction in
Expand Down Expand Up @@ -47,3 +47,117 @@ export interface ExactBsvPayloadV2 {
/** Zero-based index of the payment output in the transaction */
outputIndex: number;
}

/** An immutable input included in a BSV `upto` authorization. */
export interface UptoBsvInput {
/** Identity that owns the input for net-delta accounting. */
owner: string;
/** Cap signatures are reusable; control inputs sign every transaction version. */
kind: "cap" | "control";
/** Base64 BEEF or Atomic BEEF containing the source transaction. */
sourceTransaction: string;
sourceOutputIndex: number;
/** Compressed public key matching the source P2PKH output. */
publicKey: string;
}

/** An immutable output slot included in a BSV `upto` authorization. */
export interface UptoBsvOutput {
/** Identity that owns the output for net-delta accounting. */
owner: string;
/** Hex-encoded locking script; its value is supplied by each signed version. */
lockingScript: string;
/** Fixed value for a cap input's same-index floor output. */
fixedAmount?: string;
}

/** Canonical fields approved before the actual amount is known. */
export interface UptoBsvAuthorizationTerms {
version: 1;
network: string;
asset: "BSV";
payTo: string;
senderIdentityKey: string;
derivationPrefix: string;
derivationSuffix: string;
inputs: UptoBsvInput[];
outputs: UptoBsvOutput[];
/** Owners whose positive net deltas form the x402 amount. */
chargedOwners: string[];
/** Outputs internalized by the recipient wallet under the BRC-29 remittance. */
paymentOutputIndexes: number[];
fee: string;
sequenceStart: number;
/** Unix seconds before which the authorization is not accepted. */
validAfter: number;
/** Unix seconds at or after which the authorization is no longer accepted. */
deadline: number;
/** Absolute transaction lock time used by non-final control inputs. */
nLockTime: number;
}

/** Reusable signatures made by one cap input owner. */
export interface UptoBsvCapSignature {
inputIndex: number;
/** Base64 DER signature for `SIGHASH_SINGLE | SIGHASH_FORKID`. */
transactionSignature: string;
/** Base64 DER signature over the canonical authorization digest. */
authorizationSignature: string;
}

/** A single-use maximum-payment authorization. */
export interface UptoBsvAuthorization {
/** Lowercase SHA-256 digest of the canonical terms. */
authorizationId: string;
terms: UptoBsvAuthorizationTerms;
capSignatures: UptoBsvCapSignature[];
}

/** One ordinary, fully signed transaction produced under an authorization. */
export interface UptoBsvTransactionVersion {
authorizationId: string;
/** Base64 BEEF or Atomic BEEF for the fully signed transaction. */
transaction: string;
}

/** Pure-data result of verifying one signed transaction version locally. */
export interface UptoBsvTransactionVerification {
txid: string;
amount: string;
nSequence: number;
cooperativeClose: boolean;
ownerDeltas: Readonly<Record<string, string>>;
}

/** x402 payload for the BSV `upto` scheme. */
export interface UptoBsvPayload {
/** Inherited BRC-29 fields, kept top-level for exact-path compatibility. */
derivationPrefix: string;
derivationSuffix: string;
senderIdentityKey: string;
outputIndex: number;
authorization: UptoBsvAuthorization;
/** Added for settlement when one fully signed transaction is selected. */
transactionVersion?: UptoBsvTransactionVersion;
}

/** Request sent to the recipient when obtaining its small control input. */
export interface UptoBsvControlRequest {
network: string;
payTo: string;
senderIdentityKey: string;
derivationPrefix: string;
derivationSuffix: string;
maxAmount: string;
maxTimeoutSeconds: number;
}

/** Recipient-provided control input and transaction timing terms. */
export interface UptoBsvControlProposal {
inputs: UptoBsvInput[];
fee: string;
sequenceStart: number;
validAfter: number;
deadline: number;
nLockTime: number;
}
2 changes: 2 additions & 0 deletions typescript/packages/mechanisms/bsv/src/upto/client/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { UptoBsvScheme, type UptoBsvClientConfig, type UptoBsvControlProvider } from "./scheme";
export type { UptoBsvTransactionVerification } from "../../types";
Loading
Loading