@routedock/routedock 0.1.2 → 0.3.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/LICENSE +21 -0
- package/README.md +532 -4
- package/dist/MppSessionHandler-1EL8QBAn.d.cts +215 -0
- package/dist/MppSessionHandler-Bq4S-JTE.d.ts +215 -0
- package/dist/ProviderRegistry-CA_TdREG.d.ts +142 -0
- package/dist/ProviderRegistry-D-0rqK0N.d.cts +142 -0
- package/dist/RouteDockClient-Bh86bVly.d.ts +469 -0
- package/dist/RouteDockClient-go9aopfF.d.cts +469 -0
- package/dist/SessionReconciler-Bw-PoBVl.d.ts +134 -0
- package/dist/SessionReconciler-Cz2nuOlJ.d.cts +134 -0
- package/dist/SessionStore-B4-Hi4H8.d.cts +143 -0
- package/dist/SessionStore-BaYiegP0.d.ts +143 -0
- package/dist/client.cjs +2746 -217
- package/dist/client.d.cts +51 -81
- package/dist/client.d.ts +51 -81
- package/dist/client.js +2690 -218
- package/dist/index.cjs +3634 -420
- package/dist/index.d.cts +12 -3
- package/dist/index.d.ts +12 -3
- package/dist/index.js +3569 -419
- package/dist/logger-D8_Q2ytp.d.cts +467 -0
- package/dist/logger-D8_Q2ytp.d.ts +467 -0
- package/dist/provider/fastify.cjs +1099 -0
- package/dist/provider/fastify.d.cts +40 -0
- package/dist/provider/fastify.d.ts +40 -0
- package/dist/provider/fastify.js +1097 -0
- package/dist/provider/hono.cjs +1152 -0
- package/dist/provider/hono.d.cts +101 -0
- package/dist/provider/hono.d.ts +101 -0
- package/dist/provider/hono.js +1142 -0
- package/dist/provider.cjs +1105 -147
- package/dist/provider.d.cts +38 -27
- package/dist/provider.d.ts +38 -27
- package/dist/provider.js +1079 -148
- package/dist/react.cjs +2931 -0
- package/dist/react.d.cts +107 -0
- package/dist/react.d.ts +107 -0
- package/dist/react.js +2924 -0
- package/dist/routedockMiddleware-57gUaEMf.d.ts +64 -0
- package/dist/routedockMiddleware-DlyLrxp7.d.cts +64 -0
- package/dist/schema.cjs +404 -0
- package/dist/schema.d.cts +3 -0
- package/dist/schema.d.ts +3 -0
- package/dist/schema.js +399 -0
- package/dist/sign-AKH67sft.d.cts +53 -0
- package/dist/sign-BEq-OsSq.d.ts +53 -0
- package/dist/testing.cjs +59 -0
- package/dist/testing.d.cts +114 -0
- package/dist/testing.d.ts +114 -0
- package/dist/testing.js +56 -0
- package/package.json +143 -23
- package/PUBLISH_CHECKLIST.md +0 -70
- package/dist/SessionStore-CCVO0PHM.d.cts +0 -152
- package/dist/SessionStore-CCVO0PHM.d.ts +0 -152
- package/dist/routedockMiddleware-B7WTB3LT.d.cts +0 -53
- package/dist/routedockMiddleware-DRgdrycG.d.ts +0 -53
- package/src/client/ModeRouter.ts +0 -104
- package/src/client/MppChargeClient.ts +0 -44
- package/src/client/MppSessionClient.ts +0 -144
- package/src/client/RouteDockClient.ts +0 -124
- package/src/client/index.ts +0 -8
- package/src/client/x402Client.ts +0 -84
- package/src/index.ts +0 -9
- package/src/provider/MppChargeHandler.ts +0 -90
- package/src/provider/MppSessionHandler.ts +0 -157
- package/src/provider/index.ts +0 -7
- package/src/provider/routedockMiddleware.ts +0 -154
- package/src/provider/x402Handler.ts +0 -164
- package/src/schemas/routedock.schema.json +0 -159
- package/src/store/SessionStore.ts +0 -90
- package/src/tests/smoke.test.ts +0 -228
- package/src/types.ts +0 -165
- package/tsconfig.json +0 -14
- package/tsup.config.ts +0 -15
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* TypeScript types for the RouteDock manifest format.
|
|
5
|
-
* Derived from packages/sdk/src/schemas/routedock.schema.json (draft-07).
|
|
6
|
-
*
|
|
7
|
-
* Section 5 of ROUTEDOCK_MASTER.md is the canonical specification.
|
|
8
|
-
*/
|
|
9
|
-
type PaymentMode = 'x402' | 'mpp-charge' | 'mpp-session';
|
|
10
|
-
/** Per-request pricing config — used by x402 and mpp-charge modes */
|
|
11
|
-
interface PricingConfig {
|
|
12
|
-
/** Cost per request in the payment asset, e.g. "0.001" */
|
|
13
|
-
amount: string;
|
|
14
|
-
per: 'request';
|
|
15
|
-
/** x402 facilitator URL (OpenZeppelin Channels). Required for x402 mode. */
|
|
16
|
-
facilitator?: string;
|
|
17
|
-
}
|
|
18
|
-
/** Per-voucher pricing config — used by mpp-session (one-way-channel) mode */
|
|
19
|
-
interface SessionPricingConfig {
|
|
20
|
-
/** Cost per voucher, e.g. "0.0001" */
|
|
21
|
-
rate: string;
|
|
22
|
-
per: 'voucher';
|
|
23
|
-
/** Soroban contract address (C...) for the one-way-channel contract */
|
|
24
|
-
channel_contract: string;
|
|
25
|
-
/** Minimum initial deposit to open a channel, e.g. "0.10" */
|
|
26
|
-
min_deposit: string;
|
|
27
|
-
/**
|
|
28
|
-
* Ledgers the server has to settle before the funder can reclaim.
|
|
29
|
-
* Default: 17280 (~24h). Do NOT reduce in production — this is a
|
|
30
|
-
* security parameter the server depends on to settle first.
|
|
31
|
-
*/
|
|
32
|
-
refund_waiting_period_ledgers: number;
|
|
33
|
-
}
|
|
34
|
-
/** Full RouteDock discovery manifest — served at /.well-known/routedock.json */
|
|
35
|
-
interface RouteDockManifest {
|
|
36
|
-
/** Schema version — always "1.0" */
|
|
37
|
-
routedock: '1.0';
|
|
38
|
-
/** Human-readable provider name */
|
|
39
|
-
name: string;
|
|
40
|
-
/** Human-readable description of the provider's data or service */
|
|
41
|
-
description: string;
|
|
42
|
-
/** Payment modes supported by this provider */
|
|
43
|
-
modes: PaymentMode[];
|
|
44
|
-
/** Stellar network this provider operates on */
|
|
45
|
-
network: 'testnet' | 'mainnet';
|
|
46
|
-
/** Asset ticker symbol, e.g. "USDC" */
|
|
47
|
-
asset: string;
|
|
48
|
-
/** Stellar Asset Contract (SAC) address for the payment asset */
|
|
49
|
-
asset_contract: string;
|
|
50
|
-
/** Stellar address (G...) that receives payments */
|
|
51
|
-
payee: string;
|
|
52
|
-
/** Pricing configuration per supported payment mode */
|
|
53
|
-
pricing: {
|
|
54
|
-
x402?: PricingConfig;
|
|
55
|
-
'mpp-charge'?: PricingConfig;
|
|
56
|
-
'mpp-session'?: SessionPricingConfig;
|
|
57
|
-
};
|
|
58
|
-
/** Map of endpoint name to HTTP method + path, e.g. { price: "GET /price" } */
|
|
59
|
-
endpoints: Record<string, string>;
|
|
60
|
-
/** Capability tags indexed with trigram search in the provider registry */
|
|
61
|
-
tags: string[];
|
|
62
|
-
}
|
|
63
|
-
/** Result returned by client.pay() for any payment mode */
|
|
64
|
-
interface PaymentResult {
|
|
65
|
-
/** Parsed response body from the provider */
|
|
66
|
-
data: unknown;
|
|
67
|
-
/** On-chain Stellar transaction hash, or null for session voucher calls */
|
|
68
|
-
txHash: string | null;
|
|
69
|
-
/** Payment mode that was used for this call */
|
|
70
|
-
mode: PaymentMode;
|
|
71
|
-
/** Amount paid in the payment asset (decimal string) */
|
|
72
|
-
amount: string;
|
|
73
|
-
/** Unix timestamp (ms) when the payment completed */
|
|
74
|
-
timestamp: number;
|
|
75
|
-
}
|
|
76
|
-
/** Result returned by session.close() */
|
|
77
|
-
interface SessionCloseResult {
|
|
78
|
-
/** On-chain transaction hash for the channel close */
|
|
79
|
-
closeTxHash: string;
|
|
80
|
-
/** Total amount settled across all vouchers (decimal string) */
|
|
81
|
-
totalPaid: string;
|
|
82
|
-
/** Number of vouchers issued during the session */
|
|
83
|
-
vouchersIssued: number;
|
|
84
|
-
}
|
|
85
|
-
/** Handle for a live MPP session returned by client.openSession() */
|
|
86
|
-
interface SessionHandle {
|
|
87
|
-
/** Stellar channel contract address (C...) */
|
|
88
|
-
channelId: string;
|
|
89
|
-
/** Transaction hash from the channel-open on-chain call */
|
|
90
|
-
openTxHash: string;
|
|
91
|
-
/**
|
|
92
|
-
* Async generator of server-sent event data.
|
|
93
|
-
* Each iteration sends a voucher and yields the parsed response.
|
|
94
|
-
* UNAUDITED: uses stellar-experimental/one-way-channel contract.
|
|
95
|
-
*/
|
|
96
|
-
stream(): AsyncIterable<unknown>;
|
|
97
|
-
/** Close the channel on-chain with the highest signed voucher */
|
|
98
|
-
close(): Promise<SessionCloseResult>;
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Durable session state stored in Supabase (sessions table).
|
|
102
|
-
* cumulative_amount is a decimal string to avoid float precision loss.
|
|
103
|
-
* The monotonic invariant is enforced both here and at the DB level via trigger.
|
|
104
|
-
*/
|
|
105
|
-
interface SessionState {
|
|
106
|
-
channel_id: string;
|
|
107
|
-
payee: string;
|
|
108
|
-
payer: string;
|
|
109
|
-
/** Monotonically increasing cumulative amount — stored as string to preserve precision */
|
|
110
|
-
cumulative_amount: string;
|
|
111
|
-
last_signature: string;
|
|
112
|
-
status: 'open' | 'closing' | 'closed';
|
|
113
|
-
opened_at: string;
|
|
114
|
-
updated_at: string;
|
|
115
|
-
settlement_tx_hash: string | null;
|
|
116
|
-
}
|
|
117
|
-
declare class RouteDockError extends Error {
|
|
118
|
-
constructor(message: string);
|
|
119
|
-
}
|
|
120
|
-
/** Manifest fetch failed, schema invalid, or required fields missing */
|
|
121
|
-
declare class RouteDockManifestError extends RouteDockError {
|
|
122
|
-
constructor(message: string);
|
|
123
|
-
}
|
|
124
|
-
/** No payment mode in the manifest is supported by this client */
|
|
125
|
-
declare class RouteDockNoSupportedModeError extends RouteDockError {
|
|
126
|
-
constructor(message: string);
|
|
127
|
-
}
|
|
128
|
-
/** Session invariant violated (e.g. non-monotonic voucher amount) */
|
|
129
|
-
declare class RouteDockSessionError extends RouteDockError {
|
|
130
|
-
constructor(message: string);
|
|
131
|
-
}
|
|
132
|
-
/** Local spend cap or on-chain policy rejected the payment */
|
|
133
|
-
declare class RouteDockPolicyRejectedError extends RouteDockError {
|
|
134
|
-
readonly reason: string;
|
|
135
|
-
constructor(reason: string);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
interface SessionStore {
|
|
139
|
-
get(channelId: string): Promise<SessionState | null>;
|
|
140
|
-
/** Enforce monotonic invariant at application level before writing */
|
|
141
|
-
upsert(channelId: string, state: SessionState): Promise<void>;
|
|
142
|
-
close(channelId: string): Promise<void>;
|
|
143
|
-
}
|
|
144
|
-
declare class SupabaseSessionStore implements SessionStore {
|
|
145
|
-
private readonly supabase;
|
|
146
|
-
constructor(supabase: SupabaseClient);
|
|
147
|
-
get(channelId: string): Promise<SessionState | null>;
|
|
148
|
-
upsert(channelId: string, state: SessionState): Promise<void>;
|
|
149
|
-
close(channelId: string): Promise<void>;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export { type PaymentMode as P, RouteDockError as R, type SessionCloseResult as S, type PaymentResult as a, type PricingConfig as b, type RouteDockManifest as c, RouteDockManifestError as d, RouteDockNoSupportedModeError as e, RouteDockPolicyRejectedError as f, RouteDockSessionError as g, type SessionHandle as h, type SessionPricingConfig as i, type SessionState as j, type SessionStore as k, SupabaseSessionStore as l };
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { RequestHandler } from 'express';
|
|
2
|
-
import { P as PaymentMode, c as RouteDockManifest } from './SessionStore-CCVO0PHM.cjs';
|
|
3
|
-
|
|
4
|
-
interface RouteDockMiddlewareOptions {
|
|
5
|
-
modes: PaymentMode[];
|
|
6
|
-
pricing: {
|
|
7
|
-
x402?: string;
|
|
8
|
-
'mpp-charge'?: string;
|
|
9
|
-
'mpp-session'?: {
|
|
10
|
-
rate: string;
|
|
11
|
-
channelContract: string;
|
|
12
|
-
};
|
|
13
|
-
};
|
|
14
|
-
asset: string;
|
|
15
|
-
/** Stellar Asset Contract address for the payment asset */
|
|
16
|
-
assetContract: string;
|
|
17
|
-
payee: string;
|
|
18
|
-
network: 'testnet' | 'mainnet';
|
|
19
|
-
/** Private key (S...) of the server/payee account */
|
|
20
|
-
payeeSecretKey: string;
|
|
21
|
-
/** Required for x402 mode */
|
|
22
|
-
facilitatorApiKey?: string;
|
|
23
|
-
/** Required for mpp-session mode — ed25519 public key (G...) for verifying commitments */
|
|
24
|
-
commitmentPublicKey?: string;
|
|
25
|
-
/** Full RouteDock manifest — served at /.well-known/routedock.json */
|
|
26
|
-
manifest: RouteDockManifest;
|
|
27
|
-
/** Called after each successful on-chain settlement */
|
|
28
|
-
onSettled?: (txHash: string, amount: string, mode: string) => Promise<void>;
|
|
29
|
-
/** Called when a new mpp-session is opened (first voucher received) */
|
|
30
|
-
onSessionOpen?: (channelId: string) => Promise<void>;
|
|
31
|
-
/** Called after each verified voucher in an mpp-session */
|
|
32
|
-
onVoucher?: (voucherIndex: number, cumulativeAmount: string) => Promise<void>;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Express middleware factory for RouteDock provider endpoints.
|
|
36
|
-
*
|
|
37
|
-
* Automatically serves /.well-known/routedock.json and enforces payment
|
|
38
|
-
* verification for all three modes (x402, mpp-charge, mpp-session).
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* ```ts
|
|
42
|
-
* import { routedock } from '@routedock/sdk/provider'
|
|
43
|
-
*
|
|
44
|
-
* app.use('/price', routedock({
|
|
45
|
-
* modes: ['x402', 'mpp-charge'],
|
|
46
|
-
* pricing: { x402: '0.001', 'mpp-charge': '0.0008' },
|
|
47
|
-
* ...
|
|
48
|
-
* }))
|
|
49
|
-
* ```
|
|
50
|
-
*/
|
|
51
|
-
declare function routedock(opts: RouteDockMiddlewareOptions): RequestHandler;
|
|
52
|
-
|
|
53
|
-
export { type RouteDockMiddlewareOptions as R, routedock as r };
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { RequestHandler } from 'express';
|
|
2
|
-
import { P as PaymentMode, c as RouteDockManifest } from './SessionStore-CCVO0PHM.js';
|
|
3
|
-
|
|
4
|
-
interface RouteDockMiddlewareOptions {
|
|
5
|
-
modes: PaymentMode[];
|
|
6
|
-
pricing: {
|
|
7
|
-
x402?: string;
|
|
8
|
-
'mpp-charge'?: string;
|
|
9
|
-
'mpp-session'?: {
|
|
10
|
-
rate: string;
|
|
11
|
-
channelContract: string;
|
|
12
|
-
};
|
|
13
|
-
};
|
|
14
|
-
asset: string;
|
|
15
|
-
/** Stellar Asset Contract address for the payment asset */
|
|
16
|
-
assetContract: string;
|
|
17
|
-
payee: string;
|
|
18
|
-
network: 'testnet' | 'mainnet';
|
|
19
|
-
/** Private key (S...) of the server/payee account */
|
|
20
|
-
payeeSecretKey: string;
|
|
21
|
-
/** Required for x402 mode */
|
|
22
|
-
facilitatorApiKey?: string;
|
|
23
|
-
/** Required for mpp-session mode — ed25519 public key (G...) for verifying commitments */
|
|
24
|
-
commitmentPublicKey?: string;
|
|
25
|
-
/** Full RouteDock manifest — served at /.well-known/routedock.json */
|
|
26
|
-
manifest: RouteDockManifest;
|
|
27
|
-
/** Called after each successful on-chain settlement */
|
|
28
|
-
onSettled?: (txHash: string, amount: string, mode: string) => Promise<void>;
|
|
29
|
-
/** Called when a new mpp-session is opened (first voucher received) */
|
|
30
|
-
onSessionOpen?: (channelId: string) => Promise<void>;
|
|
31
|
-
/** Called after each verified voucher in an mpp-session */
|
|
32
|
-
onVoucher?: (voucherIndex: number, cumulativeAmount: string) => Promise<void>;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Express middleware factory for RouteDock provider endpoints.
|
|
36
|
-
*
|
|
37
|
-
* Automatically serves /.well-known/routedock.json and enforces payment
|
|
38
|
-
* verification for all three modes (x402, mpp-charge, mpp-session).
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* ```ts
|
|
42
|
-
* import { routedock } from '@routedock/sdk/provider'
|
|
43
|
-
*
|
|
44
|
-
* app.use('/price', routedock({
|
|
45
|
-
* modes: ['x402', 'mpp-charge'],
|
|
46
|
-
* pricing: { x402: '0.001', 'mpp-charge': '0.0008' },
|
|
47
|
-
* ...
|
|
48
|
-
* }))
|
|
49
|
-
* ```
|
|
50
|
-
*/
|
|
51
|
-
declare function routedock(opts: RouteDockMiddlewareOptions): RequestHandler;
|
|
52
|
-
|
|
53
|
-
export { type RouteDockMiddlewareOptions as R, routedock as r };
|
package/src/client/ModeRouter.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import Ajv from 'ajv'
|
|
2
|
-
import type { RouteDockManifest, PaymentMode } from '../types.js'
|
|
3
|
-
import { RouteDockManifestError, RouteDockNoSupportedModeError } from '../types.js'
|
|
4
|
-
import schema from '../schemas/routedock.schema.json' assert { type: 'json' }
|
|
5
|
-
|
|
6
|
-
const ajv = new Ajv()
|
|
7
|
-
const validateManifest = ajv.compile(schema)
|
|
8
|
-
|
|
9
|
-
interface CacheEntry {
|
|
10
|
-
manifest: RouteDockManifest
|
|
11
|
-
fetchedAt: number
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const CACHE_TTL_MS = 60_000
|
|
15
|
-
|
|
16
|
-
/** In-memory manifest cache keyed by base URL. */
|
|
17
|
-
const manifestCache = new Map<string, CacheEntry>()
|
|
18
|
-
|
|
19
|
-
export interface ModeSelectOptions {
|
|
20
|
-
/** Force mpp-session if the provider supports it */
|
|
21
|
-
sustained?: boolean
|
|
22
|
-
session?: boolean
|
|
23
|
-
/**
|
|
24
|
-
* Override mode selection and use this specific mode.
|
|
25
|
-
* Throws RouteDockNoSupportedModeError if the provider does not support it.
|
|
26
|
-
*/
|
|
27
|
-
forceMode?: PaymentMode
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Fetch, validate, and cache a RouteDock manifest from `baseUrl`. */
|
|
31
|
-
export async function fetchManifest(baseUrl: string): Promise<RouteDockManifest> {
|
|
32
|
-
const cached = manifestCache.get(baseUrl)
|
|
33
|
-
if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
|
|
34
|
-
return cached.manifest
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const url = baseUrl.replace(/\/$/, '') + '/.well-known/routedock.json'
|
|
38
|
-
let raw: unknown
|
|
39
|
-
try {
|
|
40
|
-
const resp = await fetch(url)
|
|
41
|
-
if (!resp.ok) {
|
|
42
|
-
throw new RouteDockManifestError(`Manifest fetch failed: HTTP ${resp.status} from ${url}`)
|
|
43
|
-
}
|
|
44
|
-
raw = await resp.json()
|
|
45
|
-
} catch (err) {
|
|
46
|
-
if (err instanceof RouteDockManifestError) throw err
|
|
47
|
-
throw new RouteDockManifestError(`Manifest fetch error: ${String(err)}`)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (!validateManifest(raw)) {
|
|
51
|
-
const msgs = ajv.errorsText(validateManifest.errors)
|
|
52
|
-
throw new RouteDockManifestError(`Invalid manifest at ${url}: ${msgs}`)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const manifest = raw as unknown as RouteDockManifest
|
|
56
|
-
manifestCache.set(baseUrl, { manifest, fetchedAt: Date.now() })
|
|
57
|
-
return manifest
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Deterministic mode selection per Section 6.3 of ROUTEDOCK_MASTER.md:
|
|
62
|
-
* 1. If { sustained | session } AND manifest supports mpp-session → mpp-session
|
|
63
|
-
* 2. Else if manifest supports mpp-charge AND network is Stellar → mpp-charge
|
|
64
|
-
* 3. Else if manifest supports x402 → x402
|
|
65
|
-
* 4. Else throw RouteDockNoSupportedModeError
|
|
66
|
-
*/
|
|
67
|
-
export function selectMode(
|
|
68
|
-
manifest: RouteDockManifest,
|
|
69
|
-
options: ModeSelectOptions = {},
|
|
70
|
-
): PaymentMode {
|
|
71
|
-
const modes = manifest.modes
|
|
72
|
-
|
|
73
|
-
if (options.forceMode) {
|
|
74
|
-
if (!modes.includes(options.forceMode)) {
|
|
75
|
-
throw new RouteDockNoSupportedModeError(
|
|
76
|
-
`Provider does not support forced mode: ${options.forceMode} (available: ${modes.join(', ')})`,
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
console.log(`[RouteDock] ${manifest.name} → ${options.forceMode} (forced)`)
|
|
80
|
-
return options.forceMode
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if ((options.sustained || options.session) && modes.includes('mpp-session')) {
|
|
84
|
-
const mode: PaymentMode = 'mpp-session'
|
|
85
|
-
console.log(`[RouteDock] ${manifest.name} → ${mode}`)
|
|
86
|
-
return mode
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (modes.includes('mpp-charge')) {
|
|
90
|
-
const mode: PaymentMode = 'mpp-charge'
|
|
91
|
-
console.log(`[RouteDock] ${manifest.name} → ${mode}`)
|
|
92
|
-
return mode
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (modes.includes('x402')) {
|
|
96
|
-
const mode: PaymentMode = 'x402'
|
|
97
|
-
console.log(`[RouteDock] ${manifest.name} → ${mode}`)
|
|
98
|
-
return mode
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
throw new RouteDockNoSupportedModeError(
|
|
102
|
-
`No supported payment mode found in manifest (modes: ${modes.join(', ')})`,
|
|
103
|
-
)
|
|
104
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { Keypair } from '@stellar/stellar-sdk'
|
|
2
|
-
import { stellar } from '@stellar/mpp/charge/client'
|
|
3
|
-
import { Mppx } from 'mppx/client'
|
|
4
|
-
import type { RouteDockManifest, PaymentResult } from '../types.js'
|
|
5
|
-
import { RouteDockManifestError } from '../types.js'
|
|
6
|
-
|
|
7
|
-
export class MppChargeClient {
|
|
8
|
-
constructor(
|
|
9
|
-
private readonly keypair: Keypair,
|
|
10
|
-
private readonly network: 'testnet' | 'mainnet',
|
|
11
|
-
) {}
|
|
12
|
-
|
|
13
|
-
async pay(url: string, manifest: RouteDockManifest): Promise<PaymentResult> {
|
|
14
|
-
const pricing = manifest.pricing['mpp-charge']
|
|
15
|
-
if (!pricing) {
|
|
16
|
-
throw new RouteDockManifestError('manifest.pricing.mpp-charge missing')
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
let txHash: string | null = null
|
|
20
|
-
|
|
21
|
-
const mppx = Mppx.create({
|
|
22
|
-
polyfill: false,
|
|
23
|
-
methods: [
|
|
24
|
-
stellar.charge({
|
|
25
|
-
keypair: this.keypair,
|
|
26
|
-
mode: 'pull',
|
|
27
|
-
onProgress(event) {
|
|
28
|
-
if (event.type === 'paid') {
|
|
29
|
-
txHash = event.hash
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
}),
|
|
33
|
-
],
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
const response = await mppx.fetch(url)
|
|
37
|
-
if (!response.ok) {
|
|
38
|
-
throw new RouteDockManifestError(`MPP charge failed: HTTP ${response.status}`)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const data = await response.json()
|
|
42
|
-
return { data, txHash, mode: 'mpp-charge', amount: pricing.amount, timestamp: Date.now() }
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MppSessionClient — uses @stellar/mpp channel/client for off-chain
|
|
3
|
-
* cumulative commitments against a pre-deployed one-way-channel contract.
|
|
4
|
-
*
|
|
5
|
-
* The channel is deployed and funded before the agent runs. The client
|
|
6
|
-
* signs ed25519 commitments per the one-way-channel protocol — the
|
|
7
|
-
* @stellar/mpp library handles the 402 challenge-response cycle.
|
|
8
|
-
*/
|
|
9
|
-
import { Keypair } from '@stellar/stellar-sdk'
|
|
10
|
-
import { stellar } from '@stellar/mpp/channel/client'
|
|
11
|
-
import { Mppx } from 'mppx/client'
|
|
12
|
-
import type {
|
|
13
|
-
RouteDockManifest,
|
|
14
|
-
SessionHandle,
|
|
15
|
-
SessionCloseResult,
|
|
16
|
-
} from '../types.js'
|
|
17
|
-
import {
|
|
18
|
-
RouteDockManifestError,
|
|
19
|
-
RouteDockSessionError,
|
|
20
|
-
} from '../types.js'
|
|
21
|
-
|
|
22
|
-
const MIN_REFUND_WAITING_PERIOD = 17_280
|
|
23
|
-
|
|
24
|
-
export class MppSessionClient {
|
|
25
|
-
constructor(
|
|
26
|
-
private readonly keypair: Keypair,
|
|
27
|
-
private readonly network: 'testnet' | 'mainnet',
|
|
28
|
-
) {}
|
|
29
|
-
|
|
30
|
-
async openSession(
|
|
31
|
-
url: string,
|
|
32
|
-
manifest: RouteDockManifest,
|
|
33
|
-
commitmentSecret: string,
|
|
34
|
-
): Promise<SessionHandle> {
|
|
35
|
-
const pricing = manifest.pricing['mpp-session']
|
|
36
|
-
if (!pricing) {
|
|
37
|
-
throw new RouteDockManifestError('manifest.pricing.mpp-session missing')
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const refundPeriod = pricing.refund_waiting_period_ledgers
|
|
41
|
-
if (refundPeriod < MIN_REFUND_WAITING_PERIOD) {
|
|
42
|
-
throw new RouteDockManifestError(
|
|
43
|
-
`refund_waiting_period_ledgers ${refundPeriod} < minimum ${MIN_REFUND_WAITING_PERIOD}`,
|
|
44
|
-
)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const commitmentKey = Keypair.fromSecret(commitmentSecret)
|
|
48
|
-
const channelContract = pricing.channel_contract
|
|
49
|
-
const agentPublicKey = this.keypair.publicKey()
|
|
50
|
-
|
|
51
|
-
let currentCumulative = 0n
|
|
52
|
-
let vouchersIssued = 0
|
|
53
|
-
|
|
54
|
-
const mppx = Mppx.create({
|
|
55
|
-
polyfill: false,
|
|
56
|
-
methods: [
|
|
57
|
-
stellar.channel({
|
|
58
|
-
commitmentKey,
|
|
59
|
-
sourceAccount: this.keypair.publicKey(),
|
|
60
|
-
onProgress(event) {
|
|
61
|
-
if (event.type === 'signed') {
|
|
62
|
-
currentCumulative = BigInt(event.cumulativeAmount)
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
}),
|
|
66
|
-
],
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
const openTxHash = channelContract
|
|
70
|
-
|
|
71
|
-
const handle: SessionHandle = {
|
|
72
|
-
channelId: channelContract,
|
|
73
|
-
openTxHash,
|
|
74
|
-
|
|
75
|
-
async *stream(): AsyncIterable<unknown> {
|
|
76
|
-
while (true) {
|
|
77
|
-
let resp: globalThis.Response | null = null
|
|
78
|
-
for (let attempt = 0; attempt < 3; attempt++) {
|
|
79
|
-
try {
|
|
80
|
-
resp = await mppx.fetch(url)
|
|
81
|
-
if (resp.ok) break
|
|
82
|
-
resp = null
|
|
83
|
-
} catch {
|
|
84
|
-
if (attempt === 2) throw new RouteDockSessionError('Voucher request failed after 3 retries')
|
|
85
|
-
await new Promise(r => setTimeout(r, 1000 * (attempt + 1)))
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if (!resp || !resp.ok) {
|
|
89
|
-
throw new RouteDockSessionError(`Voucher request failed: HTTP ${resp?.status ?? 'unknown'}`)
|
|
90
|
-
}
|
|
91
|
-
vouchersIssued++
|
|
92
|
-
yield await resp.json()
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
|
|
96
|
-
async close(): Promise<SessionCloseResult> {
|
|
97
|
-
const { rpc: rpcMod, Contract, nativeToScVal, TransactionBuilder, BASE_FEE } = await import('@stellar/stellar-sdk')
|
|
98
|
-
const rpcUrl = 'https://soroban-testnet.stellar.org'
|
|
99
|
-
const server = new rpcMod.Server(rpcUrl)
|
|
100
|
-
const contract = new Contract(channelContract)
|
|
101
|
-
const passphrase = 'Test SDF Network ; September 2015'
|
|
102
|
-
|
|
103
|
-
const account = await server.getAccount(agentPublicKey)
|
|
104
|
-
const simTx = new TransactionBuilder(account, { fee: BASE_FEE, networkPassphrase: passphrase })
|
|
105
|
-
.addOperation(contract.call('prepare_commitment', nativeToScVal(currentCumulative, { type: 'i128' })))
|
|
106
|
-
.setTimeout(30)
|
|
107
|
-
.build()
|
|
108
|
-
const simResult = await server.simulateTransaction(simTx)
|
|
109
|
-
if (rpcMod.Api.isSimulationError(simResult)) {
|
|
110
|
-
throw new RouteDockSessionError(`prepare_commitment simulation failed: ${simResult.error}`)
|
|
111
|
-
}
|
|
112
|
-
const commitmentBytes = (simResult as { result?: { retval?: { bytes: () => Buffer } } }).result?.retval?.bytes()
|
|
113
|
-
if (!commitmentBytes) throw new RouteDockSessionError('prepare_commitment returned no bytes')
|
|
114
|
-
|
|
115
|
-
const signature = commitmentKey.sign(Buffer.from(commitmentBytes))
|
|
116
|
-
|
|
117
|
-
const closeResp = await fetch(url, {
|
|
118
|
-
method: 'DELETE',
|
|
119
|
-
headers: { 'Content-Type': 'application/json' },
|
|
120
|
-
body: JSON.stringify({
|
|
121
|
-
amount: currentCumulative.toString(),
|
|
122
|
-
signature: Buffer.from(signature).toString('hex'),
|
|
123
|
-
}),
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
if (!closeResp.ok) {
|
|
127
|
-
throw new RouteDockSessionError(`Channel close failed: HTTP ${closeResp.status}`)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const closeData = (await closeResp.json()) as { closeTxHash?: string }
|
|
131
|
-
const closeTxHash = closeData.closeTxHash ?? null
|
|
132
|
-
|
|
133
|
-
if (!closeTxHash) {
|
|
134
|
-
throw new RouteDockSessionError('Channel close response missing closeTxHash')
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const totalPaid = (Number(currentCumulative) / 1e7).toFixed(7)
|
|
138
|
-
return { closeTxHash, totalPaid, vouchersIssued }
|
|
139
|
-
},
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return handle
|
|
143
|
-
}
|
|
144
|
-
}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { Keypair } from '@stellar/stellar-sdk'
|
|
2
|
-
import { fetchManifest, selectMode, type ModeSelectOptions } from './ModeRouter.js'
|
|
3
|
-
import { X402Client } from './x402Client.js'
|
|
4
|
-
import { MppChargeClient } from './MppChargeClient.js'
|
|
5
|
-
import { MppSessionClient } from './MppSessionClient.js'
|
|
6
|
-
import type { PaymentResult, SessionHandle } from '../types.js'
|
|
7
|
-
import {
|
|
8
|
-
RouteDockManifestError,
|
|
9
|
-
RouteDockPolicyRejectedError,
|
|
10
|
-
} from '../types.js'
|
|
11
|
-
|
|
12
|
-
export interface SpendCap {
|
|
13
|
-
/** Maximum total USDC spend per day (decimal string, e.g. "1.00") */
|
|
14
|
-
daily: string
|
|
15
|
-
asset: 'USDC'
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface RouteDockClientConfig {
|
|
19
|
-
/** Stellar keypair or raw secret key (S...) */
|
|
20
|
-
wallet: Keypair | string
|
|
21
|
-
network: 'testnet' | 'mainnet'
|
|
22
|
-
/** Optional local daily spend cap — checked before every payment */
|
|
23
|
-
spendCap?: SpendCap
|
|
24
|
-
/** Ed25519 secret key (S...) for signing channel commitments. Required for mpp-session. */
|
|
25
|
-
commitmentSecret?: string | undefined
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export class RouteDockClient {
|
|
29
|
-
private readonly keypair: Keypair
|
|
30
|
-
private readonly network: 'testnet' | 'mainnet'
|
|
31
|
-
private readonly spendCap: SpendCap | undefined
|
|
32
|
-
private readonly commitmentSecret: string | undefined
|
|
33
|
-
|
|
34
|
-
/** Local daily accumulator keyed by YYYY-MM-DD */
|
|
35
|
-
private dailySpend: { date: string; total: number } = { date: '', total: 0 }
|
|
36
|
-
|
|
37
|
-
private readonly x402: X402Client
|
|
38
|
-
private readonly charge: MppChargeClient
|
|
39
|
-
private readonly session: MppSessionClient
|
|
40
|
-
|
|
41
|
-
constructor(config: RouteDockClientConfig) {
|
|
42
|
-
this.keypair =
|
|
43
|
-
typeof config.wallet === 'string' ? Keypair.fromSecret(config.wallet) : config.wallet
|
|
44
|
-
this.network = config.network
|
|
45
|
-
this.spendCap = config.spendCap
|
|
46
|
-
this.commitmentSecret = config.commitmentSecret
|
|
47
|
-
|
|
48
|
-
const secretKey = this.keypair.secret()
|
|
49
|
-
this.x402 = new X402Client(secretKey, this.network)
|
|
50
|
-
this.charge = new MppChargeClient(this.keypair, this.network)
|
|
51
|
-
this.session = new MppSessionClient(this.keypair, this.network)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Pay for one request at `url`. Fetches manifest, selects payment mode,
|
|
56
|
-
* checks local spend cap, executes payment, returns result.
|
|
57
|
-
*/
|
|
58
|
-
async pay(url: string, options?: ModeSelectOptions): Promise<PaymentResult> {
|
|
59
|
-
const baseUrl = new URL(url).origin
|
|
60
|
-
const manifest = await fetchManifest(baseUrl)
|
|
61
|
-
const mode = selectMode(manifest, options)
|
|
62
|
-
|
|
63
|
-
let result: PaymentResult
|
|
64
|
-
|
|
65
|
-
switch (mode) {
|
|
66
|
-
case 'x402':
|
|
67
|
-
result = await this.x402.pay(url, manifest)
|
|
68
|
-
break
|
|
69
|
-
case 'mpp-charge':
|
|
70
|
-
result = await this.charge.pay(url, manifest)
|
|
71
|
-
break
|
|
72
|
-
case 'mpp-session':
|
|
73
|
-
throw new RouteDockManifestError(
|
|
74
|
-
'Use client.openSession() for mpp-session mode — client.pay() only handles discrete payments',
|
|
75
|
-
)
|
|
76
|
-
default:
|
|
77
|
-
throw new RouteDockManifestError(`Unknown payment mode: ${mode as string}`)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
this._checkAndRecordSpend(result.amount)
|
|
81
|
-
return result
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Open a sustained MPP session at `url`. Verifies mpp-session is supported
|
|
86
|
-
* before opening a channel. Returns a SessionHandle for streaming + closing.
|
|
87
|
-
*/
|
|
88
|
-
async openSession(url: string): Promise<SessionHandle> {
|
|
89
|
-
const baseUrl = new URL(url).origin
|
|
90
|
-
const manifest = await fetchManifest(baseUrl)
|
|
91
|
-
|
|
92
|
-
if (!manifest.modes.includes('mpp-session')) {
|
|
93
|
-
throw new RouteDockManifestError(
|
|
94
|
-
`Provider at ${baseUrl} does not support mpp-session mode`,
|
|
95
|
-
)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (!this.commitmentSecret) {
|
|
99
|
-
throw new RouteDockManifestError(
|
|
100
|
-
'commitmentSecret is required in RouteDockClientConfig for mpp-session mode',
|
|
101
|
-
)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return this.session.openSession(url, manifest, this.commitmentSecret)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Check local daily cap and record the spend. Throws if cap exceeded. */
|
|
108
|
-
private _checkAndRecordSpend(amount: string): void {
|
|
109
|
-
if (!this.spendCap) return
|
|
110
|
-
|
|
111
|
-
const today = new Date().toISOString().slice(0, 10)
|
|
112
|
-
if (this.dailySpend.date !== today) {
|
|
113
|
-
this.dailySpend = { date: today, total: 0 }
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const amountNum = parseFloat(amount)
|
|
117
|
-
const capNum = parseFloat(this.spendCap.daily)
|
|
118
|
-
if (this.dailySpend.total + amountNum > capNum) {
|
|
119
|
-
throw new RouteDockPolicyRejectedError('local_daily_cap_exceeded')
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
this.dailySpend.total += amountNum
|
|
123
|
-
}
|
|
124
|
-
}
|
package/src/client/index.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
// Client-only exports (no provider/Express)
|
|
2
|
-
export * from '../types.js'
|
|
3
|
-
export * from './RouteDockClient.js'
|
|
4
|
-
export * from './ModeRouter.js'
|
|
5
|
-
export * from './x402Client.js'
|
|
6
|
-
export * from './MppChargeClient.js'
|
|
7
|
-
export * from './MppSessionClient.js'
|
|
8
|
-
export * from '../store/SessionStore.js'
|