@reapp-sdk/express-middleware 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -156
- package/dist/bound-route.d.ts +36 -0
- package/dist/bound-route.d.ts.map +1 -0
- package/dist/bound-route.js +150 -0
- package/dist/bound-route.js.map +1 -0
- package/dist/bound-store.d.ts +79 -0
- package/dist/bound-store.d.ts.map +1 -0
- package/dist/bound-store.js +62 -0
- package/dist/bound-store.js.map +1 -0
- package/dist/bound.d.ts +66 -0
- package/dist/bound.d.ts.map +1 -0
- package/dist/bound.js +447 -0
- package/dist/bound.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/verification.js +2 -2
- package/dist/verification.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,206 +1,168 @@
|
|
|
1
|
-
# @reapp-sdk/express-middleware 0.
|
|
1
|
+
# @reapp-sdk/express-middleware 0.2.0
|
|
2
2
|
|
|
3
|
-
Fail-closed Express
|
|
3
|
+
Fail-closed Express 4/5 paid JSON routes for REAPP on Stellar.
|
|
4
4
|
|
|
5
|
-
The
|
|
6
|
-
settlement
|
|
7
|
-
before
|
|
8
|
-
|
|
9
|
-
transfer from the mandate user to the configured merchant. Header claims never
|
|
10
|
-
replace contract evidence.
|
|
11
|
-
|
|
12
|
-
Supports Express 4 and Express 5. Ships as ESM with TypeScript declarations.
|
|
5
|
+
The package authenticates an exact-origin GET challenge, verifies the on-chain
|
|
6
|
+
settlement independently, atomically claims fulfillment, stores the exact JSON
|
|
7
|
+
result before sending it, and replays those bytes on recovery. A settlement can
|
|
8
|
+
never re-run arbitrary fulfillment work.
|
|
13
9
|
|
|
14
10
|
## Install
|
|
15
11
|
|
|
16
12
|
```bash
|
|
17
|
-
npm install @reapp-sdk/express-middleware express
|
|
13
|
+
npm install @reapp-sdk/express-middleware@0.2.0 express@5.2.1
|
|
18
14
|
```
|
|
19
15
|
|
|
20
|
-
|
|
16
|
+
The exact T2 compatibility name `@reapp/express-middleware` exposes the same
|
|
17
|
+
typed ESM API.
|
|
18
|
+
|
|
19
|
+
## Safe paid route
|
|
21
20
|
|
|
22
21
|
```ts
|
|
23
22
|
import express from "express";
|
|
24
23
|
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
getVerifiedPayment,
|
|
24
|
+
InMemoryBoundRedemptionStore,
|
|
25
|
+
createBoundReappPaidJsonRoute,
|
|
28
26
|
} from "@reapp-sdk/express-middleware";
|
|
29
27
|
|
|
30
28
|
const app = express();
|
|
31
29
|
|
|
32
|
-
const
|
|
30
|
+
const paidResearch = createBoundReappPaidJsonRoute({
|
|
33
31
|
merchant: process.env.REAPP_MERCHANT_ADDRESS!,
|
|
34
32
|
sourceAccount: process.env.REAPP_READ_SOURCE_ADDRESS!,
|
|
33
|
+
audience: "https://api.example", // exact public origin; never Host-derived
|
|
34
|
+
challengeSecret: process.env.REAPP_CHALLENGE_SECRET!, // at least 32 bytes
|
|
35
35
|
amount: "1.00",
|
|
36
|
-
resource:
|
|
37
|
-
//
|
|
38
|
-
redemptionStore: new
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
36
|
+
resource: (request) => request.originalUrl,
|
|
37
|
+
// One-process demo only. Production needs a shared linearizable store.
|
|
38
|
+
redemptionStore: new InMemoryBoundRedemptionStore(),
|
|
39
|
+
}, async ({ request, payment }) => ({
|
|
40
|
+
body: {
|
|
41
|
+
ok: true,
|
|
42
|
+
resource: request.params.id,
|
|
43
|
+
data: await loadResearchOnce(request.params.id),
|
|
44
|
+
settledTx: payment.txHash,
|
|
45
|
+
},
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
app.get("/source/:id", paidResearch);
|
|
50
49
|
app.listen(4021);
|
|
51
50
|
```
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
The fulfillment callback receives no Express `Response` and cannot stream. Its
|
|
53
|
+
JSON result is bounded, hashed, and committed to the same redemption store
|
|
54
|
+
before any bytes are written to the client.
|
|
55
55
|
|
|
56
|
-
##
|
|
56
|
+
## Bound-v2 authorization
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
Before fulfillment is claimed, the package requires:
|
|
59
59
|
|
|
60
|
-
1.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
6.
|
|
71
|
-
|
|
60
|
+
1. `REAPP-PAYMENT-CAPABILITIES: reapp-bound-v2`; older clients receive `426`
|
|
61
|
+
before payment.
|
|
62
|
+
2. An HMAC-authenticated challenge binding the exact public origin, GET method,
|
|
63
|
+
path and query, network identity, registry, merchant, asset, amount, decimals,
|
|
64
|
+
random id, and first-redemption deadline.
|
|
65
|
+
3. A canonical proof whose Stellar Ed25519 signature binds that challenge,
|
|
66
|
+
transaction hash, and mandate id to the chain-derived mandate agent.
|
|
67
|
+
4. The configured RPC's exact network passphrase and a successful fresh
|
|
68
|
+
transaction.
|
|
69
|
+
5. One unambiguous payment event from the configured MandateRegistry.
|
|
70
|
+
6. Current mandate user, agent, merchant, and asset identities.
|
|
71
|
+
7. One matching same-transaction SEP-41 transfer from user to merchant.
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
A copied public transaction hash cannot unlock data. Relaying a genuine quote
|
|
74
|
+
through another origin fails before the client pays because the signed audience
|
|
75
|
+
must equal the requested URL origin.
|
|
76
76
|
|
|
77
|
-
##
|
|
77
|
+
## Atomic fulfillment state
|
|
78
78
|
|
|
79
|
-
`
|
|
80
|
-
|
|
81
|
-
`consumeOnce` operation is linearizable across every worker and host:
|
|
79
|
+
`BoundRedemptionStore` owns settlement binding and immutable response bytes in
|
|
80
|
+
one linearizable state machine:
|
|
82
81
|
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
RedemptionRecord,
|
|
86
|
-
RedemptionStore,
|
|
87
|
-
} from "@reapp-sdk/express-middleware";
|
|
88
|
-
|
|
89
|
-
class SharedRedemptionStore implements RedemptionStore {
|
|
90
|
-
async consumeOnce(
|
|
91
|
-
record: Readonly<RedemptionRecord>,
|
|
92
|
-
): Promise<"consumed" | "duplicate"> {
|
|
93
|
-
// Perform one atomic set-if-absent in your durable database.
|
|
94
|
-
// Never delete a consumed key merely because downstream delivery failed.
|
|
95
|
-
return atomicInsert(record.key, record) ? "consumed" : "duplicate";
|
|
96
|
-
}
|
|
97
|
-
}
|
|
82
|
+
```text
|
|
83
|
+
missing -> executing -> completed(exact JSON bytes)
|
|
98
84
|
```
|
|
99
85
|
|
|
100
|
-
|
|
101
|
-
|
|
86
|
+
- First valid proof: chain verification, atomic claim, one callback execution.
|
|
87
|
+
- Same proof while executing: `503`; the callback is never started again.
|
|
88
|
+
- Same proof after completion: exact stored bytes are replayed; no verifier or
|
|
89
|
+
callback runs.
|
|
90
|
+
- Same transaction with another proof: `409`.
|
|
91
|
+
- Store/RPC outage: `503`; no protected result is sent.
|
|
92
|
+
- Callback exception: one sanitized terminal JSON result is stored and replayed.
|
|
93
|
+
- Completion-store failure: no result bytes are sent and the claim remains
|
|
94
|
+
executing; recovery cannot re-run it automatically. After confirming the
|
|
95
|
+
execution owner is dead, trusted operator/outbox code calls
|
|
96
|
+
`resolveBoundReappInterruptedDelivery` to store one terminal result.
|
|
97
|
+
|
|
98
|
+
The first-redemption deadline does not prevent later replay of an already
|
|
99
|
+
completed exact result. That replay is delivery recovery, not fresh payment
|
|
100
|
+
authorization.
|
|
101
|
+
|
|
102
|
+
## Store deployment boundary
|
|
103
|
+
|
|
104
|
+
`InMemoryBoundRedemptionStore` is only for one-process demos and tests.
|
|
105
|
+
`FileBoundRedemptionStore` in the reference fulfillment app is restart-safe for
|
|
106
|
+
one Node.js process; instances targeting the same normalized path share one
|
|
107
|
+
in-process queue and use fsynced atomic replacement. It is not multi-process or
|
|
108
|
+
multi-host storage.
|
|
109
|
+
|
|
110
|
+
A production deployment must implement `BoundRedemptionStore.lookup`, `claim`,
|
|
111
|
+
and `complete` in one shared durable linearizable database. Never add a lease
|
|
112
|
+
that silently turns an executing claim back into runnable work. Side effects
|
|
113
|
+
must be transactionally coordinated with the claim through a durable job/outbox.
|
|
102
114
|
|
|
103
115
|
## Response behavior
|
|
104
116
|
|
|
105
|
-
| Condition | Status |
|
|
106
|
-
|
|
107
|
-
|
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
|
|
|
117
|
+
| Condition | Status |
|
|
118
|
+
|---|---:|
|
|
119
|
+
| Missing/wrong bound-v2 capability | `426` before payment |
|
|
120
|
+
| Method other than GET | `405` |
|
|
121
|
+
| Missing, malformed, expired-first-use, mismatched, or unverified proof | `402` |
|
|
122
|
+
| Same settlement with a different proof | `409` |
|
|
123
|
+
| Existing execution or infrastructure/store outage | `503`, retry exact proof |
|
|
124
|
+
| New completed fulfillment | stored 2xx JSON |
|
|
125
|
+
| Exact completed recovery | byte-identical stored 2xx JSON |
|
|
111
126
|
|
|
112
|
-
|
|
113
|
-
and
|
|
127
|
+
All responses are private/no-store. Proof and stored result material are
|
|
128
|
+
sensitive and must not be logged or exposed.
|
|
114
129
|
|
|
115
|
-
##
|
|
130
|
+
## Primary API
|
|
116
131
|
|
|
117
|
-
`
|
|
118
|
-
resolved requirement before any asynchronous verification:
|
|
132
|
+
### `createBoundReappPaidJsonRoute(options, fulfill)`
|
|
119
133
|
|
|
120
|
-
|
|
121
|
-
const requirePayment = createReappPaymentMiddleware({
|
|
122
|
-
merchant: MERCHANT,
|
|
123
|
-
sourceAccount: READ_SOURCE,
|
|
124
|
-
amount: (request) => priceFor(request.params.id),
|
|
125
|
-
resource: (request) => `/source/${request.params.id}`,
|
|
126
|
-
redemptionStore,
|
|
127
|
-
});
|
|
128
|
-
```
|
|
134
|
+
Required options:
|
|
129
135
|
|
|
130
|
-
|
|
131
|
-
amounts, scientific notation, over-precision, zero, and values outside i128 fail
|
|
132
|
-
closed.
|
|
133
|
-
|
|
134
|
-
## API
|
|
135
|
-
|
|
136
|
-
### `createReappPaymentMiddleware(options)`
|
|
137
|
-
|
|
138
|
-
Creates an Express 4/5 `RequestHandler`.
|
|
139
|
-
|
|
140
|
-
| Option | Type | Meaning |
|
|
141
|
-
|---|---|---|
|
|
142
|
-
| `merchant` | `string` | Required Stellar address that must receive the transfer. |
|
|
143
|
-
| `amount` | `string \| (request) => string` | Required decimal price. |
|
|
144
|
-
| `redemptionStore` | `RedemptionStore` | Required atomic consume-once store. |
|
|
145
|
-
| `sourceAccount` | `string?` | Funded `G...` address for read-only simulations; required by the default verifier. |
|
|
146
|
-
| `resource` | `string \| (request) => string` | Challenge resource id; defaults to `request.originalUrl`. |
|
|
147
|
-
| `asset` | `string?` | Required SEP-41 emitter; defaults to the configured network's native SAC. |
|
|
148
|
-
| `networkConfig` | `NetworkConfig?` | RPC, passphrase, registry, and native SAC; defaults to REAPP testnet. |
|
|
149
|
-
| `scheme` | `string?` | Wire scheme; defaults to `reapp-soroban`. |
|
|
150
|
-
| `network` | `string?` | Wire network label; defaults to `stellar-testnet`. |
|
|
151
|
-
| `decimals` | `number?` | Asset decimals; defaults to `7`. |
|
|
152
|
-
| `maxProofAgeLedgers` | `number?` | Settlement freshness window; defaults to `120`. |
|
|
153
|
-
| `pollAttempts` | `number?` | `NOT_FOUND` retries; defaults to `15`. |
|
|
154
|
-
| `pollIntervalMs` | `number?` | Retry delay; defaults to `1000`. |
|
|
155
|
-
| `maxHeaderBytes` | `number?` | Strict `X-PAYMENT` size limit; defaults to `8192`. |
|
|
156
|
-
| `allowHttpRpc` | `boolean?` | Development-only plaintext RPC escape hatch; defaults to `false`. |
|
|
157
|
-
| `verifier` | `PaymentVerifier?` | Explicit trusted verifier injection for tests or alternate RPC infrastructure. |
|
|
158
|
-
|
|
159
|
-
### Runtime exports
|
|
160
|
-
|
|
161
|
-
| Export | Purpose |
|
|
136
|
+
| Option | Meaning |
|
|
162
137
|
|---|---|
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `createRedemptionKey` | Create the normalized cross-network replay key. |
|
|
169
|
-
| `extractContractEvents`, `interpretEvents` | Strict V3/V4 Stellar event decoding helpers. |
|
|
170
|
-
| `selectPayment`, `selectTransfer` | Pure fail-closed event selection helpers. |
|
|
171
|
-
|
|
172
|
-
The package also exports TypeScript types for requirements, verified payments,
|
|
173
|
-
verifiers, redemption records and stores, middleware options, and decoded event
|
|
174
|
-
evidence.
|
|
138
|
+
| `merchant` | Stellar address that must receive the verified transfer. |
|
|
139
|
+
| `amount` | Decimal price or request-specific resolver. |
|
|
140
|
+
| `audience` | Exact configured public HTTP(S) origin or safe resolver. |
|
|
141
|
+
| `challengeSecret` | Stable private 32–4096 byte challenge key. |
|
|
142
|
+
| `redemptionStore` | Atomic claim/result store shared by all serving workers. |
|
|
175
143
|
|
|
176
|
-
|
|
144
|
+
Optional controls include `resource`, `asset`, `networkConfig`, `network`,
|
|
145
|
+
`decimals`, `sourceAccount`, verifier/polling/freshness/header limits,
|
|
146
|
+
`challengeTtlSeconds`, development-only HTTP RPC, and `maxResponseBytes`.
|
|
147
|
+
|
|
148
|
+
Runtime exports include `createBoundReappPaidJsonRoute`,
|
|
149
|
+
`resolveBoundReappInterruptedDelivery`, `InMemoryBoundRedemptionStore`,
|
|
150
|
+
`createStellarPaymentVerifier`, strict event
|
|
151
|
+
selection helpers, and all TypeScript store/evidence/result types.
|
|
177
152
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
storage, and verifier invariants remain intact.
|
|
153
|
+
Legacy proof-v1 middleware remains available only through the legacy API. The
|
|
154
|
+
low-level bound authorization middleware is intentionally not exported from the
|
|
155
|
+
package root; public paid endpoints use the result-storing route wrapper.
|
|
182
156
|
|
|
183
|
-
##
|
|
157
|
+
## Wire-format isolation
|
|
184
158
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
- The challenge resource is not committed into the current contract event.
|
|
188
|
-
Exact requester/resource binding requires an authenticated nonce/signature or
|
|
189
|
-
a future contract/event commitment; this package does not claim that property.
|
|
190
|
-
- `get_mandate` reads current contract state, not a historical state snapshot.
|
|
191
|
-
Same-transaction registry and token events prove settlement, while current
|
|
192
|
-
state binds the mandate identities and asset.
|
|
159
|
+
x402 and AP2 evolve outside the MandateRegistry. HTTP/profile adapters may
|
|
160
|
+
change without changing contract storage or weakening `execute_payment`.
|
|
193
161
|
|
|
194
162
|
## Current contract evidence
|
|
195
163
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
- Contract: [`CC6JMPDHRPBR2HBLJKRCIKV54HXDV2RFXDKW6MALQKWM6JEAJQHICRWE`](https://stellar.expert/explorer/testnet/contract/CC6JMPDHRPBR2HBLJKRCIKV54HXDV2RFXDKW6MALQKWM6JEAJQHICRWE)
|
|
164
|
+
- Testnet contract: [`CC6JMPDH…CRWE`](https://stellar.expert/explorer/testnet/contract/CC6JMPDHRPBR2HBLJKRCIKV54HXDV2RFXDKW6MALQKWM6JEAJQHICRWE)
|
|
199
165
|
- WASM SHA-256: `13f7023d4a361b6e49d3d39f61f55c5eeece51a602013a3cddae420d2ce8552b`
|
|
200
|
-
-
|
|
201
|
-
|
|
202
|
-
The contract checks and consumes authorization inside `execute_payment` before
|
|
203
|
-
the token transfer. Testnet operations include pause, authority rotation, and a
|
|
204
|
-
24-hour timelocked same-address upgrade path.
|
|
166
|
+
- Release: [`simple-v0.2.0`](https://github.com/reapp-protocol/reapp-protocol-contracts/releases/tag/simple-v0.2.0_contracts_simple_mandate_registry_mandate-registry_pkg0.2.0_cli25.1.0)
|
|
205
167
|
|
|
206
168
|
Apache-2.0.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Request, RequestHandler } from "express";
|
|
2
|
+
import { type BoundReappPaymentMiddlewareOptions } from "./bound.js";
|
|
3
|
+
import type { BoundDeliveryRecord, BoundRedemptionStore } from "./bound-store.js";
|
|
4
|
+
import type { VerifiedPayment } from "./types.js";
|
|
5
|
+
export interface BoundJsonResult {
|
|
6
|
+
/** Successful paid responses only. Defaults to 200. */
|
|
7
|
+
status?: number;
|
|
8
|
+
body: unknown;
|
|
9
|
+
}
|
|
10
|
+
export interface BoundJsonFulfillmentContext {
|
|
11
|
+
request: Request;
|
|
12
|
+
payment: Readonly<VerifiedPayment>;
|
|
13
|
+
}
|
|
14
|
+
export type BoundJsonFulfillment = (context: BoundJsonFulfillmentContext) => BoundJsonResult | Promise<BoundJsonResult>;
|
|
15
|
+
export interface BoundReappPaidJsonRouteOptions extends BoundReappPaymentMiddlewareOptions {
|
|
16
|
+
/** Maximum stored UTF-8 JSON response size. Defaults to 1 MiB. */
|
|
17
|
+
maxResponseBytes?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve an orphaned at-most-once execution without invoking fulfillment
|
|
21
|
+
* again. Call this only from trusted operator/outbox code after confirming the
|
|
22
|
+
* original execution owner is dead. The exact terminal bytes become immutable
|
|
23
|
+
* and subsequent receipt recovery replays them.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveBoundReappInterruptedDelivery(options: {
|
|
26
|
+
redemptionStore: BoundRedemptionStore;
|
|
27
|
+
record: Readonly<BoundDeliveryRecord>;
|
|
28
|
+
maxResponseBytes?: number;
|
|
29
|
+
}): Promise<Readonly<BoundDeliveryRecord>>;
|
|
30
|
+
/**
|
|
31
|
+
* Safe paid JSON route. Fulfillment executes once after an atomic claim; its
|
|
32
|
+
* exact bytes are stored before they are sent. Recovery replays those bytes and
|
|
33
|
+
* never invokes the fulfillment callback again.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createBoundReappPaidJsonRoute(options: BoundReappPaidJsonRouteOptions, fulfill: BoundJsonFulfillment): RequestHandler;
|
|
36
|
+
//# sourceMappingURL=bound-route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bound-route.d.ts","sourceRoot":"","sources":["../src/bound-route.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAgB,OAAO,EAAE,cAAc,EAAY,MAAM,SAAS,CAAC;AAC/E,OAAO,EAGL,KAAK,kCAAkC,EACxC,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EAErB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,WAAW,eAAe;IAC9B,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;CACpC;AAED,MAAM,MAAM,oBAAoB,GAAG,CACjC,OAAO,EAAE,2BAA2B,KACjC,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;AAEhD,MAAM,WAAW,8BAA+B,SAAQ,kCAAkC;IACxF,kEAAkE;IAClE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AA6BD;;;;;GAKG;AACH,wBAAsB,oCAAoC,CAAC,OAAO,EAAE;IAClE,eAAe,EAAE,oBAAoB,CAAC;IACtC,MAAM,EAAE,QAAQ,CAAC,mBAAmB,CAAC,CAAC;IACtC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GAAG,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC,CAkBzC;AAgDD;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,8BAA8B,EACvC,OAAO,EAAE,oBAAoB,GAC5B,cAAc,CAuDhB"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { Buffer } from "buffer";
|
|
3
|
+
import { createBoundReappPaymentMiddleware, getBoundDeliveryContext, } from "./bound.js";
|
|
4
|
+
const CONTENT_TYPE = "application/json; charset=utf-8";
|
|
5
|
+
const DEFAULT_MAX_RESPONSE_BYTES = 1_048_576;
|
|
6
|
+
function storedResponse(status, body, maxBytes) {
|
|
7
|
+
if (!Number.isInteger(status) || status < 200 || status > 299) {
|
|
8
|
+
throw new Error("paid JSON fulfillment status must be an integer from 200 through 299");
|
|
9
|
+
}
|
|
10
|
+
const json = JSON.stringify(body);
|
|
11
|
+
if (json === undefined)
|
|
12
|
+
throw new Error("paid JSON fulfillment body must be JSON-serializable");
|
|
13
|
+
const bytes = Buffer.from(json, "utf8");
|
|
14
|
+
if (bytes.length > maxBytes)
|
|
15
|
+
throw new Error("paid JSON fulfillment body exceeds maxResponseBytes");
|
|
16
|
+
return Object.freeze({
|
|
17
|
+
status,
|
|
18
|
+
contentType: CONTENT_TYPE,
|
|
19
|
+
bodyBase64: bytes.toString("base64"),
|
|
20
|
+
bodySha256: createHash("sha256").update(bytes).digest("hex"),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function terminalFailure(maxBytes) {
|
|
24
|
+
return storedResponse(200, {
|
|
25
|
+
ok: false,
|
|
26
|
+
error: "paid fulfillment failed after settlement",
|
|
27
|
+
deliveryState: "terminal",
|
|
28
|
+
}, maxBytes);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolve an orphaned at-most-once execution without invoking fulfillment
|
|
32
|
+
* again. Call this only from trusted operator/outbox code after confirming the
|
|
33
|
+
* original execution owner is dead. The exact terminal bytes become immutable
|
|
34
|
+
* and subsequent receipt recovery replays them.
|
|
35
|
+
*/
|
|
36
|
+
export async function resolveBoundReappInterruptedDelivery(options) {
|
|
37
|
+
if (options.record.state !== "executing") {
|
|
38
|
+
throw new Error("only an executing paid delivery can be resolved as interrupted");
|
|
39
|
+
}
|
|
40
|
+
const maxBytes = options.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES;
|
|
41
|
+
if (!Number.isInteger(maxBytes) || maxBytes < 1 || maxBytes > 16_777_216) {
|
|
42
|
+
throw new Error("maxResponseBytes must be an integer from 1 through 16777216");
|
|
43
|
+
}
|
|
44
|
+
const completed = await options.redemptionStore.complete({
|
|
45
|
+
key: options.record.key,
|
|
46
|
+
proofDigest: options.record.proofDigest,
|
|
47
|
+
executionId: options.record.executionId,
|
|
48
|
+
response: terminalFailure(maxBytes),
|
|
49
|
+
});
|
|
50
|
+
if (completed.kind !== "completed" || completed.record.state !== "completed") {
|
|
51
|
+
throw new Error("interrupted paid delivery could not be resolved atomically");
|
|
52
|
+
}
|
|
53
|
+
return completed.record;
|
|
54
|
+
}
|
|
55
|
+
function decodeStoredResponse(stored, maxBytes) {
|
|
56
|
+
if (!Number.isInteger(stored.status)
|
|
57
|
+
|| stored.status < 200
|
|
58
|
+
|| stored.status > 299
|
|
59
|
+
|| stored.contentType !== CONTENT_TYPE
|
|
60
|
+
|| !/^[0-9a-f]{64}$/.test(stored.bodySha256)
|
|
61
|
+
|| !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(stored.bodyBase64)) {
|
|
62
|
+
throw new Error("stored paid response schema is invalid");
|
|
63
|
+
}
|
|
64
|
+
const bytes = Buffer.from(stored.bodyBase64, "base64");
|
|
65
|
+
if (bytes.length > maxBytes
|
|
66
|
+
|| bytes.toString("base64") !== stored.bodyBase64
|
|
67
|
+
|| createHash("sha256").update(bytes).digest("hex") !== stored.bodySha256) {
|
|
68
|
+
throw new Error("stored paid response integrity check failed");
|
|
69
|
+
}
|
|
70
|
+
return bytes;
|
|
71
|
+
}
|
|
72
|
+
function sendStored(response, stored, maxBytes) {
|
|
73
|
+
const bytes = decodeStoredResponse(stored, maxBytes);
|
|
74
|
+
response.status(stored.status);
|
|
75
|
+
response.set("content-type", stored.contentType);
|
|
76
|
+
response.set("content-length", String(bytes.length));
|
|
77
|
+
response.set("cache-control", "private, no-store");
|
|
78
|
+
response.set("x-content-type-options", "nosniff");
|
|
79
|
+
response.end(bytes);
|
|
80
|
+
}
|
|
81
|
+
function unavailable(response, message) {
|
|
82
|
+
response.status(503);
|
|
83
|
+
response.set("retry-after", "1");
|
|
84
|
+
response.set("cache-control", "private, no-store");
|
|
85
|
+
response.json({ error: message, retryable: true });
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Safe paid JSON route. Fulfillment executes once after an atomic claim; its
|
|
89
|
+
* exact bytes are stored before they are sent. Recovery replays those bytes and
|
|
90
|
+
* never invokes the fulfillment callback again.
|
|
91
|
+
*/
|
|
92
|
+
export function createBoundReappPaidJsonRoute(options, fulfill) {
|
|
93
|
+
if (typeof fulfill !== "function")
|
|
94
|
+
throw new Error("paid JSON fulfillment callback is required");
|
|
95
|
+
const maxBytes = options.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES;
|
|
96
|
+
if (!Number.isInteger(maxBytes) || maxBytes < 1 || maxBytes > 16_777_216) {
|
|
97
|
+
throw new Error("maxResponseBytes must be an integer from 1 through 16777216");
|
|
98
|
+
}
|
|
99
|
+
const authorize = createBoundReappPaymentMiddleware(options);
|
|
100
|
+
return (request, response, next) => {
|
|
101
|
+
authorize(request, response, (authorizationError) => {
|
|
102
|
+
if (authorizationError) {
|
|
103
|
+
next(authorizationError);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
void (async () => {
|
|
107
|
+
const context = getBoundDeliveryContext(response);
|
|
108
|
+
if (!context)
|
|
109
|
+
throw new Error("bound delivery context was unavailable");
|
|
110
|
+
if (context.kind === "completed") {
|
|
111
|
+
if (context.record.state !== "completed")
|
|
112
|
+
throw new Error("completed delivery record is inconsistent");
|
|
113
|
+
sendStored(response, context.record.response, maxBytes);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (context.record.state !== "executing") {
|
|
117
|
+
throw new Error("claimed delivery record is inconsistent");
|
|
118
|
+
}
|
|
119
|
+
let result;
|
|
120
|
+
try {
|
|
121
|
+
const provided = await fulfill({ request, payment: context.record.payment });
|
|
122
|
+
result = storedResponse(provided.status ?? 200, provided.body, maxBytes);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Store one immutable terminal result. Never re-run arbitrary paid work.
|
|
126
|
+
result = terminalFailure(maxBytes);
|
|
127
|
+
}
|
|
128
|
+
let completed;
|
|
129
|
+
try {
|
|
130
|
+
completed = await options.redemptionStore.complete({
|
|
131
|
+
key: context.record.key,
|
|
132
|
+
proofDigest: context.record.proofDigest,
|
|
133
|
+
executionId: context.record.executionId,
|
|
134
|
+
response: result,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
unavailable(response, "paid fulfillment result store is unavailable; retry the same proof");
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (completed.kind !== "completed" || completed.record.state !== "completed") {
|
|
142
|
+
unavailable(response, "paid fulfillment result could not be committed");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
sendStored(response, completed.record.response, maxBytes);
|
|
146
|
+
})().catch(next);
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=bound-route.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bound-route.js","sourceRoot":"","sources":["../src/bound-route.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { VerifiedPayment } from "./types.js";
|
|
2
|
+
export interface BoundRedemptionRecord {
|
|
3
|
+
/** Network-passphrase hash, registry id, and normalized transaction hash. */
|
|
4
|
+
key: string;
|
|
5
|
+
/** SHA-256 of the strict decoded bound proof. */
|
|
6
|
+
proofDigest: string;
|
|
7
|
+
/** Chain-derived evidence captured when this exact proof was first accepted. */
|
|
8
|
+
payment: Readonly<VerifiedPayment>;
|
|
9
|
+
}
|
|
10
|
+
export interface StoredBoundJsonResponse {
|
|
11
|
+
status: number;
|
|
12
|
+
contentType: "application/json; charset=utf-8";
|
|
13
|
+
bodyBase64: string;
|
|
14
|
+
bodySha256: string;
|
|
15
|
+
}
|
|
16
|
+
export type BoundDeliveryRecord = Readonly<BoundRedemptionRecord> & Readonly<{
|
|
17
|
+
executionId: string;
|
|
18
|
+
startedAt: number;
|
|
19
|
+
}> & ({
|
|
20
|
+
state: "executing";
|
|
21
|
+
response?: never;
|
|
22
|
+
} | {
|
|
23
|
+
state: "completed";
|
|
24
|
+
response: Readonly<StoredBoundJsonResponse>;
|
|
25
|
+
});
|
|
26
|
+
export type BoundRedemptionLookup = {
|
|
27
|
+
kind: "missing";
|
|
28
|
+
} | {
|
|
29
|
+
kind: "executing";
|
|
30
|
+
record: Readonly<BoundDeliveryRecord>;
|
|
31
|
+
} | {
|
|
32
|
+
kind: "completed";
|
|
33
|
+
record: Readonly<BoundDeliveryRecord>;
|
|
34
|
+
} | {
|
|
35
|
+
kind: "conflict";
|
|
36
|
+
};
|
|
37
|
+
export type BoundRedemptionClaim = {
|
|
38
|
+
kind: "claimed";
|
|
39
|
+
record: Readonly<BoundDeliveryRecord>;
|
|
40
|
+
} | {
|
|
41
|
+
kind: "executing";
|
|
42
|
+
record: Readonly<BoundDeliveryRecord>;
|
|
43
|
+
} | {
|
|
44
|
+
kind: "completed";
|
|
45
|
+
record: Readonly<BoundDeliveryRecord>;
|
|
46
|
+
} | {
|
|
47
|
+
kind: "conflict";
|
|
48
|
+
};
|
|
49
|
+
export interface BoundRedemptionCompletion {
|
|
50
|
+
key: string;
|
|
51
|
+
proofDigest: string;
|
|
52
|
+
executionId: string;
|
|
53
|
+
response: Readonly<StoredBoundJsonResponse>;
|
|
54
|
+
}
|
|
55
|
+
export type BoundRedemptionComplete = {
|
|
56
|
+
kind: "completed";
|
|
57
|
+
record: Readonly<BoundDeliveryRecord>;
|
|
58
|
+
} | {
|
|
59
|
+
kind: "conflict";
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* One linearizable store owns both settlement binding and immutable delivery
|
|
63
|
+
* bytes. A proof is claimed at most once; recovery either waits for that claim
|
|
64
|
+
* or replays its completed result without re-running fulfillment.
|
|
65
|
+
*/
|
|
66
|
+
export interface BoundRedemptionStore {
|
|
67
|
+
lookup(key: string, proofDigest: string): BoundRedemptionLookup | Promise<BoundRedemptionLookup>;
|
|
68
|
+
claim(record: Readonly<BoundRedemptionRecord>, executionId: string, startedAt: number): BoundRedemptionClaim | Promise<BoundRedemptionClaim>;
|
|
69
|
+
complete(completion: Readonly<BoundRedemptionCompletion>): BoundRedemptionComplete | Promise<BoundRedemptionComplete>;
|
|
70
|
+
}
|
|
71
|
+
/** Process-local reference store. Production and restart drills need a durable shared store. */
|
|
72
|
+
export declare class InMemoryBoundRedemptionStore implements BoundRedemptionStore {
|
|
73
|
+
private readonly records;
|
|
74
|
+
lookup(key: string, proofDigest: string): BoundRedemptionLookup;
|
|
75
|
+
claim(record: Readonly<BoundRedemptionRecord>, executionId: string, startedAt: number): BoundRedemptionClaim;
|
|
76
|
+
complete(completion: Readonly<BoundRedemptionCompletion>): BoundRedemptionComplete;
|
|
77
|
+
get(key: string): Readonly<BoundDeliveryRecord> | undefined;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=bound-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bound-store.d.ts","sourceRoot":"","sources":["../src/bound-store.ts"],"names":[],"mappings":"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"}
|