@absol-labs/agent 0.12.0-rc.1 → 0.12.0-rc.2
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 +1 -1
- package/dist/cli/commands/claim.d.ts +32 -0
- package/dist/cli/commands/claim.d.ts.map +1 -1
- package/dist/cli/commands/claim.js +115 -27
- package/dist/cli/commands/claim.js.map +1 -1
- package/dist/cli/commands/pay.d.ts +15 -0
- package/dist/cli/commands/pay.d.ts.map +1 -1
- package/dist/cli/commands/pay.js +131 -7
- package/dist/cli/commands/pay.js.map +1 -1
- package/dist/cli/commands/reclaim.d.ts.map +1 -1
- package/dist/cli/commands/reclaim.js +20 -4
- package/dist/cli/commands/reclaim.js.map +1 -1
- package/dist/cli/commands/refund.d.ts +19 -1
- package/dist/cli/commands/refund.d.ts.map +1 -1
- package/dist/cli/commands/refund.js +200 -27
- package/dist/cli/commands/refund.js.map +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +11 -1
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/watch.d.ts.map +1 -1
- package/dist/cli/commands/watch.js +2 -0
- package/dist/cli/commands/watch.js.map +1 -1
- package/dist/cli/context.d.ts +19 -0
- package/dist/cli/context.d.ts.map +1 -1
- package/dist/cli/deps.d.ts +20 -0
- package/dist/cli/deps.d.ts.map +1 -1
- package/dist/cli/deps.js +14 -0
- package/dist/cli/deps.js.map +1 -1
- package/dist/cli/progress.d.ts +13 -1
- package/dist/cli/progress.d.ts.map +1 -1
- package/dist/cli/progress.js +7 -1
- package/dist/cli/progress.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +26 -14
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/status.d.ts +103 -0
- package/dist/cli/status.d.ts.map +1 -1
- package/dist/cli/status.js +310 -41
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/wallet.d.ts +1 -1
- package/dist/cli/wallet.d.ts.map +1 -1
- package/dist/cli/wallet.js +28 -4
- package/dist/cli/wallet.js.map +1 -1
- package/dist/gateway/http-server.d.ts +17 -0
- package/dist/gateway/http-server.d.ts.map +1 -1
- package/dist/gateway/http-server.js +76 -9
- package/dist/gateway/http-server.js.map +1 -1
- package/dist/gateway/x402-opener-mode.d.ts +39 -2
- package/dist/gateway/x402-opener-mode.d.ts.map +1 -1
- package/dist/gateway/x402-opener-mode.js +169 -36
- package/dist/gateway/x402-opener-mode.js.map +1 -1
- package/dist/mcp/server.d.ts +12 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +28 -9
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/x402-tools.d.ts +8 -3
- package/dist/mcp/x402-tools.d.ts.map +1 -1
- package/dist/mcp/x402-tools.js +126 -6
- package/dist/mcp/x402-tools.js.map +1 -1
- package/dist/sdk/client.d.ts +13 -0
- package/dist/sdk/client.d.ts.map +1 -1
- package/dist/sdk/client.js +15 -0
- package/dist/sdk/client.js.map +1 -1
- package/dist/x402-buyer/pay.d.ts +22 -0
- package/dist/x402-buyer/pay.d.ts.map +1 -1
- package/dist/x402-buyer/pay.js +33 -3
- package/dist/x402-buyer/pay.js.map +1 -1
- package/dist/x402-opener/facilitator-entry.js +4 -1
- package/dist/x402-opener/facilitator-entry.js.map +1 -1
- package/dist/x402-opener/facilitator-server.d.ts +29 -0
- package/dist/x402-opener/facilitator-server.d.ts.map +1 -1
- package/dist/x402-opener/facilitator-server.js +67 -1
- package/dist/x402-opener/facilitator-server.js.map +1 -1
- package/dist/x402-opener/refund-relay.d.ts +30 -4
- package/dist/x402-opener/refund-relay.d.ts.map +1 -1
- package/dist/x402-opener/refund-relay.js +123 -22
- package/dist/x402-opener/refund-relay.js.map +1 -1
- package/dist/x402-opener/scheme.d.ts +79 -0
- package/dist/x402-opener/scheme.d.ts.map +1 -1
- package/dist/x402-opener/scheme.js +326 -40
- package/dist/x402-opener/scheme.js.map +1 -1
- package/dist/x402-opener/terms.d.ts +61 -1
- package/dist/x402-opener/terms.d.ts.map +1 -1
- package/dist/x402-opener/terms.js +94 -2
- package/dist/x402-opener/terms.js.map +1 -1
- package/docs/cli.md +87 -9
- package/docs/mcp-reference.md +18 -2
- package/docs/quickstart.md +3 -3
- package/docs/x402.md +150 -10
- package/package.json +3 -3
- package/src/cli/commands/claim.ts +167 -32
- package/src/cli/commands/pay.ts +150 -8
- package/src/cli/commands/reclaim.ts +25 -5
- package/src/cli/commands/refund.ts +231 -33
- package/src/cli/commands/status.ts +11 -1
- package/src/cli/commands/watch.ts +4 -0
- package/src/cli/context.ts +16 -0
- package/src/cli/deps.ts +37 -0
- package/src/cli/progress.ts +21 -2
- package/src/cli/run.ts +26 -13
- package/src/cli/status.ts +440 -43
- package/src/cli/wallet.ts +44 -7
- package/src/gateway/http-server.ts +96 -8
- package/src/gateway/x402-opener-mode.ts +234 -37
- package/src/mcp/server.ts +43 -11
- package/src/mcp/x402-tools.ts +171 -7
- package/src/sdk/client.ts +25 -0
- package/src/x402-buyer/pay.ts +41 -3
- package/src/x402-opener/facilitator-entry.ts +4 -1
- package/src/x402-opener/facilitator-server.ts +101 -1
- package/src/x402-opener/refund-relay.ts +150 -20
- package/src/x402-opener/scheme.ts +459 -41
- package/src/x402-opener/terms.ts +155 -2
|
@@ -20,12 +20,13 @@ import {
|
|
|
20
20
|
DEFAULT_ORACLE_SIGNER_CACHE_SECONDS,
|
|
21
21
|
} from "./probe-auth.js";
|
|
22
22
|
import { PROBE_MARKER_HEADER_NAME } from "../capability/probe-capability.js";
|
|
23
|
-
import { networkForCaip2 } from "@absol-labs/sdk";
|
|
23
|
+
import { networkForCaip2, networkOpenerFactories } from "@absol-labs/sdk";
|
|
24
24
|
import {
|
|
25
25
|
chainIdFromNetwork,
|
|
26
26
|
assertSchemaDeclaration,
|
|
27
27
|
assertValidPrice,
|
|
28
28
|
assertValidTerms,
|
|
29
|
+
assertValidUpfrontTerms,
|
|
29
30
|
isHttpLink,
|
|
30
31
|
type OpenerTerms,
|
|
31
32
|
type X402SchemaDeclaration,
|
|
@@ -459,6 +460,13 @@ export interface CallerAuthGatewayX402OpenerEnvConfig {
|
|
|
459
460
|
readonly assetVersion: string;
|
|
460
461
|
readonly terms: OpenerTerms;
|
|
461
462
|
readonly price: bigint;
|
|
463
|
+
/**
|
|
464
|
+
* `METRIK_GATEWAY_X402V2_UPFRONT_AMOUNT`: selects the upfront opener kind
|
|
465
|
+
* (then `factoryAddress` is the upfront factory). Undefined = stream kind.
|
|
466
|
+
*/
|
|
467
|
+
readonly upfrontAmount?: bigint;
|
|
468
|
+
/** The registry's implementation of the upfront factory, when it records one. */
|
|
469
|
+
readonly upfrontImplementation?: Address;
|
|
462
470
|
readonly maxTimeoutSeconds: number;
|
|
463
471
|
/** Per-request facilitator HTTP timeout, ms. */
|
|
464
472
|
readonly facilitatorTimeoutMs: number;
|
|
@@ -643,6 +651,16 @@ export function parseCallerAuthGatewayEnvConfig(
|
|
|
643
651
|
* - `METRIK_GATEWAY_X402V2_MAX_DURATION_SECONDS` (required).
|
|
644
652
|
* - `METRIK_GATEWAY_X402V2_PRICE` (required) — base units per paid request (= deposit),
|
|
645
653
|
* `0 < price <= rate * maxDuration`.
|
|
654
|
+
* - `METRIK_GATEWAY_X402V2_UPFRONT_AMOUNT` — base units; when SET the listing
|
|
655
|
+
* quotes the UPFRONT ("minimum charge") opener instead: `amount = price`,
|
|
656
|
+
* `payTo = upfrontFactory.predict(6 terms)`; `upfrontAmount` is paid to the
|
|
657
|
+
* seller at settlement and is NOT refundable, only `price - upfrontAmount`
|
|
658
|
+
* (`<= rate * maxDuration`) is metered and refundable. `= PRICE` is
|
|
659
|
+
* upfront-only (no stream). Validated with the factory's rules at startup.
|
|
660
|
+
* - `METRIK_GATEWAY_X402V2_UPFRONT_FACTORY_ADDRESS` — the
|
|
661
|
+
* `X402UpfrontStreamOpenerFactory`; default the network registry's (only
|
|
662
|
+
* with `_UPFRONT_AMOUNT`). `_FACTORY_ADDRESS` names the STREAM factory and is
|
|
663
|
+
* refused together with `_UPFRONT_AMOUNT`.
|
|
646
664
|
* - `METRIK_GATEWAY_X402V2_NETWORK` — default `eip155:<METRIK_GATEWAY_CHAIN_ID>`.
|
|
647
665
|
* - `METRIK_GATEWAY_X402V2_ASSET` — default the network's registered USDC.
|
|
648
666
|
* - `METRIK_GATEWAY_X402V2_ASSET_NAME` / `_ASSET_VERSION` — EIP-712 domain, default the
|
|
@@ -699,8 +717,55 @@ export function parseGatewayX402OpenerEnvConfig(
|
|
|
699
717
|
// Defaults come from the registry entry for THIS network only.
|
|
700
718
|
const registered = networkForCaip2(network);
|
|
701
719
|
const factoryRaw = env.METRIK_GATEWAY_X402V2_FACTORY_ADDRESS?.trim();
|
|
720
|
+
const upfrontRaw = env.METRIK_GATEWAY_X402V2_UPFRONT_AMOUNT?.trim();
|
|
721
|
+
const upfrontFactoryRaw =
|
|
722
|
+
env.METRIK_GATEWAY_X402V2_UPFRONT_FACTORY_ADDRESS?.trim();
|
|
723
|
+
const upfrontMode = upfrontRaw !== undefined && upfrontRaw.length > 0;
|
|
724
|
+
if (
|
|
725
|
+
!upfrontMode &&
|
|
726
|
+
upfrontFactoryRaw !== undefined &&
|
|
727
|
+
upfrontFactoryRaw.length > 0
|
|
728
|
+
) {
|
|
729
|
+
throw new Error(
|
|
730
|
+
"METRIK_GATEWAY_X402V2_UPFRONT_FACTORY_ADDRESS needs METRIK_GATEWAY_X402V2_UPFRONT_AMOUNT (it selects the upfront opener)",
|
|
731
|
+
);
|
|
732
|
+
}
|
|
702
733
|
let factoryAddress: Address;
|
|
703
|
-
|
|
734
|
+
let upfrontImplementation: Address | undefined;
|
|
735
|
+
if (upfrontMode) {
|
|
736
|
+
if (factoryRaw !== undefined && factoryRaw.length > 0) {
|
|
737
|
+
throw new Error(
|
|
738
|
+
"METRIK_GATEWAY_X402V2_FACTORY_ADDRESS names the STREAM opener factory; with METRIK_GATEWAY_X402V2_UPFRONT_AMOUNT set the listing quotes the upfront factory — set METRIK_GATEWAY_X402V2_UPFRONT_FACTORY_ADDRESS (or nothing, for the registry's) instead",
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
const registeredUpfront =
|
|
742
|
+
registered === undefined
|
|
743
|
+
? []
|
|
744
|
+
: networkOpenerFactories(registered).filter(
|
|
745
|
+
(entry) => entry.kind === "upfront",
|
|
746
|
+
);
|
|
747
|
+
if (upfrontFactoryRaw === undefined || upfrontFactoryRaw.length === 0) {
|
|
748
|
+
const current = registeredUpfront.find((entry) => !entry.legacy);
|
|
749
|
+
if (current === undefined) {
|
|
750
|
+
throw new Error(
|
|
751
|
+
`METRIK_GATEWAY_X402V2_UPFRONT_FACTORY_ADDRESS is required on ${network}: the network registry has no upfront opener factory for it`,
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
factoryAddress = current.address;
|
|
755
|
+
upfrontImplementation = current.implementation ?? undefined;
|
|
756
|
+
} else if (isAddress(upfrontFactoryRaw)) {
|
|
757
|
+
factoryAddress = upfrontFactoryRaw;
|
|
758
|
+
upfrontImplementation =
|
|
759
|
+
registeredUpfront.find(
|
|
760
|
+
(entry) =>
|
|
761
|
+
entry.address.toLowerCase() === upfrontFactoryRaw.toLowerCase(),
|
|
762
|
+
)?.implementation ?? undefined;
|
|
763
|
+
} else {
|
|
764
|
+
throw new Error(
|
|
765
|
+
"METRIK_GATEWAY_X402V2_UPFRONT_FACTORY_ADDRESS must be an EVM address",
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
} else if (factoryRaw === undefined || factoryRaw.length === 0) {
|
|
704
769
|
if (registered?.openerFactory == null) {
|
|
705
770
|
throw new Error(
|
|
706
771
|
`METRIK_GATEWAY_X402V2_FACTORY_ADDRESS is required on ${network}: the network registry has no opener factory for it`,
|
|
@@ -742,9 +807,18 @@ export function parseGatewayX402OpenerEnvConfig(
|
|
|
742
807
|
),
|
|
743
808
|
};
|
|
744
809
|
const price = requireUintEnv(env, "METRIK_GATEWAY_X402V2_PRICE");
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
810
|
+
let upfrontAmount: bigint | undefined;
|
|
811
|
+
if (upfrontMode) {
|
|
812
|
+
upfrontAmount = requireUintEnv(env, "METRIK_GATEWAY_X402V2_UPFRONT_AMOUNT");
|
|
813
|
+
// Startup validation, fail closed: the upfront factory's own validateTerms
|
|
814
|
+
// rules, 0 < price, upfrontAmount <= price, and the metered part
|
|
815
|
+
// (price - upfrontAmount) <= rate * maxDuration.
|
|
816
|
+
assertValidUpfrontTerms({ ...terms, price, upfrontAmount });
|
|
817
|
+
} else {
|
|
818
|
+
// Startup validation: rate/duration > 0 and 0 < price <= rate * maxDuration.
|
|
819
|
+
assertValidTerms(terms);
|
|
820
|
+
assertValidPrice(price, terms);
|
|
821
|
+
}
|
|
748
822
|
const maxTimeoutSeconds = requirePositiveIntEnv(
|
|
749
823
|
env,
|
|
750
824
|
"METRIK_GATEWAY_X402V2_MAX_TIMEOUT_SECONDS",
|
|
@@ -828,6 +902,8 @@ export function parseGatewayX402OpenerEnvConfig(
|
|
|
828
902
|
),
|
|
829
903
|
terms,
|
|
830
904
|
price,
|
|
905
|
+
...(upfrontAmount === undefined ? {} : { upfrontAmount }),
|
|
906
|
+
...(upfrontImplementation === undefined ? {} : { upfrontImplementation }),
|
|
831
907
|
maxTimeoutSeconds,
|
|
832
908
|
facilitatorTimeoutMs: facilitatorTimeoutSeconds * 1000,
|
|
833
909
|
...(resourceBaseUrl === undefined || resourceBaseUrl.length === 0
|
|
@@ -1032,6 +1108,12 @@ export async function startCallerAuthGatewayServerFromEnv(
|
|
|
1032
1108
|
assetVersion: config.x402Opener.assetVersion,
|
|
1033
1109
|
terms: config.x402Opener.terms,
|
|
1034
1110
|
price: config.x402Opener.price,
|
|
1111
|
+
...(config.x402Opener.upfrontAmount === undefined
|
|
1112
|
+
? {}
|
|
1113
|
+
: { upfrontAmount: config.x402Opener.upfrontAmount }),
|
|
1114
|
+
...(config.x402Opener.upfrontImplementation === undefined
|
|
1115
|
+
? {}
|
|
1116
|
+
: { upfrontImplementation: config.x402Opener.upfrontImplementation }),
|
|
1035
1117
|
maxTimeoutSeconds: config.x402Opener.maxTimeoutSeconds,
|
|
1036
1118
|
facilitatorTimeoutMs: config.x402Opener.facilitatorTimeoutMs,
|
|
1037
1119
|
...(config.x402Opener.resourceBaseUrl === undefined
|
|
@@ -1058,12 +1140,18 @@ export async function startCallerAuthGatewayServerFromEnv(
|
|
|
1058
1140
|
},
|
|
1059
1141
|
{ rpcUrl: config.rpcUrl },
|
|
1060
1142
|
);
|
|
1143
|
+
const upfront = x402Opener.upfrontAmount;
|
|
1061
1144
|
console.error(
|
|
1062
1145
|
"[metrik-caller-auth-gateway] x402 v2 opener mode ENABLED: a request with neither a " +
|
|
1063
1146
|
"capability nor PAYMENT-SIGNATURE gets a 402 quoting payTo " +
|
|
1064
|
-
`${x402Opener.payTo} (the
|
|
1065
|
-
`${config.x402Opener.price} base units
|
|
1066
|
-
|
|
1147
|
+
`${x402Opener.payTo} (the ${x402Opener.openerKind} opener for serviceRef ${config.serviceRef}), price ` +
|
|
1148
|
+
`${config.x402Opener.price} base units` +
|
|
1149
|
+
(upfront === null
|
|
1150
|
+
? " (all of it escrowed and metered)"
|
|
1151
|
+
: upfront === config.x402Opener.price
|
|
1152
|
+
? " (UPFRONT-ONLY: all of it a non-refundable per-call charge; no stream, nothing to refund)"
|
|
1153
|
+
: ` (${upfront} a non-refundable per-call charge, ${config.x402Opener.price - upfront} escrowed, metered and refundable)`) +
|
|
1154
|
+
`. Payments settle via the Metrik opener facilitator at ${config.x402Opener.facilitatorUrl}, and only after the upstream answers < 400. ` +
|
|
1067
1155
|
`Refunds (no ETH needed, never automatic): ${x402Opener.refundBaseUrl}/streams/:streamId.`,
|
|
1068
1156
|
);
|
|
1069
1157
|
const refundHost = new URL(x402Opener.refundBaseUrl).hostname;
|
|
@@ -26,6 +26,11 @@ import {
|
|
|
26
26
|
type PublicClient,
|
|
27
27
|
} from "viem";
|
|
28
28
|
|
|
29
|
+
import {
|
|
30
|
+
predictUpfrontOpenerAddress,
|
|
31
|
+
x402UpfrontStreamOpenerFactoryAbi,
|
|
32
|
+
} from "@absol-labs/shared";
|
|
33
|
+
|
|
29
34
|
import { x402StreamOpenerFactoryAbi } from "../x402-opener/abi.js";
|
|
30
35
|
import {
|
|
31
36
|
RegistryListingResolver,
|
|
@@ -35,6 +40,7 @@ import { readOpenerFacilitatorMarker } from "../x402-opener/scheme.js";
|
|
|
35
40
|
import {
|
|
36
41
|
assertValidPrice,
|
|
37
42
|
assertValidTerms,
|
|
43
|
+
assertValidUpfrontTerms,
|
|
38
44
|
chainIdFromNetwork,
|
|
39
45
|
assertSchemaDeclaration,
|
|
40
46
|
DEFAULT_OPENER_DOCS_URL,
|
|
@@ -43,7 +49,11 @@ import {
|
|
|
43
49
|
OPENER_SCHEME,
|
|
44
50
|
termsToExtra,
|
|
45
51
|
termsTuple,
|
|
52
|
+
upfrontTermsToExtra,
|
|
53
|
+
upfrontTermsTuple,
|
|
54
|
+
type OpenerKind,
|
|
46
55
|
type OpenerTerms,
|
|
56
|
+
type UpfrontOpenerTerms,
|
|
47
57
|
type X402SchemaDeclaration,
|
|
48
58
|
} from "../x402-opener/terms.js";
|
|
49
59
|
|
|
@@ -90,6 +100,18 @@ import {
|
|
|
90
100
|
* - refuses to start if its price is below the facilitator's advertised
|
|
91
101
|
* minimum, or if its facilitator timeout would not outlast the facilitator's
|
|
92
102
|
* receipt wait.
|
|
103
|
+
*
|
|
104
|
+
* ## Two opener kinds
|
|
105
|
+
*
|
|
106
|
+
* Without `upfrontAmount` the listing quotes the `X402StreamOpenerFactory`
|
|
107
|
+
* (`"stream"`): the whole price funds a metered stream. With `upfrontAmount`
|
|
108
|
+
* it quotes the `X402UpfrontStreamOpenerFactory` (`"upfront"`, the "minimum
|
|
109
|
+
* charge"): `amount == price`, `payTo = predict(6 terms)`, the
|
|
110
|
+
* `upfrontAmount` is paid to the seller at settlement and is NOT refundable,
|
|
111
|
+
* and only `price - upfrontAmount` funds a metered, refundable stream. The 402
|
|
112
|
+
* description states that split. `upfrontAmount == price` is an upfront-only
|
|
113
|
+
* listing: no stream opens (the settlement reports `streamId: null`), the
|
|
114
|
+
* response is served, and no metered session or refund exists for it.
|
|
93
115
|
*/
|
|
94
116
|
|
|
95
117
|
/** How long a successful `/supported` marker check is trusted, seconds. */
|
|
@@ -118,8 +140,24 @@ export interface GatewayX402OpenerModeConfig {
|
|
|
118
140
|
readonly assetVersion: string;
|
|
119
141
|
/** Opener terms. `operator` + `serviceRef` must resolve to one operator-signed listing. */
|
|
120
142
|
readonly terms: OpenerTerms;
|
|
121
|
-
/**
|
|
143
|
+
/**
|
|
144
|
+
* Price per paid request in token base units. For a stream opener it is the
|
|
145
|
+
* whole stream deposit; for an upfront opener the exact x402 amount, split
|
|
146
|
+
* into `upfrontAmount` (non-refundable) and `price - upfrontAmount` (deposit).
|
|
147
|
+
*/
|
|
122
148
|
readonly price: bigint;
|
|
149
|
+
/**
|
|
150
|
+
* Selects the UPFRONT opener kind (`factoryAddress` must then be an
|
|
151
|
+
* `X402UpfrontStreamOpenerFactory`): the non-refundable per-call part, base
|
|
152
|
+
* units, `0 <= upfrontAmount <= price`. Unset = the stream opener kind.
|
|
153
|
+
*/
|
|
154
|
+
readonly upfrontAmount?: bigint;
|
|
155
|
+
/**
|
|
156
|
+
* The implementation the upfront factory must report (e.g. the registry's);
|
|
157
|
+
* checked at startup when given. `payTo` is always recomputed offline from
|
|
158
|
+
* the factory's own `implementation()` and must equal its on-chain `predict`.
|
|
159
|
+
*/
|
|
160
|
+
readonly upfrontImplementation?: Address;
|
|
123
161
|
/** Authorization validity the client signs for. Default 300s. */
|
|
124
162
|
readonly maxTimeoutSeconds?: number;
|
|
125
163
|
/** Public origin clients reach this gateway at, for the 402 `resource.url`. */
|
|
@@ -168,6 +206,11 @@ export interface GatewayX402OpenerModeConfig {
|
|
|
168
206
|
export function describeOpenerPurchase(input: {
|
|
169
207
|
readonly terms: OpenerTerms;
|
|
170
208
|
readonly price: bigint;
|
|
209
|
+
/**
|
|
210
|
+
* Upfront openers only: the non-refundable per-call part. The description
|
|
211
|
+
* then states the split (non-refundable vs metered and refundable).
|
|
212
|
+
*/
|
|
213
|
+
readonly upfrontAmount?: bigint;
|
|
171
214
|
readonly assetDecimals: number;
|
|
172
215
|
readonly assetSymbol: string;
|
|
173
216
|
/** Refund URL template containing `{streamId}`. */
|
|
@@ -178,15 +221,42 @@ export function describeOpenerPurchase(input: {
|
|
|
178
221
|
const amount = (value: bigint) =>
|
|
179
222
|
`${formatUnits(value, input.assetDecimals)} ${input.assetSymbol}`;
|
|
180
223
|
const { ratePerSecond, maxDuration } = input.terms;
|
|
181
|
-
const cap =
|
|
182
|
-
input.price < ratePerSecond * maxDuration
|
|
183
|
-
? ` (the seller can earn at most ${amount(input.price)})`
|
|
184
|
-
: "";
|
|
185
224
|
const prefix =
|
|
186
225
|
input.serviceDescription === undefined ||
|
|
187
226
|
input.serviceDescription.trim().length === 0
|
|
188
227
|
? ""
|
|
189
228
|
: `${input.serviceDescription.trim()} `;
|
|
229
|
+
if (input.upfrontAmount !== undefined) {
|
|
230
|
+
const upfront = input.upfrontAmount;
|
|
231
|
+
const metered = input.price - upfront;
|
|
232
|
+
if (metered === 0n) {
|
|
233
|
+
return (
|
|
234
|
+
`${prefix}Pays for one call: ${amount(input.price)}, charged in full at settlement as a NON-REFUNDABLE per-call charge ` +
|
|
235
|
+
"(paid to the seller whatever is delivered). No escrow stream is opened, so nothing is metered and there is nothing to refund or reclaim. " +
|
|
236
|
+
`Docs: ${input.docsUrl}`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
const meteredCap =
|
|
240
|
+
metered < ratePerSecond * maxDuration
|
|
241
|
+
? ` (the seller can earn at most ${amount(metered)} of it)`
|
|
242
|
+
: "";
|
|
243
|
+
return (
|
|
244
|
+
`${prefix}Pays for one call: ${amount(input.price)} in total. ` +
|
|
245
|
+
(upfront === 0n
|
|
246
|
+
? "Nothing of it is charged upfront. "
|
|
247
|
+
: `${amount(upfront)} is a NON-REFUNDABLE per-call charge, paid to the seller at settlement whatever is delivered. `) +
|
|
248
|
+
`The remaining ${amount(metered)} is metered and refundable: it funds a Metrik escrow stream of ` +
|
|
249
|
+
`${maxDuration.toString()} seconds at ${amount(ratePerSecond)}/s${meteredCap}; ` +
|
|
250
|
+
"the seller earns it only for seconds Metrik's verifier confirms the service is delivering, " +
|
|
251
|
+
`and any unearned part of it (at most ${amount(metered)}) is refundable to you (no ETH needed) via ${input.refundUrlTemplate}. ` +
|
|
252
|
+
"Refunds are not automatic: close the stream and reclaim there, using the streamId from the PAYMENT-RESPONSE. " +
|
|
253
|
+
`Docs: ${input.docsUrl}`
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
const cap =
|
|
257
|
+
input.price < ratePerSecond * maxDuration
|
|
258
|
+
? ` (the seller can earn at most ${amount(input.price)})`
|
|
259
|
+
: "";
|
|
190
260
|
return (
|
|
191
261
|
`${prefix}Pays for one call. Your payment of ${amount(input.price)} funds a Metrik escrow stream of ` +
|
|
192
262
|
`${maxDuration.toString()} seconds at ${amount(ratePerSecond)}/s${cap}; ` +
|
|
@@ -216,9 +286,13 @@ class OpenerExactServerScheme extends ExactEvmScheme {
|
|
|
216
286
|
| Record<string, unknown>
|
|
217
287
|
| undefined;
|
|
218
288
|
const streamId = opened?.streamId;
|
|
289
|
+
// An upfront-only payment reports `streamId: null` (no stream): it gets no
|
|
290
|
+
// refund URL, since there is nothing to refund. bytes32(0) is treated the
|
|
291
|
+
// same, defensively.
|
|
219
292
|
if (
|
|
220
293
|
typeof streamId !== "string" ||
|
|
221
294
|
!/^0x[0-9a-fA-F]{64}$/.test(streamId) ||
|
|
295
|
+
/^0x0{64}$/i.test(streamId) ||
|
|
222
296
|
opened?.refundUrl !== undefined
|
|
223
297
|
) {
|
|
224
298
|
return undefined;
|
|
@@ -249,6 +323,10 @@ export interface GatewayX402OpenerMode {
|
|
|
249
323
|
readonly network: Network;
|
|
250
324
|
readonly terms: OpenerTerms;
|
|
251
325
|
readonly price: bigint;
|
|
326
|
+
/** `"stream"` or `"upfront"` (selected by `upfrontAmount`). */
|
|
327
|
+
readonly openerKind: OpenerKind;
|
|
328
|
+
/** The upfront kind's non-refundable part; `null` for the stream kind. */
|
|
329
|
+
readonly upfrontAmount: bigint | null;
|
|
252
330
|
readonly resourceBaseUrl: string | undefined;
|
|
253
331
|
/** The 402 `description` (what the payment buys). */
|
|
254
332
|
readonly description: string;
|
|
@@ -417,9 +495,25 @@ export async function buildGatewayX402OpenerMode(
|
|
|
417
495
|
config: GatewayX402OpenerModeConfig,
|
|
418
496
|
deps: GatewayX402OpenerModeDeps = {},
|
|
419
497
|
): Promise<GatewayX402OpenerMode> {
|
|
498
|
+
const kind: OpenerKind =
|
|
499
|
+
config.upfrontAmount === undefined ? "stream" : "upfront";
|
|
500
|
+
const upfrontTerms: UpfrontOpenerTerms | undefined =
|
|
501
|
+
config.upfrontAmount === undefined
|
|
502
|
+
? undefined
|
|
503
|
+
: {
|
|
504
|
+
...config.terms,
|
|
505
|
+
price: config.price,
|
|
506
|
+
upfrontAmount: config.upfrontAmount,
|
|
507
|
+
};
|
|
420
508
|
try {
|
|
421
|
-
|
|
422
|
-
|
|
509
|
+
if (upfrontTerms === undefined) {
|
|
510
|
+
assertValidTerms(config.terms);
|
|
511
|
+
assertValidPrice(config.price, config.terms);
|
|
512
|
+
} else {
|
|
513
|
+
// The factory's own validateTerms rules + amount == price + the metered
|
|
514
|
+
// part within rate * maxDuration: exactly what the SDK buyer requires.
|
|
515
|
+
assertValidUpfrontTerms(upfrontTerms);
|
|
516
|
+
}
|
|
423
517
|
} catch (error) {
|
|
424
518
|
throw new X402OpenerModeConfigError(
|
|
425
519
|
error instanceof Error ? error.message : String(error),
|
|
@@ -501,24 +595,86 @@ export async function buildGatewayX402OpenerMode(
|
|
|
501
595
|
const factory = getAddress(config.factoryAddress);
|
|
502
596
|
const escrow = getAddress(config.escrowAddress);
|
|
503
597
|
const asset = getAddress(config.asset);
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
598
|
+
let factoryEscrow: Address;
|
|
599
|
+
let factoryUsdc: Address;
|
|
600
|
+
let predicted: Address;
|
|
601
|
+
if (upfrontTerms === undefined) {
|
|
602
|
+
[factoryEscrow, factoryUsdc, predicted] = await Promise.all([
|
|
603
|
+
publicClient.readContract({
|
|
604
|
+
address: factory,
|
|
605
|
+
abi: x402StreamOpenerFactoryAbi,
|
|
606
|
+
functionName: "escrow",
|
|
607
|
+
}),
|
|
608
|
+
publicClient.readContract({
|
|
609
|
+
address: factory,
|
|
610
|
+
abi: x402StreamOpenerFactoryAbi,
|
|
611
|
+
functionName: "usdc",
|
|
612
|
+
}),
|
|
613
|
+
publicClient.readContract({
|
|
614
|
+
address: factory,
|
|
615
|
+
abi: x402StreamOpenerFactoryAbi,
|
|
616
|
+
functionName: "predict",
|
|
617
|
+
args: [termsTuple(config.terms)],
|
|
618
|
+
}),
|
|
619
|
+
]);
|
|
620
|
+
} else {
|
|
621
|
+
let implementation: Address;
|
|
622
|
+
[factoryEscrow, factoryUsdc, predicted, implementation] = await Promise.all(
|
|
623
|
+
[
|
|
624
|
+
publicClient.readContract({
|
|
625
|
+
address: factory,
|
|
626
|
+
abi: x402UpfrontStreamOpenerFactoryAbi,
|
|
627
|
+
functionName: "escrow",
|
|
628
|
+
}),
|
|
629
|
+
publicClient.readContract({
|
|
630
|
+
address: factory,
|
|
631
|
+
abi: x402UpfrontStreamOpenerFactoryAbi,
|
|
632
|
+
functionName: "usdc",
|
|
633
|
+
}),
|
|
634
|
+
publicClient.readContract({
|
|
635
|
+
address: factory,
|
|
636
|
+
abi: x402UpfrontStreamOpenerFactoryAbi,
|
|
637
|
+
functionName: "predict",
|
|
638
|
+
args: [upfrontTermsTuple(upfrontTerms)],
|
|
639
|
+
}),
|
|
640
|
+
publicClient.readContract({
|
|
641
|
+
address: factory,
|
|
642
|
+
abi: x402UpfrontStreamOpenerFactoryAbi,
|
|
643
|
+
functionName: "implementation",
|
|
644
|
+
}),
|
|
645
|
+
],
|
|
646
|
+
).catch((error: unknown) => {
|
|
647
|
+
throw new X402OpenerModeConfigError(
|
|
648
|
+
`factory ${factory} is not an X402UpfrontStreamOpenerFactory that accepts these upfront terms (${error instanceof Error ? (error.message.split("\n")[0] ?? "error") : String(error)})`,
|
|
649
|
+
);
|
|
650
|
+
});
|
|
651
|
+
if (
|
|
652
|
+
config.upfrontImplementation !== undefined &&
|
|
653
|
+
getAddress(implementation) !== getAddress(config.upfrontImplementation)
|
|
654
|
+
) {
|
|
655
|
+
throw new X402OpenerModeConfigError(
|
|
656
|
+
`upfront factory ${factory} reports implementation ${implementation}, not the expected ${config.upfrontImplementation}`,
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
// The SDK buyer recomputes payTo OFFLINE; quote only a payTo both agree on.
|
|
660
|
+
const offline = predictUpfrontOpenerAddress({
|
|
661
|
+
factory,
|
|
662
|
+
implementation: getAddress(implementation),
|
|
663
|
+
terms: {
|
|
664
|
+
operator: getAddress(upfrontTerms.operator),
|
|
665
|
+
serviceRef: upfrontTerms.serviceRef.toLowerCase() as `0x${string}`,
|
|
666
|
+
ratePerSecond: upfrontTerms.ratePerSecond,
|
|
667
|
+
maxDuration: upfrontTerms.maxDuration,
|
|
668
|
+
price: upfrontTerms.price,
|
|
669
|
+
upfrontAmount: upfrontTerms.upfrontAmount,
|
|
670
|
+
},
|
|
671
|
+
});
|
|
672
|
+
if (getAddress(offline) !== getAddress(predicted)) {
|
|
673
|
+
throw new X402OpenerModeConfigError(
|
|
674
|
+
`factory.predict(terms) = ${predicted} disagrees with the offline CREATE2 derivation ${offline}; refusing to quote it`,
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
522
678
|
if (getAddress(factoryEscrow) !== escrow) {
|
|
523
679
|
throw new X402OpenerModeConfigError(
|
|
524
680
|
`factory ${factory} settles into ${factoryEscrow}, not this gateway's escrow ${escrow}`,
|
|
@@ -577,22 +733,26 @@ export async function buildGatewayX402OpenerMode(
|
|
|
577
733
|
{ cause },
|
|
578
734
|
);
|
|
579
735
|
}
|
|
580
|
-
const
|
|
736
|
+
const supportedKind = supported.kinds.find(
|
|
581
737
|
(candidate) =>
|
|
582
738
|
candidate.x402Version === 2 &&
|
|
583
739
|
candidate.scheme === OPENER_SCHEME &&
|
|
584
740
|
candidate.network === config.network,
|
|
585
741
|
);
|
|
586
|
-
const marker = readOpenerFacilitatorMarker(
|
|
742
|
+
const marker = readOpenerFacilitatorMarker(supportedKind?.extra);
|
|
587
743
|
if (
|
|
588
744
|
marker === undefined ||
|
|
589
|
-
|
|
590
|
-
|
|
745
|
+
!markerSettles(
|
|
746
|
+
marker,
|
|
747
|
+
upfrontTerms === undefined ? "stream" : "upfront",
|
|
748
|
+
factory,
|
|
749
|
+
escrow,
|
|
750
|
+
) ||
|
|
591
751
|
marker.usdc !== asset ||
|
|
592
752
|
marker.chainId !== chainId
|
|
593
753
|
) {
|
|
594
754
|
throw new X402OpenerFacilitatorUnverifiedError(
|
|
595
|
-
`the x402 facilitator at ${config.facilitatorUrl} does not advertise Metrik opener settlement for factory ${factory} on ${config.network}. ` +
|
|
755
|
+
`the x402 facilitator at ${config.facilitatorUrl} does not advertise Metrik ${upfrontTerms === undefined ? "" : "upfront "}opener settlement for factory ${factory} on ${config.network}. ` +
|
|
596
756
|
"Refusing to quote an opener payTo: a stock exact facilitator would transfer the payment straight to the opener and strand it permanently.",
|
|
597
757
|
);
|
|
598
758
|
}
|
|
@@ -624,6 +784,9 @@ export async function buildGatewayX402OpenerMode(
|
|
|
624
784
|
const description = describeOpenerPurchase({
|
|
625
785
|
terms: config.terms,
|
|
626
786
|
price: config.price,
|
|
787
|
+
...(config.upfrontAmount === undefined
|
|
788
|
+
? {}
|
|
789
|
+
: { upfrontAmount: config.upfrontAmount }),
|
|
627
790
|
assetDecimals,
|
|
628
791
|
assetSymbol: config.assetName,
|
|
629
792
|
refundUrlTemplate: `${refundBaseUrl}/streams/{streamId}`,
|
|
@@ -644,11 +807,21 @@ export async function buildGatewayX402OpenerMode(
|
|
|
644
807
|
name: config.assetName,
|
|
645
808
|
version: config.assetVersion,
|
|
646
809
|
assetTransferMethod: "eip3009",
|
|
647
|
-
[OPENER_EXTRA_KEY]:
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
810
|
+
[OPENER_EXTRA_KEY]:
|
|
811
|
+
upfrontTerms === undefined
|
|
812
|
+
? {
|
|
813
|
+
...termsToExtra(factory, config.terms),
|
|
814
|
+
refundUrl: `${refundBaseUrl}/streams/{streamId}`,
|
|
815
|
+
docsUrl,
|
|
816
|
+
}
|
|
817
|
+
: {
|
|
818
|
+
...upfrontTermsToExtra(factory, upfrontTerms),
|
|
819
|
+
// An upfront-only listing opens no stream: no refund URL.
|
|
820
|
+
...(upfrontTerms.upfrontAmount === upfrontTerms.price
|
|
821
|
+
? {}
|
|
822
|
+
: { refundUrl: `${refundBaseUrl}/streams/{streamId}` }),
|
|
823
|
+
docsUrl,
|
|
824
|
+
},
|
|
652
825
|
},
|
|
653
826
|
},
|
|
654
827
|
maxTimeoutSeconds,
|
|
@@ -692,6 +865,7 @@ export async function buildGatewayX402OpenerMode(
|
|
|
692
865
|
await ready();
|
|
693
866
|
if (refundBaseUrl !== config.facilitatorUrl.replace(/\/+$/, "")) {
|
|
694
867
|
await checkRefundRelayAt(refundBaseUrl, deps.fetch ?? fetch, {
|
|
868
|
+
kind,
|
|
695
869
|
factory,
|
|
696
870
|
escrow,
|
|
697
871
|
asset,
|
|
@@ -705,6 +879,8 @@ export async function buildGatewayX402OpenerMode(
|
|
|
705
879
|
network: config.network,
|
|
706
880
|
terms: config.terms,
|
|
707
881
|
price: config.price,
|
|
882
|
+
openerKind: kind,
|
|
883
|
+
upfrontAmount: config.upfrontAmount ?? null,
|
|
708
884
|
resourceBaseUrl: config.resourceBaseUrl,
|
|
709
885
|
description,
|
|
710
886
|
refundBaseUrl,
|
|
@@ -729,6 +905,7 @@ async function checkRefundRelayAt(
|
|
|
729
905
|
refundBaseUrl: string,
|
|
730
906
|
fetchImpl: typeof fetch,
|
|
731
907
|
expected: {
|
|
908
|
+
readonly kind: OpenerKind;
|
|
732
909
|
readonly factory: Address;
|
|
733
910
|
readonly escrow: Address;
|
|
734
911
|
readonly asset: Address;
|
|
@@ -765,8 +942,7 @@ async function checkRefundRelayAt(
|
|
|
765
942
|
const marker = readOpenerFacilitatorMarker(kind?.extra);
|
|
766
943
|
if (
|
|
767
944
|
marker === undefined ||
|
|
768
|
-
marker.
|
|
769
|
-
marker.escrow !== expected.escrow ||
|
|
945
|
+
!markerSettles(marker, expected.kind, expected.factory, expected.escrow) ||
|
|
770
946
|
marker.usdc !== expected.asset ||
|
|
771
947
|
marker.chainId !== expected.chainId ||
|
|
772
948
|
marker.refundRelay !== true
|
|
@@ -777,6 +953,27 @@ async function checkRefundRelayAt(
|
|
|
777
953
|
}
|
|
778
954
|
}
|
|
779
955
|
|
|
956
|
+
/**
|
|
957
|
+
* Whether a facilitator marker settles THIS listing's opener kind: the stream
|
|
958
|
+
* factory is the marker's `factory`; the upfront factory its `upfrontOpener`.
|
|
959
|
+
*/
|
|
960
|
+
function markerSettles(
|
|
961
|
+
marker: NonNullable<ReturnType<typeof readOpenerFacilitatorMarker>>,
|
|
962
|
+
kind: OpenerKind,
|
|
963
|
+
factory: Address,
|
|
964
|
+
escrow: Address,
|
|
965
|
+
): boolean {
|
|
966
|
+
if (kind === "upfront") {
|
|
967
|
+
return (
|
|
968
|
+
marker.upfrontOpener !== undefined &&
|
|
969
|
+
marker.upfrontOpener.factory === factory &&
|
|
970
|
+
marker.upfrontOpener.escrow === escrow &&
|
|
971
|
+
marker.escrow === escrow
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
return marker.factory === factory && marker.escrow === escrow;
|
|
975
|
+
}
|
|
976
|
+
|
|
780
977
|
/**
|
|
781
978
|
* One x402-mode request, mirroring the reference `@x402/*` middleware: verify
|
|
782
979
|
* via the facilitator, run the upstream, and settle ONLY when the upstream
|
package/src/mcp/server.ts
CHANGED
|
@@ -377,6 +377,18 @@ export interface StoredStreamRecord {
|
|
|
377
377
|
* reservation key, not an escrow stream id.
|
|
378
378
|
*/
|
|
379
379
|
readonly x402Reservation?: boolean;
|
|
380
|
+
/**
|
|
381
|
+
* NOT a stream: a settled x402 upfront-only charge (an upfront opener with
|
|
382
|
+
* `upfrontAmount == price` opens no stream). It counts against the
|
|
383
|
+
* cumulative cap like any spend and has nothing to refund; `streamId` is a
|
|
384
|
+
* charge key, not an escrow stream id.
|
|
385
|
+
*/
|
|
386
|
+
readonly x402Charge?: "upfront-only";
|
|
387
|
+
/**
|
|
388
|
+
* x402 upfront openers: the NON-REFUNDABLE part of `budgetUsdc` (paid to the
|
|
389
|
+
* seller at settlement). Informational: `budgetUsdc` always counts it.
|
|
390
|
+
*/
|
|
391
|
+
readonly nonRefundableUsdc?: bigint;
|
|
380
392
|
}
|
|
381
393
|
|
|
382
394
|
export interface StreamRegistry {
|
|
@@ -456,6 +468,8 @@ export class FileStreamRegistry implements StreamRegistry {
|
|
|
456
468
|
refundUrl: z.string().url().optional(),
|
|
457
469
|
opener: addressSchema.optional(),
|
|
458
470
|
x402Reservation: z.boolean().optional(),
|
|
471
|
+
x402Charge: z.literal("upfront-only").optional(),
|
|
472
|
+
nonRefundableUsdc: decimalStringSchema.optional(),
|
|
459
473
|
}),
|
|
460
474
|
)
|
|
461
475
|
.parse(JSON.parse(contents));
|
|
@@ -480,6 +494,12 @@ export class FileStreamRegistry implements StreamRegistry {
|
|
|
480
494
|
? {}
|
|
481
495
|
: { opener: record.opener as `0x${string}` }),
|
|
482
496
|
...(record.x402Reservation === true ? { x402Reservation: true } : {}),
|
|
497
|
+
...(record.x402Charge === undefined
|
|
498
|
+
? {}
|
|
499
|
+
: { x402Charge: record.x402Charge }),
|
|
500
|
+
...(record.nonRefundableUsdc === undefined
|
|
501
|
+
? {}
|
|
502
|
+
: { nonRefundableUsdc: BigInt(record.nonRefundableUsdc) }),
|
|
483
503
|
},
|
|
484
504
|
]),
|
|
485
505
|
);
|
|
@@ -496,6 +516,9 @@ export class FileStreamRegistry implements StreamRegistry {
|
|
|
496
516
|
...record,
|
|
497
517
|
budgetUsdc: record.budgetUsdc.toString(),
|
|
498
518
|
ratePerSecondUsdc: record.ratePerSecondUsdc.toString(),
|
|
519
|
+
...(record.nonRefundableUsdc === undefined
|
|
520
|
+
? {}
|
|
521
|
+
: { nonRefundableUsdc: record.nonRefundableUsdc.toString() }),
|
|
499
522
|
})),
|
|
500
523
|
null,
|
|
501
524
|
2,
|
|
@@ -1129,21 +1152,27 @@ export function createVerifiedStreamMcpServer(
|
|
|
1129
1152
|
// A reservation key is not an escrow stream: report it as what it
|
|
1130
1153
|
// is instead of reading a stream that does not exist.
|
|
1131
1154
|
const status =
|
|
1132
|
-
record.
|
|
1155
|
+
record.x402Charge === "upfront-only"
|
|
1133
1156
|
? {
|
|
1134
1157
|
streamId: record.streamId,
|
|
1135
|
-
|
|
1136
|
-
"
|
|
1158
|
+
x402Charge:
|
|
1159
|
+
"a settled x402 upfront-only charge: the whole price was a non-refundable per-call charge, no stream exists and there is nothing to refund; its amount counts against the spend mandate",
|
|
1137
1160
|
}
|
|
1138
|
-
:
|
|
1139
|
-
|
|
1140
|
-
.then((value) =>
|
|
1141
|
-
serializeStreamStatus(value, record.streamId),
|
|
1142
|
-
)
|
|
1143
|
-
.catch((error) => ({
|
|
1161
|
+
: record.x402Reservation === true
|
|
1162
|
+
? {
|
|
1144
1163
|
streamId: record.streamId,
|
|
1145
|
-
|
|
1146
|
-
|
|
1164
|
+
x402Reservation:
|
|
1165
|
+
"an x402 payment was signed but its stream could not be confirmed; its amount stays counted against the spend mandate",
|
|
1166
|
+
}
|
|
1167
|
+
: await options.agentClient
|
|
1168
|
+
.getStreamStatus(record.streamId)
|
|
1169
|
+
.then((value) =>
|
|
1170
|
+
serializeStreamStatus(value, record.streamId),
|
|
1171
|
+
)
|
|
1172
|
+
.catch((error) => ({
|
|
1173
|
+
streamId: record.streamId,
|
|
1174
|
+
statusError: asErrorMessage(error),
|
|
1175
|
+
}));
|
|
1147
1176
|
return {
|
|
1148
1177
|
streamId: record.streamId,
|
|
1149
1178
|
buyer: record.buyer,
|
|
@@ -1154,6 +1183,9 @@ export function createVerifiedStreamMcpServer(
|
|
|
1154
1183
|
maxDurationSeconds: record.maxDurationSeconds,
|
|
1155
1184
|
openedAtSeconds: record.openedAtSeconds,
|
|
1156
1185
|
mandateId: record.mandateId,
|
|
1186
|
+
...(record.nonRefundableUsdc === undefined
|
|
1187
|
+
? {}
|
|
1188
|
+
: { nonRefundableUsdc: record.nonRefundableUsdc.toString() }),
|
|
1157
1189
|
status,
|
|
1158
1190
|
};
|
|
1159
1191
|
}),
|