@agentcash/router 1.5.0 → 1.5.1
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 +80 -17
- package/dist/index.cjs +336 -77
- package/dist/index.d.cts +99 -42
- package/dist/index.d.ts +99 -42
- package/dist/index.js +336 -77
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -470,7 +470,7 @@ var import_server2 = require("next/server");
|
|
|
470
470
|
|
|
471
471
|
// src/auth/normalize-wallet.ts
|
|
472
472
|
function normalizeWalletAddress(address) {
|
|
473
|
-
return
|
|
473
|
+
return /^0x/i.test(address) ? address.toLowerCase() : address;
|
|
474
474
|
}
|
|
475
475
|
|
|
476
476
|
// src/plugin.ts
|
|
@@ -635,12 +635,13 @@ var HttpError = class extends Error {
|
|
|
635
635
|
};
|
|
636
636
|
|
|
637
637
|
// src/handler.ts
|
|
638
|
-
async function safeCallHandler(handler, ctx) {
|
|
638
|
+
async function safeCallHandler(handler, ctx, options = {}) {
|
|
639
639
|
try {
|
|
640
640
|
const result = await handler(ctx);
|
|
641
641
|
if (result instanceof Response) return result;
|
|
642
642
|
return import_server.NextResponse.json(result);
|
|
643
643
|
} catch (error) {
|
|
644
|
+
options.onError?.(error);
|
|
644
645
|
const status = error instanceof HttpError ? error.status : typeof error.status === "number" ? error.status : 500;
|
|
645
646
|
const message = error instanceof Error ? error.message : "Internal error";
|
|
646
647
|
return import_server.NextResponse.json({ success: false, error: message }, { status });
|
|
@@ -763,6 +764,9 @@ async function verifyX402Payment(opts) {
|
|
|
763
764
|
throw err;
|
|
764
765
|
}
|
|
765
766
|
if (!verify.isValid) return invalidPaymentVerification();
|
|
767
|
+
if (typeof verify.payer !== "string" || verify.payer.length === 0) {
|
|
768
|
+
throw new Error("x402 verification succeeded without a payer address");
|
|
769
|
+
}
|
|
766
770
|
return {
|
|
767
771
|
valid: true,
|
|
768
772
|
payer: verify.payer,
|
|
@@ -1057,6 +1061,17 @@ function getRequirementRecipient(requirements) {
|
|
|
1057
1061
|
const payTo = requirements?.payTo;
|
|
1058
1062
|
return typeof payTo === "string" ? payTo : void 0;
|
|
1059
1063
|
}
|
|
1064
|
+
function errorStatus(error, fallback) {
|
|
1065
|
+
const status = error?.status;
|
|
1066
|
+
return typeof status === "number" ? status : fallback;
|
|
1067
|
+
}
|
|
1068
|
+
function errorMessage(error, fallback) {
|
|
1069
|
+
return error instanceof Error ? error.message : fallback;
|
|
1070
|
+
}
|
|
1071
|
+
function handlerFailureError(response) {
|
|
1072
|
+
const message = response.statusText || `Handler returned HTTP ${response.status}`;
|
|
1073
|
+
return Object.assign(new Error(message), { status: response.status });
|
|
1074
|
+
}
|
|
1060
1075
|
function siwxSignatureType(network) {
|
|
1061
1076
|
return network.startsWith("solana:") ? "ed25519" : "eip191";
|
|
1062
1077
|
}
|
|
@@ -1096,11 +1111,20 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1096
1111
|
setVerifiedWallet: (addr) => pluginCtx.setVerifiedWallet(addr)
|
|
1097
1112
|
};
|
|
1098
1113
|
let rawResult;
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1114
|
+
let handlerError;
|
|
1115
|
+
const response = await safeCallHandler(
|
|
1116
|
+
async (c) => {
|
|
1117
|
+
rawResult = await handler(c);
|
|
1118
|
+
return rawResult;
|
|
1119
|
+
},
|
|
1120
|
+
ctx,
|
|
1121
|
+
{
|
|
1122
|
+
onError(error) {
|
|
1123
|
+
handlerError = error;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
);
|
|
1127
|
+
return { response, rawResult, handlerError };
|
|
1104
1128
|
}
|
|
1105
1129
|
function finalize(response, rawResult, meta, pluginCtx, requestBody) {
|
|
1106
1130
|
fireProviderQuota(routeEntry, response, rawResult, deps, pluginCtx);
|
|
@@ -1111,6 +1135,87 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1111
1135
|
firePluginResponse(deps, pluginCtx, meta, response, requestBody);
|
|
1112
1136
|
return response;
|
|
1113
1137
|
}
|
|
1138
|
+
function settlementContext(scope) {
|
|
1139
|
+
return {
|
|
1140
|
+
route: routeEntry.key,
|
|
1141
|
+
request: scope.request,
|
|
1142
|
+
body: scope.parsedBody,
|
|
1143
|
+
wallet: scope.wallet,
|
|
1144
|
+
account: scope.account,
|
|
1145
|
+
payment: scope.payment,
|
|
1146
|
+
response: scope.response,
|
|
1147
|
+
result: scope.rawResult
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
async function runBeforeSettle(scope) {
|
|
1151
|
+
const hook = routeEntry.settlement?.beforeSettle;
|
|
1152
|
+
if (!hook) return null;
|
|
1153
|
+
try {
|
|
1154
|
+
await hook(settlementContext(scope));
|
|
1155
|
+
return null;
|
|
1156
|
+
} catch (error) {
|
|
1157
|
+
return fail(
|
|
1158
|
+
errorStatus(error, 500),
|
|
1159
|
+
errorMessage(error, "Pre-settlement validation failed"),
|
|
1160
|
+
scope.meta,
|
|
1161
|
+
scope.pluginCtx,
|
|
1162
|
+
scope.parsedBody
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
async function runSettlementError(scope, error, phase) {
|
|
1167
|
+
const hook = routeEntry.settlement?.onSettlementError;
|
|
1168
|
+
if (!hook) return;
|
|
1169
|
+
try {
|
|
1170
|
+
await hook({
|
|
1171
|
+
...settlementContext(scope),
|
|
1172
|
+
error,
|
|
1173
|
+
phase
|
|
1174
|
+
});
|
|
1175
|
+
} catch (hookError) {
|
|
1176
|
+
const message = errorMessage(hookError, "Settlement error hook failed");
|
|
1177
|
+
console.error(`[router] ${routeEntry.key}: onSettlementError failed: ${message}`);
|
|
1178
|
+
firePluginHook(deps.plugin, "onAlert", scope.pluginCtx, {
|
|
1179
|
+
level: "error",
|
|
1180
|
+
message: `Settlement error hook failed: ${message}`,
|
|
1181
|
+
route: routeEntry.key
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
async function runAfterSettle(scope) {
|
|
1186
|
+
const hook = routeEntry.settlement?.afterSettle;
|
|
1187
|
+
if (!hook) return;
|
|
1188
|
+
try {
|
|
1189
|
+
await hook(settlementContext(scope));
|
|
1190
|
+
} catch (error) {
|
|
1191
|
+
const message = errorMessage(error, "Post-settlement hook failed");
|
|
1192
|
+
console.error(`[router] ${routeEntry.key}: afterSettle failed: ${message}`);
|
|
1193
|
+
firePluginHook(deps.plugin, "onAlert", scope.pluginCtx, {
|
|
1194
|
+
level: "error",
|
|
1195
|
+
message: `Post-settlement hook failed: ${message}`,
|
|
1196
|
+
route: routeEntry.key
|
|
1197
|
+
});
|
|
1198
|
+
await runSettlementError(scope, error, "afterSettle");
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
async function runSettledHandlerError(scope, error = scope.handlerError ?? handlerFailureError(scope.response)) {
|
|
1202
|
+
const hook = routeEntry.settlement?.onSettledHandlerError;
|
|
1203
|
+
if (!hook) return;
|
|
1204
|
+
try {
|
|
1205
|
+
await hook({
|
|
1206
|
+
...settlementContext(scope),
|
|
1207
|
+
error
|
|
1208
|
+
});
|
|
1209
|
+
} catch (hookError) {
|
|
1210
|
+
const message = errorMessage(hookError, "Settled handler error hook failed");
|
|
1211
|
+
console.error(`[router] ${routeEntry.key}: onSettledHandlerError failed: ${message}`);
|
|
1212
|
+
firePluginHook(deps.plugin, "onAlert", scope.pluginCtx, {
|
|
1213
|
+
level: "error",
|
|
1214
|
+
message: `Settled handler error hook failed: ${message}`,
|
|
1215
|
+
route: routeEntry.key
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1114
1219
|
return async (request) => {
|
|
1115
1220
|
await deps.initPromise;
|
|
1116
1221
|
const meta = buildMeta(request, routeEntry);
|
|
@@ -1366,18 +1471,9 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1366
1471
|
return fail(status, message, meta, pluginCtx, body.data);
|
|
1367
1472
|
}
|
|
1368
1473
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
} catch (err) {
|
|
1373
|
-
return fail(
|
|
1374
|
-
err.status ?? 500,
|
|
1375
|
-
err instanceof Error ? err.message : "Price resolution failed",
|
|
1376
|
-
meta,
|
|
1377
|
-
pluginCtx,
|
|
1378
|
-
body.data
|
|
1379
|
-
);
|
|
1380
|
-
}
|
|
1474
|
+
const priceResult = await resolveDynamicPrice(body.data, routeEntry, deps, pluginCtx, meta);
|
|
1475
|
+
if ("error" in priceResult) return priceResult.error;
|
|
1476
|
+
const price = priceResult.price;
|
|
1381
1477
|
if (!routeEntry.protocols.includes(protocol)) {
|
|
1382
1478
|
const accepted = routeEntry.protocols.join(", ") || "none";
|
|
1383
1479
|
console.warn(
|
|
@@ -1431,7 +1527,7 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1431
1527
|
amount: price,
|
|
1432
1528
|
network: matchedNetwork
|
|
1433
1529
|
});
|
|
1434
|
-
const { response, rawResult } = await invoke(
|
|
1530
|
+
const { response, rawResult, handlerError } = await invoke(
|
|
1435
1531
|
request,
|
|
1436
1532
|
meta,
|
|
1437
1533
|
pluginCtx,
|
|
@@ -1440,7 +1536,21 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1440
1536
|
body.data,
|
|
1441
1537
|
payment
|
|
1442
1538
|
);
|
|
1539
|
+
const settleScope = {
|
|
1540
|
+
request,
|
|
1541
|
+
meta,
|
|
1542
|
+
pluginCtx,
|
|
1543
|
+
wallet,
|
|
1544
|
+
account,
|
|
1545
|
+
parsedBody: body.data,
|
|
1546
|
+
payment,
|
|
1547
|
+
response,
|
|
1548
|
+
rawResult,
|
|
1549
|
+
handlerError
|
|
1550
|
+
};
|
|
1443
1551
|
if (response.status < 400) {
|
|
1552
|
+
const validationFailure = await runBeforeSettle(settleScope);
|
|
1553
|
+
if (validationFailure) return validationFailure;
|
|
1444
1554
|
try {
|
|
1445
1555
|
const settle = await settleX402Payment(
|
|
1446
1556
|
deps.x402Server,
|
|
@@ -1466,13 +1576,21 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1466
1576
|
}
|
|
1467
1577
|
response.headers.set("PAYMENT-RESPONSE", settle.encoded);
|
|
1468
1578
|
response.headers.set("Cache-Control", "private");
|
|
1579
|
+
const transaction = String(settle.result?.transaction ?? "");
|
|
1580
|
+
const settledPayment = {
|
|
1581
|
+
...payment,
|
|
1582
|
+
status: "settled",
|
|
1583
|
+
...transaction ? { transaction } : {}
|
|
1584
|
+
};
|
|
1469
1585
|
firePluginHook(deps.plugin, "onPaymentSettled", pluginCtx, {
|
|
1470
1586
|
protocol: "x402",
|
|
1471
|
-
payer:
|
|
1472
|
-
transaction
|
|
1587
|
+
payer: wallet,
|
|
1588
|
+
transaction,
|
|
1473
1589
|
network: matchedNetwork
|
|
1474
1590
|
});
|
|
1591
|
+
await runAfterSettle({ ...settleScope, payment: settledPayment });
|
|
1475
1592
|
} catch (err) {
|
|
1593
|
+
await runSettlementError(settleScope, err, "settle");
|
|
1476
1594
|
const errObj = err;
|
|
1477
1595
|
console.error("Settlement failed", {
|
|
1478
1596
|
message: err instanceof Error ? err.message : String(err),
|
|
@@ -1531,27 +1649,44 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1531
1649
|
amount: price,
|
|
1532
1650
|
network: "tempo:4217"
|
|
1533
1651
|
});
|
|
1534
|
-
const
|
|
1652
|
+
const mppRecipient2 = deps.mppRecipient ?? deps.payeeAddress;
|
|
1653
|
+
const payment2 = {
|
|
1654
|
+
protocol: "mpp",
|
|
1655
|
+
status: "verified",
|
|
1656
|
+
payer: wallet,
|
|
1657
|
+
amount: price,
|
|
1658
|
+
network: "tempo:4217",
|
|
1659
|
+
...mppRecipient2 ? { recipient: mppRecipient2 } : {}
|
|
1660
|
+
};
|
|
1661
|
+
const { response: response2, rawResult: rawResult2, handlerError: handlerError2 } = await invoke(
|
|
1535
1662
|
request,
|
|
1536
1663
|
meta,
|
|
1537
1664
|
pluginCtx,
|
|
1538
1665
|
wallet,
|
|
1539
1666
|
account,
|
|
1540
1667
|
body.data,
|
|
1541
|
-
|
|
1542
|
-
protocol: "mpp",
|
|
1543
|
-
status: "verified",
|
|
1544
|
-
payer: wallet,
|
|
1545
|
-
amount: price,
|
|
1546
|
-
network: "tempo:4217",
|
|
1547
|
-
recipient: deps.payeeAddress
|
|
1548
|
-
}
|
|
1668
|
+
payment2
|
|
1549
1669
|
);
|
|
1670
|
+
const settleScope2 = {
|
|
1671
|
+
request,
|
|
1672
|
+
meta,
|
|
1673
|
+
pluginCtx,
|
|
1674
|
+
wallet,
|
|
1675
|
+
account,
|
|
1676
|
+
parsedBody: body.data,
|
|
1677
|
+
payment: payment2,
|
|
1678
|
+
response: response2,
|
|
1679
|
+
rawResult: rawResult2,
|
|
1680
|
+
handlerError: handlerError2
|
|
1681
|
+
};
|
|
1550
1682
|
if (response2.status < 400) {
|
|
1683
|
+
const validationFailure = await runBeforeSettle(settleScope2);
|
|
1684
|
+
if (validationFailure) return validationFailure;
|
|
1551
1685
|
let mppResult2;
|
|
1552
1686
|
try {
|
|
1553
1687
|
mppResult2 = await deps.mppx.charge({ amount: price })(request);
|
|
1554
1688
|
} catch (err) {
|
|
1689
|
+
await runSettlementError(settleScope2, err, "settle");
|
|
1555
1690
|
const message = err instanceof Error ? err.message : String(err);
|
|
1556
1691
|
console.error(
|
|
1557
1692
|
`[router] ${routeEntry.key}: MPP broadcast failed after handler: ${message}`
|
|
@@ -1580,6 +1715,13 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1580
1715
|
} catch {
|
|
1581
1716
|
}
|
|
1582
1717
|
const detail = rejectReason || "transaction reverted on-chain after handler execution";
|
|
1718
|
+
const settlementError = Object.assign(new Error(detail), {
|
|
1719
|
+
status: 402,
|
|
1720
|
+
detail,
|
|
1721
|
+
mppResult: mppResult2,
|
|
1722
|
+
challenge: mppResult2.challenge
|
|
1723
|
+
});
|
|
1724
|
+
await runSettlementError(settleScope2, settlementError, "settle");
|
|
1583
1725
|
console.error(
|
|
1584
1726
|
`[router] ${routeEntry.key}: MPP payment failed after handler \u2014 ${detail}`
|
|
1585
1727
|
);
|
|
@@ -1617,6 +1759,17 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1617
1759
|
transaction: txHash2,
|
|
1618
1760
|
network: "tempo:4217"
|
|
1619
1761
|
});
|
|
1762
|
+
const settledPayment = {
|
|
1763
|
+
...payment2,
|
|
1764
|
+
status: "settled",
|
|
1765
|
+
...txHash2 ? { transaction: txHash2 } : {},
|
|
1766
|
+
...receiptHeader2 ? { receipt: receiptHeader2 } : {}
|
|
1767
|
+
};
|
|
1768
|
+
await runAfterSettle({
|
|
1769
|
+
...settleScope2,
|
|
1770
|
+
payment: settledPayment,
|
|
1771
|
+
response: receiptResponse
|
|
1772
|
+
});
|
|
1620
1773
|
finalize(receiptResponse, rawResult2, meta, pluginCtx, body.data);
|
|
1621
1774
|
return receiptResponse;
|
|
1622
1775
|
}
|
|
@@ -1672,24 +1825,38 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1672
1825
|
amount: price,
|
|
1673
1826
|
network: "tempo:4217"
|
|
1674
1827
|
});
|
|
1675
|
-
const
|
|
1828
|
+
const mppRecipient = deps.mppRecipient ?? deps.payeeAddress;
|
|
1829
|
+
const payment = {
|
|
1830
|
+
protocol: "mpp",
|
|
1831
|
+
status: "settled",
|
|
1832
|
+
payer: wallet,
|
|
1833
|
+
amount: price,
|
|
1834
|
+
network: "tempo:4217",
|
|
1835
|
+
...mppRecipient ? { recipient: mppRecipient } : {},
|
|
1836
|
+
...txHash ? { transaction: txHash } : {},
|
|
1837
|
+
...receiptHeader ? { receipt: receiptHeader } : {}
|
|
1838
|
+
};
|
|
1839
|
+
const { response, rawResult, handlerError } = await invoke(
|
|
1676
1840
|
request,
|
|
1677
1841
|
meta,
|
|
1678
1842
|
pluginCtx,
|
|
1679
1843
|
wallet,
|
|
1680
1844
|
account,
|
|
1681
1845
|
body.data,
|
|
1682
|
-
|
|
1683
|
-
protocol: "mpp",
|
|
1684
|
-
status: "settled",
|
|
1685
|
-
payer: wallet,
|
|
1686
|
-
amount: price,
|
|
1687
|
-
network: "tempo:4217",
|
|
1688
|
-
recipient: deps.payeeAddress,
|
|
1689
|
-
...txHash ? { transaction: txHash } : {},
|
|
1690
|
-
...receiptHeader ? { receipt: receiptHeader } : {}
|
|
1691
|
-
}
|
|
1846
|
+
payment
|
|
1692
1847
|
);
|
|
1848
|
+
const settleScope = {
|
|
1849
|
+
request,
|
|
1850
|
+
meta,
|
|
1851
|
+
pluginCtx,
|
|
1852
|
+
wallet,
|
|
1853
|
+
account,
|
|
1854
|
+
parsedBody: body.data,
|
|
1855
|
+
payment,
|
|
1856
|
+
response,
|
|
1857
|
+
rawResult,
|
|
1858
|
+
handlerError
|
|
1859
|
+
};
|
|
1693
1860
|
if (response.status < 400) {
|
|
1694
1861
|
if (routeEntry.siwxEnabled) {
|
|
1695
1862
|
try {
|
|
@@ -1710,9 +1877,11 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1710
1877
|
transaction: txHash,
|
|
1711
1878
|
network: "tempo:4217"
|
|
1712
1879
|
});
|
|
1880
|
+
await runAfterSettle({ ...settleScope, response: receiptResponse });
|
|
1713
1881
|
finalize(receiptResponse, rawResult, meta, pluginCtx, body.data);
|
|
1714
1882
|
return receiptResponse;
|
|
1715
1883
|
}
|
|
1884
|
+
await runSettledHandlerError(settleScope);
|
|
1716
1885
|
finalize(response, rawResult, meta, pluginCtx, body.data);
|
|
1717
1886
|
return response;
|
|
1718
1887
|
}
|
|
@@ -1785,9 +1954,10 @@ async function resolveDynamicPrice(bodyData, routeEntry, deps, pluginCtx, meta)
|
|
|
1785
1954
|
});
|
|
1786
1955
|
return { price: routeEntry.maxPrice };
|
|
1787
1956
|
} else {
|
|
1957
|
+
const message = errorMessage(err, "Price calculation failed");
|
|
1788
1958
|
const errorResponse = import_server2.NextResponse.json(
|
|
1789
|
-
{ success: false, error:
|
|
1790
|
-
{ status: 500 }
|
|
1959
|
+
{ success: false, error: message },
|
|
1960
|
+
{ status: errorStatus(err, 500) }
|
|
1791
1961
|
);
|
|
1792
1962
|
firePluginResponse(deps, pluginCtx, meta, errorResponse);
|
|
1793
1963
|
return { error: errorResponse };
|
|
@@ -1960,22 +2130,13 @@ function fireProviderQuota(routeEntry, response, handlerResult, deps, pluginCtx)
|
|
|
1960
2130
|
|
|
1961
2131
|
// src/validate-examples.ts
|
|
1962
2132
|
function validateExamples(key, bodySchema, querySchema, outputSchema, inputExample, hasInputExample, outputExample, hasOutputExample) {
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
);
|
|
1967
|
-
}
|
|
1968
|
-
if (querySchema && !hasInputExample) {
|
|
1969
|
-
throw new Error(
|
|
1970
|
-
`route '${key}': .query() requires a matching .inputExample() \u2014 the bazaar discovery extension needs a conforming sample query to advertise.`
|
|
1971
|
-
);
|
|
2133
|
+
const inputSchema = bodySchema ?? querySchema;
|
|
2134
|
+
if (hasInputExample && !inputSchema) {
|
|
2135
|
+
throw new Error(`route '${key}': .inputExample() requires .body() or .query()`);
|
|
1972
2136
|
}
|
|
1973
|
-
if (
|
|
1974
|
-
throw new Error(
|
|
1975
|
-
`route '${key}': .output() requires a matching .outputExample() \u2014 the bazaar discovery extension needs a conforming sample response to advertise.`
|
|
1976
|
-
);
|
|
2137
|
+
if (hasOutputExample && !outputSchema) {
|
|
2138
|
+
throw new Error(`route '${key}': .outputExample() requires .output()`);
|
|
1977
2139
|
}
|
|
1978
|
-
const inputSchema = bodySchema ?? querySchema;
|
|
1979
2140
|
if (inputSchema && hasInputExample) {
|
|
1980
2141
|
const result = inputSchema.safeParse(inputExample);
|
|
1981
2142
|
if (!result.success) {
|
|
@@ -2049,6 +2210,8 @@ var RouteBuilder = class {
|
|
|
2049
2210
|
/** @internal */
|
|
2050
2211
|
_validateFn;
|
|
2051
2212
|
/** @internal */
|
|
2213
|
+
_settlement;
|
|
2214
|
+
/** @internal */
|
|
2052
2215
|
_mppInfo;
|
|
2053
2216
|
constructor(key, registry, deps) {
|
|
2054
2217
|
this._key = key;
|
|
@@ -2062,11 +2225,21 @@ var RouteBuilder = class {
|
|
|
2062
2225
|
return next;
|
|
2063
2226
|
}
|
|
2064
2227
|
paid(pricing, options) {
|
|
2228
|
+
if (this._authMode === "unprotected") {
|
|
2229
|
+
throw new Error(
|
|
2230
|
+
`route '${this._key}': Cannot combine .unprotected() and .paid() on the same route.`
|
|
2231
|
+
);
|
|
2232
|
+
}
|
|
2233
|
+
if (this._pricing !== void 0) {
|
|
2234
|
+
throw new Error(
|
|
2235
|
+
`route '${this._key}': Cannot call .paid() more than once on the same route.`
|
|
2236
|
+
);
|
|
2237
|
+
}
|
|
2065
2238
|
const next = this.fork();
|
|
2066
2239
|
next._authMode = "paid";
|
|
2067
2240
|
next._pricing = pricing;
|
|
2068
2241
|
if (options?.protocols) {
|
|
2069
|
-
next._protocols = options.protocols;
|
|
2242
|
+
next._protocols = [...options.protocols];
|
|
2070
2243
|
} else if (next._protocols.length === 0) {
|
|
2071
2244
|
next._protocols = ["x402"];
|
|
2072
2245
|
}
|
|
@@ -2131,6 +2304,16 @@ var RouteBuilder = class {
|
|
|
2131
2304
|
return next;
|
|
2132
2305
|
}
|
|
2133
2306
|
unprotected() {
|
|
2307
|
+
if (this._authMode && this._authMode !== "unprotected") {
|
|
2308
|
+
throw new Error(
|
|
2309
|
+
`route '${this._key}': Cannot combine .unprotected() and .${this._authMode}() on the same route.`
|
|
2310
|
+
);
|
|
2311
|
+
}
|
|
2312
|
+
if (this._pricing) {
|
|
2313
|
+
throw new Error(
|
|
2314
|
+
`route '${this._key}': Cannot combine .unprotected() and .paid() on the same route.`
|
|
2315
|
+
);
|
|
2316
|
+
}
|
|
2134
2317
|
const next = this.fork();
|
|
2135
2318
|
next._authMode = "unprotected";
|
|
2136
2319
|
next._protocols = [];
|
|
@@ -2145,32 +2328,43 @@ var RouteBuilder = class {
|
|
|
2145
2328
|
next._providerConfig = config ?? {};
|
|
2146
2329
|
return next;
|
|
2147
2330
|
}
|
|
2148
|
-
|
|
2149
|
-
// Schema methods
|
|
2150
|
-
// -------------------------------------------------------------------------
|
|
2151
|
-
body(schema) {
|
|
2331
|
+
body(schema, example) {
|
|
2152
2332
|
const next = this.fork();
|
|
2153
2333
|
next._bodySchema = schema;
|
|
2334
|
+
if (example !== void 0) {
|
|
2335
|
+
next._inputExample = example;
|
|
2336
|
+
next._hasInputExample = true;
|
|
2337
|
+
}
|
|
2154
2338
|
return next;
|
|
2155
2339
|
}
|
|
2156
|
-
query(schema) {
|
|
2340
|
+
query(schema, example) {
|
|
2157
2341
|
const next = this.fork();
|
|
2158
2342
|
next._querySchema = schema;
|
|
2343
|
+
if (example !== void 0) {
|
|
2344
|
+
next._inputExample = example;
|
|
2345
|
+
next._hasInputExample = true;
|
|
2346
|
+
}
|
|
2159
2347
|
next._method = "GET";
|
|
2160
2348
|
return next;
|
|
2161
2349
|
}
|
|
2162
|
-
output(schema) {
|
|
2350
|
+
output(schema, example) {
|
|
2163
2351
|
const next = this.fork();
|
|
2164
2352
|
next._outputSchema = schema;
|
|
2353
|
+
if (example !== void 0) {
|
|
2354
|
+
next._outputExample = example;
|
|
2355
|
+
next._hasOutputExample = true;
|
|
2356
|
+
}
|
|
2165
2357
|
return next;
|
|
2166
2358
|
}
|
|
2167
2359
|
/**
|
|
2168
2360
|
* Provide a conforming example of the request input (body or query params).
|
|
2169
2361
|
*
|
|
2170
|
-
*
|
|
2171
|
-
*
|
|
2172
|
-
*
|
|
2173
|
-
*
|
|
2362
|
+
* Optional. When provided, the example is validated against the request schema
|
|
2363
|
+
* at route registration and embedded in the bazaar discovery extension so
|
|
2364
|
+
* indexers can advertise a working sample call.
|
|
2365
|
+
*
|
|
2366
|
+
* For the common case, pass the example directly to `.body(schema, example)` or
|
|
2367
|
+
* `.query(schema, example)` instead.
|
|
2174
2368
|
*
|
|
2175
2369
|
* @example
|
|
2176
2370
|
* ```ts
|
|
@@ -2190,10 +2384,11 @@ var RouteBuilder = class {
|
|
|
2190
2384
|
/**
|
|
2191
2385
|
* Provide a conforming example of the response output.
|
|
2192
2386
|
*
|
|
2193
|
-
*
|
|
2194
|
-
*
|
|
2195
|
-
*
|
|
2196
|
-
*
|
|
2387
|
+
* Optional. When provided, the example is validated against the output schema
|
|
2388
|
+
* at route registration and embedded in the bazaar discovery extension so
|
|
2389
|
+
* indexers can advertise the response shape.
|
|
2390
|
+
*
|
|
2391
|
+
* For the common case, pass the example directly to `.output(schema, example)` instead.
|
|
2197
2392
|
*
|
|
2198
2393
|
* Accepts any JSON value (objects, arrays, or primitives) — top-level array
|
|
2199
2394
|
* or primitive responses (e.g. `z.array(...)`) are supported alongside the
|
|
@@ -2266,15 +2461,39 @@ var RouteBuilder = class {
|
|
|
2266
2461
|
return next;
|
|
2267
2462
|
}
|
|
2268
2463
|
// -------------------------------------------------------------------------
|
|
2464
|
+
// Settlement lifecycle
|
|
2465
|
+
// -------------------------------------------------------------------------
|
|
2466
|
+
/**
|
|
2467
|
+
* Add route-specific settlement hooks.
|
|
2468
|
+
*
|
|
2469
|
+
* `beforeSettle` runs after a successful handler response but before
|
|
2470
|
+
* router-controlled settlement/broadcast, so it can still prevent the charge
|
|
2471
|
+
* for x402 and MPP transaction-payload flows. `afterSettle` runs after
|
|
2472
|
+
* settlement and is intended for durable ledgers or app-owned refund queues.
|
|
2473
|
+
*/
|
|
2474
|
+
settlement(lifecycle) {
|
|
2475
|
+
const next = this.fork();
|
|
2476
|
+
next._settlement = lifecycle;
|
|
2477
|
+
return next;
|
|
2478
|
+
}
|
|
2479
|
+
// -------------------------------------------------------------------------
|
|
2269
2480
|
// Terminal method
|
|
2270
2481
|
// -------------------------------------------------------------------------
|
|
2271
2482
|
handler(fn) {
|
|
2272
2483
|
const handlerFn = fn;
|
|
2484
|
+
if (!this._authMode) {
|
|
2485
|
+
throw new Error(
|
|
2486
|
+
`route '${this._key}': Select an auth mode: .paid(pricing), .siwx(), .apiKey(resolver), or .unprotected()`
|
|
2487
|
+
);
|
|
2488
|
+
}
|
|
2273
2489
|
if (this._validateFn && !this._bodySchema) {
|
|
2274
2490
|
throw new Error(
|
|
2275
2491
|
`route '${this._key}': .validate() requires .body() \u2014 validation runs on parsed body`
|
|
2276
2492
|
);
|
|
2277
2493
|
}
|
|
2494
|
+
if (this._settlement && !this._pricing) {
|
|
2495
|
+
throw new Error(`route '${this._key}': .settlement() requires a paid route`);
|
|
2496
|
+
}
|
|
2278
2497
|
validateExamples(
|
|
2279
2498
|
this._key,
|
|
2280
2499
|
this._bodySchema,
|
|
@@ -2306,6 +2525,7 @@ var RouteBuilder = class {
|
|
|
2306
2525
|
providerName: this._providerName,
|
|
2307
2526
|
providerConfig: this._providerConfig,
|
|
2308
2527
|
validateFn: this._validateFn,
|
|
2528
|
+
settlement: this._settlement,
|
|
2309
2529
|
mppInfo: this._mppInfo
|
|
2310
2530
|
};
|
|
2311
2531
|
this._registry.register(entry);
|
|
@@ -2442,6 +2662,7 @@ function toDiscoveryResource(method, url, mode) {
|
|
|
2442
2662
|
|
|
2443
2663
|
// src/discovery/openapi.ts
|
|
2444
2664
|
var import_server4 = require("next/server");
|
|
2665
|
+
init_constants();
|
|
2445
2666
|
function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
|
|
2446
2667
|
const normalizedBase = baseUrl.replace(/\/+$/, "");
|
|
2447
2668
|
let cached = null;
|
|
@@ -2583,7 +2804,7 @@ function toProtocolObject(protocol, mppInfo) {
|
|
|
2583
2804
|
mpp: {
|
|
2584
2805
|
method: mppInfo?.method ?? "tempo",
|
|
2585
2806
|
intent: mppInfo?.intent ?? "charge",
|
|
2586
|
-
currency: mppInfo?.currency ??
|
|
2807
|
+
currency: mppInfo?.currency ?? TEMPO_USDC_CURRENCY
|
|
2587
2808
|
}
|
|
2588
2809
|
};
|
|
2589
2810
|
}
|
|
@@ -2698,6 +2919,8 @@ function mppFromEnv(env, options = {}) {
|
|
|
2698
2919
|
const secretKey = env.MPP_SECRET_KEY;
|
|
2699
2920
|
const currency = env.MPP_CURRENCY;
|
|
2700
2921
|
const rpcUrl = env.TEMPO_RPC_URL;
|
|
2922
|
+
const feePayerKey = options.feePayerKey ?? env.MPP_FEE_PAYER_KEY;
|
|
2923
|
+
const feePayerKeySource = options.feePayerKey !== void 0 ? "feePayerKey" : "MPP_FEE_PAYER_KEY";
|
|
2701
2924
|
const hasAnyMppEnv = Boolean(secretKey || currency || rpcUrl || options.require);
|
|
2702
2925
|
if (!hasAnyMppEnv) return void 0;
|
|
2703
2926
|
const missing = [
|
|
@@ -2708,12 +2931,21 @@ function mppFromEnv(env, options = {}) {
|
|
|
2708
2931
|
if (missing.length > 0) {
|
|
2709
2932
|
throw new Error(`MPP env is incomplete. Missing: ${missing.join(", ")}`);
|
|
2710
2933
|
}
|
|
2934
|
+
if (!isEvmAddress(currency)) {
|
|
2935
|
+
throw new Error("MPP_CURRENCY must be a 0x-prefixed 20-byte Tempo currency address");
|
|
2936
|
+
}
|
|
2937
|
+
if (options.recipient && !isEvmAddress(options.recipient)) {
|
|
2938
|
+
throw new Error("MPP recipient must be a 0x-prefixed EVM address");
|
|
2939
|
+
}
|
|
2940
|
+
if (feePayerKey && !isEvmPrivateKey(feePayerKey)) {
|
|
2941
|
+
throw new Error(`${feePayerKeySource} must be a 0x-prefixed 32-byte EVM private key`);
|
|
2942
|
+
}
|
|
2711
2943
|
return {
|
|
2712
2944
|
secretKey,
|
|
2713
2945
|
currency,
|
|
2714
2946
|
rpcUrl,
|
|
2715
2947
|
...options.recipient ? { recipient: options.recipient } : {},
|
|
2716
|
-
...
|
|
2948
|
+
...feePayerKey ? { feePayerKey } : {},
|
|
2717
2949
|
...options.useDefaultStore !== void 0 ? { useDefaultStore: options.useDefaultStore } : {}
|
|
2718
2950
|
};
|
|
2719
2951
|
}
|
|
@@ -2851,13 +3083,26 @@ function validateMppConfig(config, env) {
|
|
|
2851
3083
|
protocol: "mpp",
|
|
2852
3084
|
message: "MPP requires currency. Set MPP_CURRENCY or pass mpp.currency."
|
|
2853
3085
|
});
|
|
3086
|
+
} else if (!isEvmAddress(mpp.currency)) {
|
|
3087
|
+
issues.push({
|
|
3088
|
+
code: "invalid_mpp_currency",
|
|
3089
|
+
protocol: "mpp",
|
|
3090
|
+
message: "MPP currency must be a 0x-prefixed 20-byte Tempo currency address. Use TEMPO_USDC_CURRENCY for Tempo USDC."
|
|
3091
|
+
});
|
|
2854
3092
|
}
|
|
2855
|
-
|
|
3093
|
+
const mppRecipient = mpp.recipient ?? config.payeeAddress;
|
|
3094
|
+
if (!mppRecipient) {
|
|
2856
3095
|
issues.push({
|
|
2857
3096
|
code: "missing_mpp_recipient",
|
|
2858
3097
|
protocol: "mpp",
|
|
2859
3098
|
message: "MPP requires a recipient address. Set mpp.recipient or payeeAddress in your router config."
|
|
2860
3099
|
});
|
|
3100
|
+
} else if (!isEvmAddress(mppRecipient)) {
|
|
3101
|
+
issues.push({
|
|
3102
|
+
code: "invalid_mpp_recipient",
|
|
3103
|
+
protocol: "mpp",
|
|
3104
|
+
message: "MPP recipient must be a 0x-prefixed EVM address. Solana recipients require x402."
|
|
3105
|
+
});
|
|
2861
3106
|
}
|
|
2862
3107
|
const placeholder = findPlaceholderPayee([mpp.recipient, config.payeeAddress]);
|
|
2863
3108
|
if (placeholder) {
|
|
@@ -2874,6 +3119,13 @@ function validateMppConfig(config, env) {
|
|
|
2874
3119
|
message: "MPP requires an authenticated Tempo RPC URL. Set TEMPO_RPC_URL env var or pass rpcUrl in the mpp config object."
|
|
2875
3120
|
});
|
|
2876
3121
|
}
|
|
3122
|
+
if (mpp.feePayerKey && !isEvmPrivateKey(mpp.feePayerKey)) {
|
|
3123
|
+
issues.push({
|
|
3124
|
+
code: "invalid_mpp_fee_payer_key",
|
|
3125
|
+
protocol: "mpp",
|
|
3126
|
+
message: "MPP feePayerKey must be a 0x-prefixed 32-byte EVM private key."
|
|
3127
|
+
});
|
|
3128
|
+
}
|
|
2877
3129
|
if (mpp.useDefaultStore && !mpp.store && (!env.KV_REST_API_URL || !env.KV_REST_API_TOKEN)) {
|
|
2878
3130
|
issues.push({
|
|
2879
3131
|
code: "missing_mpp_default_store_env",
|
|
@@ -2891,8 +3143,14 @@ function usesDefaultEvmFacilitator(config) {
|
|
|
2891
3143
|
function isSupportedX402Network(network) {
|
|
2892
3144
|
return isEvmNetwork(network) || isSolanaNetwork(network);
|
|
2893
3145
|
}
|
|
3146
|
+
function isEvmAddress(value) {
|
|
3147
|
+
return /^0x[a-fA-F0-9]{40}$/.test(value);
|
|
3148
|
+
}
|
|
3149
|
+
function isEvmPrivateKey(value) {
|
|
3150
|
+
return /^0x[a-fA-F0-9]{64}$/.test(value);
|
|
3151
|
+
}
|
|
2894
3152
|
function findPlaceholderPayee(values) {
|
|
2895
|
-
return values.find((value) => value
|
|
3153
|
+
return values.find((value) => value !== void 0 && /^0x0{40}$/i.test(value)) ?? null;
|
|
2896
3154
|
}
|
|
2897
3155
|
|
|
2898
3156
|
// src/index.ts
|
|
@@ -2941,6 +3199,7 @@ function createRouter(config) {
|
|
|
2941
3199
|
nonceStore,
|
|
2942
3200
|
entitlementStore,
|
|
2943
3201
|
payeeAddress: config.payeeAddress ?? "",
|
|
3202
|
+
mppRecipient: config.mpp?.recipient ?? config.payeeAddress,
|
|
2944
3203
|
network,
|
|
2945
3204
|
x402FacilitatorsByNetwork: void 0,
|
|
2946
3205
|
x402Accepts,
|