@orbitmem/relay 0.1.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 +108 -0
- package/dist/app.d.ts +7 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +27 -0
- package/dist/app.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/ed25519-verify.d.ts +14 -0
- package/dist/middleware/ed25519-verify.d.ts.map +1 -0
- package/dist/middleware/ed25519-verify.js +31 -0
- package/dist/middleware/ed25519-verify.js.map +1 -0
- package/dist/middleware/erc8128.d.ts +25 -0
- package/dist/middleware/erc8128.d.ts.map +1 -0
- package/dist/middleware/erc8128.js +196 -0
- package/dist/middleware/erc8128.js.map +1 -0
- package/dist/middleware/mpp.d.ts +36 -0
- package/dist/middleware/mpp.d.ts.map +1 -0
- package/dist/middleware/mpp.js +122 -0
- package/dist/middleware/mpp.js.map +1 -0
- package/dist/middleware/session.d.ts +13 -0
- package/dist/middleware/session.d.ts.map +1 -0
- package/dist/middleware/session.js +47 -0
- package/dist/middleware/session.js.map +1 -0
- package/dist/routes/data.d.ts +5 -0
- package/dist/routes/data.d.ts.map +1 -0
- package/dist/routes/data.js +44 -0
- package/dist/routes/data.js.map +1 -0
- package/dist/routes/health.d.ts +3 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +4 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/snapshots.d.ts +5 -0
- package/dist/routes/snapshots.d.ts.map +1 -0
- package/dist/routes/snapshots.js +31 -0
- package/dist/routes/snapshots.js.map +1 -0
- package/dist/routes/vault.d.ts +6 -0
- package/dist/routes/vault.d.ts.map +1 -0
- package/dist/routes/vault.js +105 -0
- package/dist/routes/vault.js.map +1 -0
- package/dist/services/index.d.ts +17 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +60 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/live-discovery.d.ts +17 -0
- package/dist/services/live-discovery.d.ts.map +1 -0
- package/dist/services/live-discovery.js +97 -0
- package/dist/services/live-discovery.js.map +1 -0
- package/dist/services/live-snapshot.d.ts +18 -0
- package/dist/services/live-snapshot.d.ts.map +1 -0
- package/dist/services/live-snapshot.js +43 -0
- package/dist/services/live-snapshot.js.map +1 -0
- package/dist/services/live-vault.d.ts +27 -0
- package/dist/services/live-vault.d.ts.map +1 -0
- package/dist/services/live-vault.js +73 -0
- package/dist/services/live-vault.js.map +1 -0
- package/dist/services/mock-discovery.d.ts +14 -0
- package/dist/services/mock-discovery.d.ts.map +1 -0
- package/dist/services/mock-discovery.js +86 -0
- package/dist/services/mock-discovery.js.map +1 -0
- package/dist/services/mock-snapshot.d.ts +7 -0
- package/dist/services/mock-snapshot.d.ts.map +1 -0
- package/dist/services/mock-snapshot.js +30 -0
- package/dist/services/mock-snapshot.js.map +1 -0
- package/dist/services/mock-vault.d.ts +33 -0
- package/dist/services/mock-vault.d.ts.map +1 -0
- package/dist/services/mock-vault.js +88 -0
- package/dist/services/mock-vault.js.map +1 -0
- package/dist/services/orbitdb-peer.d.ts +12 -0
- package/dist/services/orbitdb-peer.d.ts.map +1 -0
- package/dist/services/orbitdb-peer.js +23 -0
- package/dist/services/orbitdb-peer.js.map +1 -0
- package/dist/services/plan.d.ts +14 -0
- package/dist/services/plan.d.ts.map +1 -0
- package/dist/services/plan.js +37 -0
- package/dist/services/plan.js.map +1 -0
- package/dist/services/test-helpers.d.ts +4 -0
- package/dist/services/test-helpers.d.ts.map +1 -0
- package/dist/services/test-helpers.js +10 -0
- package/dist/services/test-helpers.js.map +1 -0
- package/dist/services/types.d.ts +100 -0
- package/dist/services/types.d.ts.map +1 -0
- package/dist/services/types.js +2 -0
- package/dist/services/types.js.map +1 -0
- package/package.json +70 -0
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# @orbitmem/relay
|
|
2
|
+
|
|
3
|
+
HTTP relay server for OrbitMem — authenticated vault access, data discovery, and snapshot archival.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @orbitmem/relay
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Development (hot reload)
|
|
15
|
+
PORT=3000 RELAY_MODE=mock bun run --hot src/index.ts
|
|
16
|
+
|
|
17
|
+
# Production
|
|
18
|
+
bun run dist/index.js
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Environment Variables
|
|
22
|
+
|
|
23
|
+
| Variable | Default | Description |
|
|
24
|
+
|----------|---------|-------------|
|
|
25
|
+
| `PORT` | `3000` | Server port |
|
|
26
|
+
| `RELAY_MODE` | `mock` | `mock` (in-memory) or `live` (OrbitDB/IPFS) |
|
|
27
|
+
|
|
28
|
+
## API Endpoints
|
|
29
|
+
|
|
30
|
+
All routes are prefixed with `/v1`.
|
|
31
|
+
|
|
32
|
+
### Health
|
|
33
|
+
|
|
34
|
+
| Method | Path | Auth | Description |
|
|
35
|
+
|--------|------|------|-------------|
|
|
36
|
+
| GET | `/health` | No | Health check |
|
|
37
|
+
|
|
38
|
+
### Auth
|
|
39
|
+
|
|
40
|
+
| Method | Path | Auth | Description |
|
|
41
|
+
|--------|------|------|-------------|
|
|
42
|
+
| POST | `/auth/challenge` | No | Generate nonce + message for wallet signing |
|
|
43
|
+
| POST | `/auth/session` | ERC-8128 | Issue session token |
|
|
44
|
+
|
|
45
|
+
### Vault
|
|
46
|
+
|
|
47
|
+
| Method | Path | Auth | Description |
|
|
48
|
+
|--------|------|------|-------------|
|
|
49
|
+
| GET | `/vault/public/:address/keys` | No | List public keys |
|
|
50
|
+
| GET | `/vault/public/:address/:key` | No | Read public value |
|
|
51
|
+
| POST | `/vault/read` | ERC-8128 | Read encrypted value |
|
|
52
|
+
| POST | `/vault/write` | ERC-8128 | Write vault entry |
|
|
53
|
+
| POST | `/vault/delete` | ERC-8128 | Delete vault entry |
|
|
54
|
+
| POST | `/vault/keys` | ERC-8128 | List vault keys |
|
|
55
|
+
| POST | `/vault/sync` | ERC-8128 | Trigger vault sync |
|
|
56
|
+
|
|
57
|
+
### Discovery
|
|
58
|
+
|
|
59
|
+
| Method | Path | Auth | Description |
|
|
60
|
+
|--------|------|------|-------------|
|
|
61
|
+
| GET | `/data/stats` | No | Global data statistics |
|
|
62
|
+
| GET | `/data/user/stats` | ERC-8128 | Per-user statistics |
|
|
63
|
+
| GET | `/data/search` | No | Search data registrations |
|
|
64
|
+
| GET | `/data/:dataId/score` | No | Get data quality score |
|
|
65
|
+
|
|
66
|
+
### Snapshots
|
|
67
|
+
|
|
68
|
+
| Method | Path | Auth | Description |
|
|
69
|
+
|--------|------|------|-------------|
|
|
70
|
+
| GET | `/snapshots` | ERC-8128 | List snapshots |
|
|
71
|
+
| POST | `/snapshots/archive` | ERC-8128 | Archive snapshot |
|
|
72
|
+
| GET | `/snapshots/usage` | ERC-8128 | Storage usage and limit |
|
|
73
|
+
|
|
74
|
+
## Authentication
|
|
75
|
+
|
|
76
|
+
The relay supports three authentication methods, checked in order:
|
|
77
|
+
|
|
78
|
+
1. **Bearer Session Token** — Stateless HMAC-SHA256 token (fastest)
|
|
79
|
+
```
|
|
80
|
+
Authorization: Bearer <token>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
2. **RFC 9421 Signature** — Standard HTTP message signatures with ERC-8128 verification
|
|
84
|
+
|
|
85
|
+
3. **Legacy X-OrbitMem Headers** — Backward-compatible signed requests
|
|
86
|
+
```
|
|
87
|
+
X-OrbitMem-Signer: 0x...
|
|
88
|
+
X-OrbitMem-Timestamp: 1234567890
|
|
89
|
+
X-OrbitMem-Nonce: unique-id
|
|
90
|
+
X-OrbitMem-Signature: 0x...
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Programmatic Usage
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { createApp } from "@orbitmem/relay/app";
|
|
97
|
+
import { createMockServices } from "@orbitmem/relay/services";
|
|
98
|
+
|
|
99
|
+
const services = createMockServices();
|
|
100
|
+
const app = createApp(services);
|
|
101
|
+
|
|
102
|
+
// Use with any Hono-compatible runtime
|
|
103
|
+
export default app;
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT
|
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { type MPPConfig } from "./middleware/mpp.js";
|
|
3
|
+
import type { RelayServices } from "./services/types.js";
|
|
4
|
+
export declare function buildApp(services: RelayServices, mppConfig?: MPPConfig): Hono;
|
|
5
|
+
declare const app: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
6
|
+
export { app };
|
|
7
|
+
//# sourceMappingURL=app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI5B,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAMrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,aAAa,EAAE,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,CAS7E;AASD,QAAA,MAAM,GAAG,4EAAmD,CAAC;AAE7D,OAAO,EAAE,GAAG,EAAE,CAAC"}
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { cors } from "hono/cors";
|
|
3
|
+
import { logger } from "hono/logger";
|
|
4
|
+
import { createDataRoutes } from "./routes/data.js";
|
|
5
|
+
import { healthRoutes } from "./routes/health.js";
|
|
6
|
+
import { createSnapshotRoutes } from "./routes/snapshots.js";
|
|
7
|
+
import { createVaultRoutes } from "./routes/vault.js";
|
|
8
|
+
import { createMockServices } from "./services/index.js";
|
|
9
|
+
export function buildApp(services, mppConfig) {
|
|
10
|
+
const app = new Hono().basePath("/v1");
|
|
11
|
+
app.use(logger());
|
|
12
|
+
app.use(cors());
|
|
13
|
+
app.route("/", healthRoutes);
|
|
14
|
+
app.route("/", createVaultRoutes(services.vault, mppConfig));
|
|
15
|
+
app.route("/", createDataRoutes(services.discovery));
|
|
16
|
+
app.route("/", createSnapshotRoutes(services.snapshot, services.plan));
|
|
17
|
+
return app;
|
|
18
|
+
}
|
|
19
|
+
const defaultMppConfig = process.env.MPP_ACCEPTED_METHODS
|
|
20
|
+
? {
|
|
21
|
+
acceptedMethods: process.env.MPP_ACCEPTED_METHODS.split(","),
|
|
22
|
+
network: (process.env.MPP_NETWORK ?? "base-sepolia"),
|
|
23
|
+
}
|
|
24
|
+
: undefined;
|
|
25
|
+
const app = buildApp(createMockServices(), defaultMppConfig);
|
|
26
|
+
export { app };
|
|
27
|
+
//# sourceMappingURL=app.js.map
|
package/dist/app.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAGzD,MAAM,UAAU,QAAQ,CAAC,QAAuB,EAAE,SAAqB;IACrE,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACvC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IAClB,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAChB,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IAC7B,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,iBAAiB,CAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;IAC7D,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,gBAAgB,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;IACrD,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,oBAAoB,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IACvE,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,gBAAgB,GAA0B,OAAO,CAAC,GAAG,CAAC,oBAAoB;IAC9E,CAAC,CAAC;QACE,eAAe,EAAE,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,KAAK,CAAC,GAAG,CAAiC;QAC5F,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,cAAc,CAAyB;KAC7E;IACH,CAAC,CAAC,SAAS,CAAC;AAEd,MAAM,GAAG,GAAG,QAAQ,CAAC,kBAAkB,EAAE,EAAE,gBAAgB,CAAC,CAAC;AAE7D,OAAO,EAAE,GAAG,EAAE,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;AA4BA,wBAA0C"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { buildApp } from "./app.js";
|
|
2
|
+
import { createLiveServices, createMockServices } from "./services/index.js";
|
|
3
|
+
const port = Number(process.env.PORT ?? 3000);
|
|
4
|
+
const mode = process.env.RELAY_MODE ?? "mock";
|
|
5
|
+
console.log(`OrbitMem Relay starting on port ${port} (mode: ${mode})`);
|
|
6
|
+
let app;
|
|
7
|
+
if (mode === "live") {
|
|
8
|
+
const services = await createLiveServices();
|
|
9
|
+
app = buildApp(services);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
app = buildApp(createMockServices());
|
|
13
|
+
}
|
|
14
|
+
// Graceful shutdown
|
|
15
|
+
process.on("SIGINT", () => {
|
|
16
|
+
console.log("Shutting down...");
|
|
17
|
+
process.exit(0);
|
|
18
|
+
});
|
|
19
|
+
process.on("SIGTERM", () => {
|
|
20
|
+
console.log("Shutting down...");
|
|
21
|
+
process.exit(0);
|
|
22
|
+
});
|
|
23
|
+
export default { port, fetch: app.fetch };
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE7E,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;AAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,MAAM,CAAC;AAE9C,OAAO,CAAC,GAAG,CAAC,mCAAmC,IAAI,WAAW,IAAI,GAAG,CAAC,CAAC;AAEvE,IAAI,GAAgC,CAAC;AAErC,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;IACpB,MAAM,QAAQ,GAAG,MAAM,kBAAkB,EAAE,CAAC;IAC5C,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC3B,CAAC;KAAM,CAAC;IACN,GAAG,GAAG,QAAQ,CAAC,kBAAkB,EAAE,CAAC,CAAC;AACvC,CAAC;AAED,oBAAoB;AACpB,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;IACxB,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAChC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;IACzB,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAChC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,eAAe,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify an Ed25519 signature against a payload and public key.
|
|
3
|
+
* Used for Solana wallet authentication in the legacy X-OrbitMem-* header path.
|
|
4
|
+
*
|
|
5
|
+
* The signer address is the hex-encoded Ed25519 public key (not base58).
|
|
6
|
+
* This doubles as both identifier and verification key.
|
|
7
|
+
*
|
|
8
|
+
* @param payload - The signed payload bytes
|
|
9
|
+
* @param signature - The Ed25519 signature (64 bytes)
|
|
10
|
+
* @param publicKeyHex - The signer's public key as hex string (64 hex chars = 32 bytes)
|
|
11
|
+
* @returns true if the signature is valid
|
|
12
|
+
*/
|
|
13
|
+
export declare function verifyEd25519(payload: Uint8Array, signature: Uint8Array, publicKeyHex: string): Promise<boolean>;
|
|
14
|
+
//# sourceMappingURL=ed25519-verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ed25519-verify.d.ts","sourceRoot":"","sources":["../../src/middleware/ed25519-verify.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,UAAU,EACnB,SAAS,EAAE,UAAU,EACrB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,OAAO,CAAC,CAOlB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
2
|
+
/**
|
|
3
|
+
* Verify an Ed25519 signature against a payload and public key.
|
|
4
|
+
* Used for Solana wallet authentication in the legacy X-OrbitMem-* header path.
|
|
5
|
+
*
|
|
6
|
+
* The signer address is the hex-encoded Ed25519 public key (not base58).
|
|
7
|
+
* This doubles as both identifier and verification key.
|
|
8
|
+
*
|
|
9
|
+
* @param payload - The signed payload bytes
|
|
10
|
+
* @param signature - The Ed25519 signature (64 bytes)
|
|
11
|
+
* @param publicKeyHex - The signer's public key as hex string (64 hex chars = 32 bytes)
|
|
12
|
+
* @returns true if the signature is valid
|
|
13
|
+
*/
|
|
14
|
+
export async function verifyEd25519(payload, signature, publicKeyHex) {
|
|
15
|
+
try {
|
|
16
|
+
const publicKey = hexToBytes(publicKeyHex);
|
|
17
|
+
return ed25519.verify(signature, payload, publicKey);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function hexToBytes(hex) {
|
|
24
|
+
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
25
|
+
const bytes = new Uint8Array(clean.length / 2);
|
|
26
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
27
|
+
bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
28
|
+
}
|
|
29
|
+
return bytes;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=ed25519-verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ed25519-verify.js","sourceRoot":"","sources":["../../src/middleware/ed25519-verify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAEnD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAmB,EACnB,SAAqB,EACrB,YAAoB;IAEpB,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;QAC3C,OAAO,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,GAAW;IAC7B,MAAM,KAAK,GAAG,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IACxD,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC/C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,KAAK,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from "hono";
|
|
2
|
+
export type ERC8128Env = {
|
|
3
|
+
Variables: {
|
|
4
|
+
signer: string;
|
|
5
|
+
signerFamily: string;
|
|
6
|
+
signerAlgorithm: string;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* ERC-8128 signature verification middleware using @slicekit/erc8128.
|
|
11
|
+
*
|
|
12
|
+
* Options:
|
|
13
|
+
* - `verify: 'evm'` — cryptographic verification using viem (secp256k1 recovery)
|
|
14
|
+
* - `verifier: fn` — custom verification callback (legacy, mapped to verifyMessage)
|
|
15
|
+
* - neither — trusts headers without signature check (development/testing)
|
|
16
|
+
* - `required` — whether auth is required (default true)
|
|
17
|
+
* - `replayable` — allow replayable (nonce-less) class-bound signatures (default false)
|
|
18
|
+
*/
|
|
19
|
+
export declare function erc8128(opts?: {
|
|
20
|
+
verify?: "evm" | "auto";
|
|
21
|
+
verifier?: (payload: Uint8Array, signature: Uint8Array, algorithm: string) => Promise<boolean>;
|
|
22
|
+
required?: boolean;
|
|
23
|
+
replayable?: boolean;
|
|
24
|
+
}): MiddlewareHandler<ERC8128Env>;
|
|
25
|
+
//# sourceMappingURL=erc8128.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"erc8128.d.ts","sourceRoot":"","sources":["../../src/middleware/erc8128.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAM9C,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE;QACT,MAAM,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,MAAM,CAAC;QACrB,eAAe,EAAE,MAAM,CAAC;KACzB,CAAC;CACH,CAAC;AA4BF;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,IAAI,CAAC,EAAE;IAC7B,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAuEhC"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { createVerifierClient } from "@slicekit/erc8128";
|
|
2
|
+
import { getAddress, verifyMessage } from "viem";
|
|
3
|
+
import { verifyEd25519 } from "./ed25519-verify.js";
|
|
4
|
+
import { verifySessionToken } from "./session.js";
|
|
5
|
+
/**
|
|
6
|
+
* In-memory nonce store for replay protection.
|
|
7
|
+
* Entries auto-expire after their TTL.
|
|
8
|
+
*/
|
|
9
|
+
function createMemoryNonceStore() {
|
|
10
|
+
const seen = new Map(); // key -> expiry timestamp (ms)
|
|
11
|
+
function clean() {
|
|
12
|
+
const now = Date.now();
|
|
13
|
+
for (const [key, expiry] of seen) {
|
|
14
|
+
if (now > expiry)
|
|
15
|
+
seen.delete(key);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
async consume(key, ttlSeconds) {
|
|
20
|
+
clean();
|
|
21
|
+
if (seen.has(key))
|
|
22
|
+
return false; // already consumed
|
|
23
|
+
seen.set(key, Date.now() + ttlSeconds * 1000);
|
|
24
|
+
return true; // newly stored
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const nonceStore = createMemoryNonceStore();
|
|
29
|
+
/**
|
|
30
|
+
* ERC-8128 signature verification middleware using @slicekit/erc8128.
|
|
31
|
+
*
|
|
32
|
+
* Options:
|
|
33
|
+
* - `verify: 'evm'` — cryptographic verification using viem (secp256k1 recovery)
|
|
34
|
+
* - `verifier: fn` — custom verification callback (legacy, mapped to verifyMessage)
|
|
35
|
+
* - neither — trusts headers without signature check (development/testing)
|
|
36
|
+
* - `required` — whether auth is required (default true)
|
|
37
|
+
* - `replayable` — allow replayable (nonce-less) class-bound signatures (default false)
|
|
38
|
+
*/
|
|
39
|
+
export function erc8128(opts) {
|
|
40
|
+
const required = opts?.required ?? true;
|
|
41
|
+
const allowReplayable = opts?.replayable ?? true;
|
|
42
|
+
return async (c, next) => {
|
|
43
|
+
// --- Bearer session token path (fastest, no signature check) ---
|
|
44
|
+
const authHeader = c.req.header("Authorization");
|
|
45
|
+
if (authHeader?.startsWith("Bearer ")) {
|
|
46
|
+
const token = authHeader.slice(7);
|
|
47
|
+
const session = await verifySessionToken(token);
|
|
48
|
+
if (session) {
|
|
49
|
+
c.set("signer", session.address);
|
|
50
|
+
c.set("signerFamily", "session");
|
|
51
|
+
c.set("signerAlgorithm", "hmac-sha256");
|
|
52
|
+
await next();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// Invalid/expired bearer — check for ERC-8128 fallback headers
|
|
56
|
+
const hasLegacy = !!c.req.header("X-OrbitMem-Signer");
|
|
57
|
+
const hasRfc = !!c.req.header("Signature-Input");
|
|
58
|
+
if (!hasLegacy && !hasRfc) {
|
|
59
|
+
return c.json({ error: "Invalid or expired session token" }, 401);
|
|
60
|
+
}
|
|
61
|
+
// Fall through to ERC-8128 verification below
|
|
62
|
+
}
|
|
63
|
+
// Check if this is a legacy X-OrbitMem-* request or RFC 9421 Signature request
|
|
64
|
+
const hasLegacyHeaders = !!c.req.header("X-OrbitMem-Signer");
|
|
65
|
+
const hasRfc9421Headers = !!c.req.header("Signature-Input");
|
|
66
|
+
if (!hasLegacyHeaders && !hasRfc9421Headers) {
|
|
67
|
+
if (!required) {
|
|
68
|
+
await next();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
return c.json({ error: "Missing ERC-8128 headers" }, 401);
|
|
72
|
+
}
|
|
73
|
+
// --- Legacy X-OrbitMem-* header path (backward compat) ---
|
|
74
|
+
if (hasLegacyHeaders) {
|
|
75
|
+
return handleLegacy(c, next, opts);
|
|
76
|
+
}
|
|
77
|
+
// --- RFC 9421 / @slicekit/erc8128 path ---
|
|
78
|
+
const shouldVerify = opts?.verify === "evm" || opts?.verifier;
|
|
79
|
+
if (!shouldVerify) {
|
|
80
|
+
// Trust mode: parse keyid from Signature-Input to extract signer address
|
|
81
|
+
const sigInput = c.req.header("Signature-Input") ?? "";
|
|
82
|
+
const keyidMatch = sigInput.match(/keyid="(?:eip155|erc8128):(\d+):(0x[a-fA-F0-9]+)"/);
|
|
83
|
+
if (!keyidMatch) {
|
|
84
|
+
return c.json({ error: "Cannot parse signer from Signature-Input" }, 401);
|
|
85
|
+
}
|
|
86
|
+
c.set("signer", getAddress(keyidMatch[2]));
|
|
87
|
+
c.set("signerFamily", "evm");
|
|
88
|
+
c.set("signerAlgorithm", "ecdsa-secp256k1");
|
|
89
|
+
await next();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// Verified mode — use @slicekit/erc8128 verifier
|
|
93
|
+
const result = await verifyWithLibrary(c.req.raw.clone(), allowReplayable);
|
|
94
|
+
if (!result.ok) {
|
|
95
|
+
return c.json({ error: `Invalid signature: ${result.reason}` }, 401);
|
|
96
|
+
}
|
|
97
|
+
c.set("signer", getAddress(result.address));
|
|
98
|
+
c.set("signerFamily", "evm");
|
|
99
|
+
c.set("signerAlgorithm", "ecdsa-secp256k1");
|
|
100
|
+
await next();
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async function verifyWithLibrary(request, allowReplayable) {
|
|
104
|
+
const verifier = createVerifierClient({
|
|
105
|
+
verifyMessage: async ({ address, message, signature }) => {
|
|
106
|
+
return verifyMessage({ address, message, signature });
|
|
107
|
+
},
|
|
108
|
+
nonceStore,
|
|
109
|
+
defaults: {
|
|
110
|
+
replayable: allowReplayable,
|
|
111
|
+
classBoundPolicies: [["@authority"]],
|
|
112
|
+
clockSkewSec: 30,
|
|
113
|
+
maxValiditySec: 3600, // 1 hour max
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
return verifier.verifyRequest({ request });
|
|
117
|
+
}
|
|
118
|
+
// --- Legacy handler for backward compatibility with X-OrbitMem-* headers ---
|
|
119
|
+
const legacyNonceCache = new Map();
|
|
120
|
+
const LEGACY_NONCE_TTL = 5 * 60 * 1000;
|
|
121
|
+
const LEGACY_TIMESTAMP_TOLERANCE = 30 * 1000;
|
|
122
|
+
function cleanLegacyNonces() {
|
|
123
|
+
const now = Date.now();
|
|
124
|
+
for (const [nonce, ts] of legacyNonceCache) {
|
|
125
|
+
if (now - ts > LEGACY_NONCE_TTL)
|
|
126
|
+
legacyNonceCache.delete(nonce);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async function evmVerify(payload, signature, claimedSigner) {
|
|
130
|
+
try {
|
|
131
|
+
const sigHex = `0x${Array.from(signature)
|
|
132
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
133
|
+
.join("")}`;
|
|
134
|
+
return await verifyMessage({
|
|
135
|
+
address: claimedSigner,
|
|
136
|
+
message: { raw: payload },
|
|
137
|
+
signature: sigHex,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function handleLegacy(c, next, opts) {
|
|
145
|
+
const signer = c.req.header("X-OrbitMem-Signer");
|
|
146
|
+
const family = c.req.header("X-OrbitMem-Family");
|
|
147
|
+
const algorithm = c.req.header("X-OrbitMem-Algorithm");
|
|
148
|
+
const timestampStr = c.req.header("X-OrbitMem-Timestamp");
|
|
149
|
+
const nonce = c.req.header("X-OrbitMem-Nonce");
|
|
150
|
+
const signatureHex = c.req.header("X-OrbitMem-Signature");
|
|
151
|
+
if (!signer || !family || !algorithm || !timestampStr || !nonce || !signatureHex) {
|
|
152
|
+
return c.json({ error: "Missing ERC-8128 headers" }, 401);
|
|
153
|
+
}
|
|
154
|
+
const timestamp = Number(timestampStr);
|
|
155
|
+
const now = Date.now();
|
|
156
|
+
if (Math.abs(now - timestamp) > LEGACY_TIMESTAMP_TOLERANCE) {
|
|
157
|
+
return c.json({ error: "Request timestamp out of tolerance" }, 401);
|
|
158
|
+
}
|
|
159
|
+
cleanLegacyNonces();
|
|
160
|
+
if (legacyNonceCache.has(nonce)) {
|
|
161
|
+
return c.json({ error: "Replay detected: nonce already used" }, 401);
|
|
162
|
+
}
|
|
163
|
+
const shouldVerify = opts?.verify === "evm" || opts?.verify === "auto" || opts?.verifier;
|
|
164
|
+
if (shouldVerify) {
|
|
165
|
+
const body = c.req.method !== "GET" ? await c.req.text() : undefined;
|
|
166
|
+
const bodyHash = body
|
|
167
|
+
? new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(body)))
|
|
168
|
+
: new Uint8Array(0);
|
|
169
|
+
const payload = new TextEncoder().encode(`${c.req.method}\n${c.req.path}\n${timestamp}\n${nonce}\n${Array.from(bodyHash)
|
|
170
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
171
|
+
.join("")}`);
|
|
172
|
+
const signature = new Uint8Array(signatureHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
|
|
173
|
+
let valid;
|
|
174
|
+
if (opts?.verify === "evm" || (opts?.verify === "auto" && family === "evm")) {
|
|
175
|
+
valid = await evmVerify(payload, signature, signer);
|
|
176
|
+
}
|
|
177
|
+
else if (opts?.verify === "auto" && family === "solana" && algorithm === "ed25519") {
|
|
178
|
+
valid = await verifyEd25519(payload, signature, signer);
|
|
179
|
+
}
|
|
180
|
+
else if (opts?.verifier) {
|
|
181
|
+
valid = await opts.verifier(payload, signature, algorithm);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
valid = false;
|
|
185
|
+
}
|
|
186
|
+
if (!valid) {
|
|
187
|
+
return c.json({ error: "Invalid signature" }, 401);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
legacyNonceCache.set(nonce, Date.now());
|
|
191
|
+
c.set("signer", signer);
|
|
192
|
+
c.set("signerFamily", family);
|
|
193
|
+
c.set("signerAlgorithm", algorithm);
|
|
194
|
+
await next();
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=erc8128.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"erc8128.js","sourceRoot":"","sources":["../../src/middleware/erc8128.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from "hono";
|
|
2
|
+
import type { IVaultService } from "../services/types.js";
|
|
3
|
+
export type MPPConfig = {
|
|
4
|
+
acceptedMethods: ("tempo" | "stripe" | "lightning")[];
|
|
5
|
+
network: "base" | "base-sepolia";
|
|
6
|
+
};
|
|
7
|
+
export type MPPEnv = {
|
|
8
|
+
Variables: {
|
|
9
|
+
mppPayment?: {
|
|
10
|
+
producer: string;
|
|
11
|
+
amount: string;
|
|
12
|
+
currency: string;
|
|
13
|
+
method: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* MPP pricing middleware for vault read routes (GET with :address/:key params).
|
|
19
|
+
*/
|
|
20
|
+
export declare function mppPricing(opts: {
|
|
21
|
+
vault: IVaultService;
|
|
22
|
+
config: MPPConfig;
|
|
23
|
+
}): MiddlewareHandler<MPPEnv>;
|
|
24
|
+
/**
|
|
25
|
+
* MPP pricing middleware for POST /vault/read.
|
|
26
|
+
* Extracts vaultAddress from JSON body instead of route params.
|
|
27
|
+
*/
|
|
28
|
+
export declare function mppPricingPost(opts: {
|
|
29
|
+
vault: IVaultService;
|
|
30
|
+
config: MPPConfig;
|
|
31
|
+
}): MiddlewareHandler<MPPEnv & {
|
|
32
|
+
Variables: {
|
|
33
|
+
signer: string;
|
|
34
|
+
};
|
|
35
|
+
}>;
|
|
36
|
+
//# sourceMappingURL=mpp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mpp.d.ts","sourceRoot":"","sources":["../../src/middleware/mpp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAE9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,MAAM,MAAM,SAAS,GAAG;IACtB,eAAe,EAAE,CAAC,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC,EAAE,CAAC;IACtD,OAAO,EAAE,MAAM,GAAG,cAAc,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IACnB,SAAS,EAAE;QACT,UAAU,CAAC,EAAE;YACX,QAAQ,EAAE,MAAM,CAAC;YACjB,MAAM,EAAE,MAAM,CAAC;YACf,QAAQ,EAAE,MAAM,CAAC;YACjB,MAAM,EAAE,MAAM,CAAC;SAChB,CAAC;KACH,CAAC;CACH,CAAC;AA8BF;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE;IAC/B,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,SAAS,CAAC;CACnB,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAwD5B;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,SAAS,CAAC;CACnB,GAAG,iBAAiB,CAAC,MAAM,GAAG;IAAE,SAAS,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC,CAsDhE"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/** In-memory LRU pricing cache (address:path -> pricing). TTL: 60s. Only caches positive hits. */
|
|
2
|
+
const pricingCache = new Map();
|
|
3
|
+
const CACHE_TTL = 60_000;
|
|
4
|
+
const MAX_CACHE_SIZE = 1000;
|
|
5
|
+
function getCachedPricing(key) {
|
|
6
|
+
const entry = pricingCache.get(key);
|
|
7
|
+
if (!entry)
|
|
8
|
+
return undefined;
|
|
9
|
+
if (Date.now() > entry.expiry) {
|
|
10
|
+
pricingCache.delete(key);
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
return entry.value;
|
|
14
|
+
}
|
|
15
|
+
function setCachedPricing(key, value) {
|
|
16
|
+
if (pricingCache.size >= MAX_CACHE_SIZE) {
|
|
17
|
+
const keys = Array.from(pricingCache.keys());
|
|
18
|
+
for (let i = 0; i < keys.length / 2; i++) {
|
|
19
|
+
pricingCache.delete(keys[i]);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
pricingCache.set(key, { value, expiry: Date.now() + CACHE_TTL });
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* MPP pricing middleware for vault read routes (GET with :address/:key params).
|
|
26
|
+
*/
|
|
27
|
+
export function mppPricing(opts) {
|
|
28
|
+
const { vault, config } = opts;
|
|
29
|
+
return async (c, next) => {
|
|
30
|
+
const address = c.req.param("address");
|
|
31
|
+
const key = c.req.param("key");
|
|
32
|
+
if (!address || !key) {
|
|
33
|
+
await next();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const cacheKey = `${address}:${key}`;
|
|
37
|
+
let pricing = getCachedPricing(cacheKey);
|
|
38
|
+
if (pricing === undefined) {
|
|
39
|
+
const fetched = await vault.getVaultPricing(address, key);
|
|
40
|
+
if (fetched) {
|
|
41
|
+
setCachedPricing(cacheKey, fetched);
|
|
42
|
+
pricing = fetched;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (!pricing) {
|
|
46
|
+
await next();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const authHeader = c.req.header("Authorization");
|
|
50
|
+
if (!authHeader || !authHeader.startsWith("Payment ")) {
|
|
51
|
+
const challenge = `Payment realm="orbitmem", intent="charge", amount="${pricing.amount}", currency="${pricing.currency}", recipient="${address}", network="${config.network}"`;
|
|
52
|
+
c.header("WWW-Authenticate", challenge);
|
|
53
|
+
return c.json({
|
|
54
|
+
error: "payment_required",
|
|
55
|
+
amount: pricing.amount,
|
|
56
|
+
currency: pricing.currency,
|
|
57
|
+
recipient: address,
|
|
58
|
+
network: config.network,
|
|
59
|
+
methods: config.acceptedMethods,
|
|
60
|
+
}, 402);
|
|
61
|
+
}
|
|
62
|
+
// TODO: Verify payment credential via mppx once SDK API is confirmed.
|
|
63
|
+
// For now, accept any Authorization: Payment header as valid.
|
|
64
|
+
c.set("mppPayment", {
|
|
65
|
+
producer: address,
|
|
66
|
+
amount: pricing.amount,
|
|
67
|
+
currency: pricing.currency,
|
|
68
|
+
method: "unverified",
|
|
69
|
+
});
|
|
70
|
+
await next();
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* MPP pricing middleware for POST /vault/read.
|
|
75
|
+
* Extracts vaultAddress from JSON body instead of route params.
|
|
76
|
+
*/
|
|
77
|
+
export function mppPricingPost(opts) {
|
|
78
|
+
const { vault, config } = opts;
|
|
79
|
+
return async (c, next) => {
|
|
80
|
+
const body = await c.req.json();
|
|
81
|
+
const address = body.vaultAddress ?? c.get("signer");
|
|
82
|
+
const path = body.path;
|
|
83
|
+
if (!address || !path) {
|
|
84
|
+
await next();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const cacheKey = `${address}:${path}`;
|
|
88
|
+
let pricing = getCachedPricing(cacheKey);
|
|
89
|
+
if (pricing === undefined) {
|
|
90
|
+
const fetched = await vault.getVaultPricing(address, path);
|
|
91
|
+
if (fetched) {
|
|
92
|
+
setCachedPricing(cacheKey, fetched);
|
|
93
|
+
pricing = fetched;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (!pricing) {
|
|
97
|
+
await next();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const authHeader = c.req.header("Authorization");
|
|
101
|
+
if (!authHeader || !authHeader.startsWith("Payment ")) {
|
|
102
|
+
const challenge = `Payment realm="orbitmem", intent="charge", amount="${pricing.amount}", currency="${pricing.currency}", recipient="${address}", network="${config.network}"`;
|
|
103
|
+
c.header("WWW-Authenticate", challenge);
|
|
104
|
+
return c.json({
|
|
105
|
+
error: "payment_required",
|
|
106
|
+
amount: pricing.amount,
|
|
107
|
+
currency: pricing.currency,
|
|
108
|
+
recipient: address,
|
|
109
|
+
network: config.network,
|
|
110
|
+
methods: config.acceptedMethods,
|
|
111
|
+
}, 402);
|
|
112
|
+
}
|
|
113
|
+
c.set("mppPayment", {
|
|
114
|
+
producer: address,
|
|
115
|
+
amount: pricing.amount,
|
|
116
|
+
currency: pricing.currency,
|
|
117
|
+
method: "unverified",
|
|
118
|
+
});
|
|
119
|
+
await next();
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=mpp.js.map
|