@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.js
CHANGED
|
@@ -420,7 +420,7 @@ import { NextResponse as NextResponse2 } from "next/server";
|
|
|
420
420
|
|
|
421
421
|
// src/auth/normalize-wallet.ts
|
|
422
422
|
function normalizeWalletAddress(address) {
|
|
423
|
-
return
|
|
423
|
+
return /^0x/i.test(address) ? address.toLowerCase() : address;
|
|
424
424
|
}
|
|
425
425
|
|
|
426
426
|
// src/plugin.ts
|
|
@@ -585,12 +585,13 @@ var HttpError = class extends Error {
|
|
|
585
585
|
};
|
|
586
586
|
|
|
587
587
|
// src/handler.ts
|
|
588
|
-
async function safeCallHandler(handler, ctx) {
|
|
588
|
+
async function safeCallHandler(handler, ctx, options = {}) {
|
|
589
589
|
try {
|
|
590
590
|
const result = await handler(ctx);
|
|
591
591
|
if (result instanceof Response) return result;
|
|
592
592
|
return NextResponse.json(result);
|
|
593
593
|
} catch (error) {
|
|
594
|
+
options.onError?.(error);
|
|
594
595
|
const status = error instanceof HttpError ? error.status : typeof error.status === "number" ? error.status : 500;
|
|
595
596
|
const message = error instanceof Error ? error.message : "Internal error";
|
|
596
597
|
return NextResponse.json({ success: false, error: message }, { status });
|
|
@@ -713,6 +714,9 @@ async function verifyX402Payment(opts) {
|
|
|
713
714
|
throw err;
|
|
714
715
|
}
|
|
715
716
|
if (!verify.isValid) return invalidPaymentVerification();
|
|
717
|
+
if (typeof verify.payer !== "string" || verify.payer.length === 0) {
|
|
718
|
+
throw new Error("x402 verification succeeded without a payer address");
|
|
719
|
+
}
|
|
716
720
|
return {
|
|
717
721
|
valid: true,
|
|
718
722
|
payer: verify.payer,
|
|
@@ -1007,6 +1011,17 @@ function getRequirementRecipient(requirements) {
|
|
|
1007
1011
|
const payTo = requirements?.payTo;
|
|
1008
1012
|
return typeof payTo === "string" ? payTo : void 0;
|
|
1009
1013
|
}
|
|
1014
|
+
function errorStatus(error, fallback) {
|
|
1015
|
+
const status = error?.status;
|
|
1016
|
+
return typeof status === "number" ? status : fallback;
|
|
1017
|
+
}
|
|
1018
|
+
function errorMessage(error, fallback) {
|
|
1019
|
+
return error instanceof Error ? error.message : fallback;
|
|
1020
|
+
}
|
|
1021
|
+
function handlerFailureError(response) {
|
|
1022
|
+
const message = response.statusText || `Handler returned HTTP ${response.status}`;
|
|
1023
|
+
return Object.assign(new Error(message), { status: response.status });
|
|
1024
|
+
}
|
|
1010
1025
|
function siwxSignatureType(network) {
|
|
1011
1026
|
return network.startsWith("solana:") ? "ed25519" : "eip191";
|
|
1012
1027
|
}
|
|
@@ -1046,11 +1061,20 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1046
1061
|
setVerifiedWallet: (addr) => pluginCtx.setVerifiedWallet(addr)
|
|
1047
1062
|
};
|
|
1048
1063
|
let rawResult;
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1064
|
+
let handlerError;
|
|
1065
|
+
const response = await safeCallHandler(
|
|
1066
|
+
async (c) => {
|
|
1067
|
+
rawResult = await handler(c);
|
|
1068
|
+
return rawResult;
|
|
1069
|
+
},
|
|
1070
|
+
ctx,
|
|
1071
|
+
{
|
|
1072
|
+
onError(error) {
|
|
1073
|
+
handlerError = error;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
);
|
|
1077
|
+
return { response, rawResult, handlerError };
|
|
1054
1078
|
}
|
|
1055
1079
|
function finalize(response, rawResult, meta, pluginCtx, requestBody) {
|
|
1056
1080
|
fireProviderQuota(routeEntry, response, rawResult, deps, pluginCtx);
|
|
@@ -1061,6 +1085,87 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1061
1085
|
firePluginResponse(deps, pluginCtx, meta, response, requestBody);
|
|
1062
1086
|
return response;
|
|
1063
1087
|
}
|
|
1088
|
+
function settlementContext(scope) {
|
|
1089
|
+
return {
|
|
1090
|
+
route: routeEntry.key,
|
|
1091
|
+
request: scope.request,
|
|
1092
|
+
body: scope.parsedBody,
|
|
1093
|
+
wallet: scope.wallet,
|
|
1094
|
+
account: scope.account,
|
|
1095
|
+
payment: scope.payment,
|
|
1096
|
+
response: scope.response,
|
|
1097
|
+
result: scope.rawResult
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
async function runBeforeSettle(scope) {
|
|
1101
|
+
const hook = routeEntry.settlement?.beforeSettle;
|
|
1102
|
+
if (!hook) return null;
|
|
1103
|
+
try {
|
|
1104
|
+
await hook(settlementContext(scope));
|
|
1105
|
+
return null;
|
|
1106
|
+
} catch (error) {
|
|
1107
|
+
return fail(
|
|
1108
|
+
errorStatus(error, 500),
|
|
1109
|
+
errorMessage(error, "Pre-settlement validation failed"),
|
|
1110
|
+
scope.meta,
|
|
1111
|
+
scope.pluginCtx,
|
|
1112
|
+
scope.parsedBody
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
async function runSettlementError(scope, error, phase) {
|
|
1117
|
+
const hook = routeEntry.settlement?.onSettlementError;
|
|
1118
|
+
if (!hook) return;
|
|
1119
|
+
try {
|
|
1120
|
+
await hook({
|
|
1121
|
+
...settlementContext(scope),
|
|
1122
|
+
error,
|
|
1123
|
+
phase
|
|
1124
|
+
});
|
|
1125
|
+
} catch (hookError) {
|
|
1126
|
+
const message = errorMessage(hookError, "Settlement error hook failed");
|
|
1127
|
+
console.error(`[router] ${routeEntry.key}: onSettlementError failed: ${message}`);
|
|
1128
|
+
firePluginHook(deps.plugin, "onAlert", scope.pluginCtx, {
|
|
1129
|
+
level: "error",
|
|
1130
|
+
message: `Settlement error hook failed: ${message}`,
|
|
1131
|
+
route: routeEntry.key
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
async function runAfterSettle(scope) {
|
|
1136
|
+
const hook = routeEntry.settlement?.afterSettle;
|
|
1137
|
+
if (!hook) return;
|
|
1138
|
+
try {
|
|
1139
|
+
await hook(settlementContext(scope));
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
const message = errorMessage(error, "Post-settlement hook failed");
|
|
1142
|
+
console.error(`[router] ${routeEntry.key}: afterSettle failed: ${message}`);
|
|
1143
|
+
firePluginHook(deps.plugin, "onAlert", scope.pluginCtx, {
|
|
1144
|
+
level: "error",
|
|
1145
|
+
message: `Post-settlement hook failed: ${message}`,
|
|
1146
|
+
route: routeEntry.key
|
|
1147
|
+
});
|
|
1148
|
+
await runSettlementError(scope, error, "afterSettle");
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
async function runSettledHandlerError(scope, error = scope.handlerError ?? handlerFailureError(scope.response)) {
|
|
1152
|
+
const hook = routeEntry.settlement?.onSettledHandlerError;
|
|
1153
|
+
if (!hook) return;
|
|
1154
|
+
try {
|
|
1155
|
+
await hook({
|
|
1156
|
+
...settlementContext(scope),
|
|
1157
|
+
error
|
|
1158
|
+
});
|
|
1159
|
+
} catch (hookError) {
|
|
1160
|
+
const message = errorMessage(hookError, "Settled handler error hook failed");
|
|
1161
|
+
console.error(`[router] ${routeEntry.key}: onSettledHandlerError failed: ${message}`);
|
|
1162
|
+
firePluginHook(deps.plugin, "onAlert", scope.pluginCtx, {
|
|
1163
|
+
level: "error",
|
|
1164
|
+
message: `Settled handler error hook failed: ${message}`,
|
|
1165
|
+
route: routeEntry.key
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1064
1169
|
return async (request) => {
|
|
1065
1170
|
await deps.initPromise;
|
|
1066
1171
|
const meta = buildMeta(request, routeEntry);
|
|
@@ -1316,18 +1421,9 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1316
1421
|
return fail(status, message, meta, pluginCtx, body.data);
|
|
1317
1422
|
}
|
|
1318
1423
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
} catch (err) {
|
|
1323
|
-
return fail(
|
|
1324
|
-
err.status ?? 500,
|
|
1325
|
-
err instanceof Error ? err.message : "Price resolution failed",
|
|
1326
|
-
meta,
|
|
1327
|
-
pluginCtx,
|
|
1328
|
-
body.data
|
|
1329
|
-
);
|
|
1330
|
-
}
|
|
1424
|
+
const priceResult = await resolveDynamicPrice(body.data, routeEntry, deps, pluginCtx, meta);
|
|
1425
|
+
if ("error" in priceResult) return priceResult.error;
|
|
1426
|
+
const price = priceResult.price;
|
|
1331
1427
|
if (!routeEntry.protocols.includes(protocol)) {
|
|
1332
1428
|
const accepted = routeEntry.protocols.join(", ") || "none";
|
|
1333
1429
|
console.warn(
|
|
@@ -1381,7 +1477,7 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1381
1477
|
amount: price,
|
|
1382
1478
|
network: matchedNetwork
|
|
1383
1479
|
});
|
|
1384
|
-
const { response, rawResult } = await invoke(
|
|
1480
|
+
const { response, rawResult, handlerError } = await invoke(
|
|
1385
1481
|
request,
|
|
1386
1482
|
meta,
|
|
1387
1483
|
pluginCtx,
|
|
@@ -1390,7 +1486,21 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1390
1486
|
body.data,
|
|
1391
1487
|
payment
|
|
1392
1488
|
);
|
|
1489
|
+
const settleScope = {
|
|
1490
|
+
request,
|
|
1491
|
+
meta,
|
|
1492
|
+
pluginCtx,
|
|
1493
|
+
wallet,
|
|
1494
|
+
account,
|
|
1495
|
+
parsedBody: body.data,
|
|
1496
|
+
payment,
|
|
1497
|
+
response,
|
|
1498
|
+
rawResult,
|
|
1499
|
+
handlerError
|
|
1500
|
+
};
|
|
1393
1501
|
if (response.status < 400) {
|
|
1502
|
+
const validationFailure = await runBeforeSettle(settleScope);
|
|
1503
|
+
if (validationFailure) return validationFailure;
|
|
1394
1504
|
try {
|
|
1395
1505
|
const settle = await settleX402Payment(
|
|
1396
1506
|
deps.x402Server,
|
|
@@ -1416,13 +1526,21 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1416
1526
|
}
|
|
1417
1527
|
response.headers.set("PAYMENT-RESPONSE", settle.encoded);
|
|
1418
1528
|
response.headers.set("Cache-Control", "private");
|
|
1529
|
+
const transaction = String(settle.result?.transaction ?? "");
|
|
1530
|
+
const settledPayment = {
|
|
1531
|
+
...payment,
|
|
1532
|
+
status: "settled",
|
|
1533
|
+
...transaction ? { transaction } : {}
|
|
1534
|
+
};
|
|
1419
1535
|
firePluginHook(deps.plugin, "onPaymentSettled", pluginCtx, {
|
|
1420
1536
|
protocol: "x402",
|
|
1421
|
-
payer:
|
|
1422
|
-
transaction
|
|
1537
|
+
payer: wallet,
|
|
1538
|
+
transaction,
|
|
1423
1539
|
network: matchedNetwork
|
|
1424
1540
|
});
|
|
1541
|
+
await runAfterSettle({ ...settleScope, payment: settledPayment });
|
|
1425
1542
|
} catch (err) {
|
|
1543
|
+
await runSettlementError(settleScope, err, "settle");
|
|
1426
1544
|
const errObj = err;
|
|
1427
1545
|
console.error("Settlement failed", {
|
|
1428
1546
|
message: err instanceof Error ? err.message : String(err),
|
|
@@ -1481,27 +1599,44 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1481
1599
|
amount: price,
|
|
1482
1600
|
network: "tempo:4217"
|
|
1483
1601
|
});
|
|
1484
|
-
const
|
|
1602
|
+
const mppRecipient2 = deps.mppRecipient ?? deps.payeeAddress;
|
|
1603
|
+
const payment2 = {
|
|
1604
|
+
protocol: "mpp",
|
|
1605
|
+
status: "verified",
|
|
1606
|
+
payer: wallet,
|
|
1607
|
+
amount: price,
|
|
1608
|
+
network: "tempo:4217",
|
|
1609
|
+
...mppRecipient2 ? { recipient: mppRecipient2 } : {}
|
|
1610
|
+
};
|
|
1611
|
+
const { response: response2, rawResult: rawResult2, handlerError: handlerError2 } = await invoke(
|
|
1485
1612
|
request,
|
|
1486
1613
|
meta,
|
|
1487
1614
|
pluginCtx,
|
|
1488
1615
|
wallet,
|
|
1489
1616
|
account,
|
|
1490
1617
|
body.data,
|
|
1491
|
-
|
|
1492
|
-
protocol: "mpp",
|
|
1493
|
-
status: "verified",
|
|
1494
|
-
payer: wallet,
|
|
1495
|
-
amount: price,
|
|
1496
|
-
network: "tempo:4217",
|
|
1497
|
-
recipient: deps.payeeAddress
|
|
1498
|
-
}
|
|
1618
|
+
payment2
|
|
1499
1619
|
);
|
|
1620
|
+
const settleScope2 = {
|
|
1621
|
+
request,
|
|
1622
|
+
meta,
|
|
1623
|
+
pluginCtx,
|
|
1624
|
+
wallet,
|
|
1625
|
+
account,
|
|
1626
|
+
parsedBody: body.data,
|
|
1627
|
+
payment: payment2,
|
|
1628
|
+
response: response2,
|
|
1629
|
+
rawResult: rawResult2,
|
|
1630
|
+
handlerError: handlerError2
|
|
1631
|
+
};
|
|
1500
1632
|
if (response2.status < 400) {
|
|
1633
|
+
const validationFailure = await runBeforeSettle(settleScope2);
|
|
1634
|
+
if (validationFailure) return validationFailure;
|
|
1501
1635
|
let mppResult2;
|
|
1502
1636
|
try {
|
|
1503
1637
|
mppResult2 = await deps.mppx.charge({ amount: price })(request);
|
|
1504
1638
|
} catch (err) {
|
|
1639
|
+
await runSettlementError(settleScope2, err, "settle");
|
|
1505
1640
|
const message = err instanceof Error ? err.message : String(err);
|
|
1506
1641
|
console.error(
|
|
1507
1642
|
`[router] ${routeEntry.key}: MPP broadcast failed after handler: ${message}`
|
|
@@ -1530,6 +1665,13 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1530
1665
|
} catch {
|
|
1531
1666
|
}
|
|
1532
1667
|
const detail = rejectReason || "transaction reverted on-chain after handler execution";
|
|
1668
|
+
const settlementError = Object.assign(new Error(detail), {
|
|
1669
|
+
status: 402,
|
|
1670
|
+
detail,
|
|
1671
|
+
mppResult: mppResult2,
|
|
1672
|
+
challenge: mppResult2.challenge
|
|
1673
|
+
});
|
|
1674
|
+
await runSettlementError(settleScope2, settlementError, "settle");
|
|
1533
1675
|
console.error(
|
|
1534
1676
|
`[router] ${routeEntry.key}: MPP payment failed after handler \u2014 ${detail}`
|
|
1535
1677
|
);
|
|
@@ -1567,6 +1709,17 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1567
1709
|
transaction: txHash2,
|
|
1568
1710
|
network: "tempo:4217"
|
|
1569
1711
|
});
|
|
1712
|
+
const settledPayment = {
|
|
1713
|
+
...payment2,
|
|
1714
|
+
status: "settled",
|
|
1715
|
+
...txHash2 ? { transaction: txHash2 } : {},
|
|
1716
|
+
...receiptHeader2 ? { receipt: receiptHeader2 } : {}
|
|
1717
|
+
};
|
|
1718
|
+
await runAfterSettle({
|
|
1719
|
+
...settleScope2,
|
|
1720
|
+
payment: settledPayment,
|
|
1721
|
+
response: receiptResponse
|
|
1722
|
+
});
|
|
1570
1723
|
finalize(receiptResponse, rawResult2, meta, pluginCtx, body.data);
|
|
1571
1724
|
return receiptResponse;
|
|
1572
1725
|
}
|
|
@@ -1622,24 +1775,38 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1622
1775
|
amount: price,
|
|
1623
1776
|
network: "tempo:4217"
|
|
1624
1777
|
});
|
|
1625
|
-
const
|
|
1778
|
+
const mppRecipient = deps.mppRecipient ?? deps.payeeAddress;
|
|
1779
|
+
const payment = {
|
|
1780
|
+
protocol: "mpp",
|
|
1781
|
+
status: "settled",
|
|
1782
|
+
payer: wallet,
|
|
1783
|
+
amount: price,
|
|
1784
|
+
network: "tempo:4217",
|
|
1785
|
+
...mppRecipient ? { recipient: mppRecipient } : {},
|
|
1786
|
+
...txHash ? { transaction: txHash } : {},
|
|
1787
|
+
...receiptHeader ? { receipt: receiptHeader } : {}
|
|
1788
|
+
};
|
|
1789
|
+
const { response, rawResult, handlerError } = await invoke(
|
|
1626
1790
|
request,
|
|
1627
1791
|
meta,
|
|
1628
1792
|
pluginCtx,
|
|
1629
1793
|
wallet,
|
|
1630
1794
|
account,
|
|
1631
1795
|
body.data,
|
|
1632
|
-
|
|
1633
|
-
protocol: "mpp",
|
|
1634
|
-
status: "settled",
|
|
1635
|
-
payer: wallet,
|
|
1636
|
-
amount: price,
|
|
1637
|
-
network: "tempo:4217",
|
|
1638
|
-
recipient: deps.payeeAddress,
|
|
1639
|
-
...txHash ? { transaction: txHash } : {},
|
|
1640
|
-
...receiptHeader ? { receipt: receiptHeader } : {}
|
|
1641
|
-
}
|
|
1796
|
+
payment
|
|
1642
1797
|
);
|
|
1798
|
+
const settleScope = {
|
|
1799
|
+
request,
|
|
1800
|
+
meta,
|
|
1801
|
+
pluginCtx,
|
|
1802
|
+
wallet,
|
|
1803
|
+
account,
|
|
1804
|
+
parsedBody: body.data,
|
|
1805
|
+
payment,
|
|
1806
|
+
response,
|
|
1807
|
+
rawResult,
|
|
1808
|
+
handlerError
|
|
1809
|
+
};
|
|
1643
1810
|
if (response.status < 400) {
|
|
1644
1811
|
if (routeEntry.siwxEnabled) {
|
|
1645
1812
|
try {
|
|
@@ -1660,9 +1827,11 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1660
1827
|
transaction: txHash,
|
|
1661
1828
|
network: "tempo:4217"
|
|
1662
1829
|
});
|
|
1830
|
+
await runAfterSettle({ ...settleScope, response: receiptResponse });
|
|
1663
1831
|
finalize(receiptResponse, rawResult, meta, pluginCtx, body.data);
|
|
1664
1832
|
return receiptResponse;
|
|
1665
1833
|
}
|
|
1834
|
+
await runSettledHandlerError(settleScope);
|
|
1666
1835
|
finalize(response, rawResult, meta, pluginCtx, body.data);
|
|
1667
1836
|
return response;
|
|
1668
1837
|
}
|
|
@@ -1735,9 +1904,10 @@ async function resolveDynamicPrice(bodyData, routeEntry, deps, pluginCtx, meta)
|
|
|
1735
1904
|
});
|
|
1736
1905
|
return { price: routeEntry.maxPrice };
|
|
1737
1906
|
} else {
|
|
1907
|
+
const message = errorMessage(err, "Price calculation failed");
|
|
1738
1908
|
const errorResponse = NextResponse2.json(
|
|
1739
|
-
{ success: false, error:
|
|
1740
|
-
{ status: 500 }
|
|
1909
|
+
{ success: false, error: message },
|
|
1910
|
+
{ status: errorStatus(err, 500) }
|
|
1741
1911
|
);
|
|
1742
1912
|
firePluginResponse(deps, pluginCtx, meta, errorResponse);
|
|
1743
1913
|
return { error: errorResponse };
|
|
@@ -1910,22 +2080,13 @@ function fireProviderQuota(routeEntry, response, handlerResult, deps, pluginCtx)
|
|
|
1910
2080
|
|
|
1911
2081
|
// src/validate-examples.ts
|
|
1912
2082
|
function validateExamples(key, bodySchema, querySchema, outputSchema, inputExample, hasInputExample, outputExample, hasOutputExample) {
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
);
|
|
1917
|
-
}
|
|
1918
|
-
if (querySchema && !hasInputExample) {
|
|
1919
|
-
throw new Error(
|
|
1920
|
-
`route '${key}': .query() requires a matching .inputExample() \u2014 the bazaar discovery extension needs a conforming sample query to advertise.`
|
|
1921
|
-
);
|
|
2083
|
+
const inputSchema = bodySchema ?? querySchema;
|
|
2084
|
+
if (hasInputExample && !inputSchema) {
|
|
2085
|
+
throw new Error(`route '${key}': .inputExample() requires .body() or .query()`);
|
|
1922
2086
|
}
|
|
1923
|
-
if (
|
|
1924
|
-
throw new Error(
|
|
1925
|
-
`route '${key}': .output() requires a matching .outputExample() \u2014 the bazaar discovery extension needs a conforming sample response to advertise.`
|
|
1926
|
-
);
|
|
2087
|
+
if (hasOutputExample && !outputSchema) {
|
|
2088
|
+
throw new Error(`route '${key}': .outputExample() requires .output()`);
|
|
1927
2089
|
}
|
|
1928
|
-
const inputSchema = bodySchema ?? querySchema;
|
|
1929
2090
|
if (inputSchema && hasInputExample) {
|
|
1930
2091
|
const result = inputSchema.safeParse(inputExample);
|
|
1931
2092
|
if (!result.success) {
|
|
@@ -1999,6 +2160,8 @@ var RouteBuilder = class {
|
|
|
1999
2160
|
/** @internal */
|
|
2000
2161
|
_validateFn;
|
|
2001
2162
|
/** @internal */
|
|
2163
|
+
_settlement;
|
|
2164
|
+
/** @internal */
|
|
2002
2165
|
_mppInfo;
|
|
2003
2166
|
constructor(key, registry, deps) {
|
|
2004
2167
|
this._key = key;
|
|
@@ -2012,11 +2175,21 @@ var RouteBuilder = class {
|
|
|
2012
2175
|
return next;
|
|
2013
2176
|
}
|
|
2014
2177
|
paid(pricing, options) {
|
|
2178
|
+
if (this._authMode === "unprotected") {
|
|
2179
|
+
throw new Error(
|
|
2180
|
+
`route '${this._key}': Cannot combine .unprotected() and .paid() on the same route.`
|
|
2181
|
+
);
|
|
2182
|
+
}
|
|
2183
|
+
if (this._pricing !== void 0) {
|
|
2184
|
+
throw new Error(
|
|
2185
|
+
`route '${this._key}': Cannot call .paid() more than once on the same route.`
|
|
2186
|
+
);
|
|
2187
|
+
}
|
|
2015
2188
|
const next = this.fork();
|
|
2016
2189
|
next._authMode = "paid";
|
|
2017
2190
|
next._pricing = pricing;
|
|
2018
2191
|
if (options?.protocols) {
|
|
2019
|
-
next._protocols = options.protocols;
|
|
2192
|
+
next._protocols = [...options.protocols];
|
|
2020
2193
|
} else if (next._protocols.length === 0) {
|
|
2021
2194
|
next._protocols = ["x402"];
|
|
2022
2195
|
}
|
|
@@ -2081,6 +2254,16 @@ var RouteBuilder = class {
|
|
|
2081
2254
|
return next;
|
|
2082
2255
|
}
|
|
2083
2256
|
unprotected() {
|
|
2257
|
+
if (this._authMode && this._authMode !== "unprotected") {
|
|
2258
|
+
throw new Error(
|
|
2259
|
+
`route '${this._key}': Cannot combine .unprotected() and .${this._authMode}() on the same route.`
|
|
2260
|
+
);
|
|
2261
|
+
}
|
|
2262
|
+
if (this._pricing) {
|
|
2263
|
+
throw new Error(
|
|
2264
|
+
`route '${this._key}': Cannot combine .unprotected() and .paid() on the same route.`
|
|
2265
|
+
);
|
|
2266
|
+
}
|
|
2084
2267
|
const next = this.fork();
|
|
2085
2268
|
next._authMode = "unprotected";
|
|
2086
2269
|
next._protocols = [];
|
|
@@ -2095,32 +2278,43 @@ var RouteBuilder = class {
|
|
|
2095
2278
|
next._providerConfig = config ?? {};
|
|
2096
2279
|
return next;
|
|
2097
2280
|
}
|
|
2098
|
-
|
|
2099
|
-
// Schema methods
|
|
2100
|
-
// -------------------------------------------------------------------------
|
|
2101
|
-
body(schema) {
|
|
2281
|
+
body(schema, example) {
|
|
2102
2282
|
const next = this.fork();
|
|
2103
2283
|
next._bodySchema = schema;
|
|
2284
|
+
if (example !== void 0) {
|
|
2285
|
+
next._inputExample = example;
|
|
2286
|
+
next._hasInputExample = true;
|
|
2287
|
+
}
|
|
2104
2288
|
return next;
|
|
2105
2289
|
}
|
|
2106
|
-
query(schema) {
|
|
2290
|
+
query(schema, example) {
|
|
2107
2291
|
const next = this.fork();
|
|
2108
2292
|
next._querySchema = schema;
|
|
2293
|
+
if (example !== void 0) {
|
|
2294
|
+
next._inputExample = example;
|
|
2295
|
+
next._hasInputExample = true;
|
|
2296
|
+
}
|
|
2109
2297
|
next._method = "GET";
|
|
2110
2298
|
return next;
|
|
2111
2299
|
}
|
|
2112
|
-
output(schema) {
|
|
2300
|
+
output(schema, example) {
|
|
2113
2301
|
const next = this.fork();
|
|
2114
2302
|
next._outputSchema = schema;
|
|
2303
|
+
if (example !== void 0) {
|
|
2304
|
+
next._outputExample = example;
|
|
2305
|
+
next._hasOutputExample = true;
|
|
2306
|
+
}
|
|
2115
2307
|
return next;
|
|
2116
2308
|
}
|
|
2117
2309
|
/**
|
|
2118
2310
|
* Provide a conforming example of the request input (body or query params).
|
|
2119
2311
|
*
|
|
2120
|
-
*
|
|
2121
|
-
*
|
|
2122
|
-
*
|
|
2123
|
-
*
|
|
2312
|
+
* Optional. When provided, the example is validated against the request schema
|
|
2313
|
+
* at route registration and embedded in the bazaar discovery extension so
|
|
2314
|
+
* indexers can advertise a working sample call.
|
|
2315
|
+
*
|
|
2316
|
+
* For the common case, pass the example directly to `.body(schema, example)` or
|
|
2317
|
+
* `.query(schema, example)` instead.
|
|
2124
2318
|
*
|
|
2125
2319
|
* @example
|
|
2126
2320
|
* ```ts
|
|
@@ -2140,10 +2334,11 @@ var RouteBuilder = class {
|
|
|
2140
2334
|
/**
|
|
2141
2335
|
* Provide a conforming example of the response output.
|
|
2142
2336
|
*
|
|
2143
|
-
*
|
|
2144
|
-
*
|
|
2145
|
-
*
|
|
2146
|
-
*
|
|
2337
|
+
* Optional. When provided, the example is validated against the output schema
|
|
2338
|
+
* at route registration and embedded in the bazaar discovery extension so
|
|
2339
|
+
* indexers can advertise the response shape.
|
|
2340
|
+
*
|
|
2341
|
+
* For the common case, pass the example directly to `.output(schema, example)` instead.
|
|
2147
2342
|
*
|
|
2148
2343
|
* Accepts any JSON value (objects, arrays, or primitives) — top-level array
|
|
2149
2344
|
* or primitive responses (e.g. `z.array(...)`) are supported alongside the
|
|
@@ -2216,15 +2411,39 @@ var RouteBuilder = class {
|
|
|
2216
2411
|
return next;
|
|
2217
2412
|
}
|
|
2218
2413
|
// -------------------------------------------------------------------------
|
|
2414
|
+
// Settlement lifecycle
|
|
2415
|
+
// -------------------------------------------------------------------------
|
|
2416
|
+
/**
|
|
2417
|
+
* Add route-specific settlement hooks.
|
|
2418
|
+
*
|
|
2419
|
+
* `beforeSettle` runs after a successful handler response but before
|
|
2420
|
+
* router-controlled settlement/broadcast, so it can still prevent the charge
|
|
2421
|
+
* for x402 and MPP transaction-payload flows. `afterSettle` runs after
|
|
2422
|
+
* settlement and is intended for durable ledgers or app-owned refund queues.
|
|
2423
|
+
*/
|
|
2424
|
+
settlement(lifecycle) {
|
|
2425
|
+
const next = this.fork();
|
|
2426
|
+
next._settlement = lifecycle;
|
|
2427
|
+
return next;
|
|
2428
|
+
}
|
|
2429
|
+
// -------------------------------------------------------------------------
|
|
2219
2430
|
// Terminal method
|
|
2220
2431
|
// -------------------------------------------------------------------------
|
|
2221
2432
|
handler(fn) {
|
|
2222
2433
|
const handlerFn = fn;
|
|
2434
|
+
if (!this._authMode) {
|
|
2435
|
+
throw new Error(
|
|
2436
|
+
`route '${this._key}': Select an auth mode: .paid(pricing), .siwx(), .apiKey(resolver), or .unprotected()`
|
|
2437
|
+
);
|
|
2438
|
+
}
|
|
2223
2439
|
if (this._validateFn && !this._bodySchema) {
|
|
2224
2440
|
throw new Error(
|
|
2225
2441
|
`route '${this._key}': .validate() requires .body() \u2014 validation runs on parsed body`
|
|
2226
2442
|
);
|
|
2227
2443
|
}
|
|
2444
|
+
if (this._settlement && !this._pricing) {
|
|
2445
|
+
throw new Error(`route '${this._key}': .settlement() requires a paid route`);
|
|
2446
|
+
}
|
|
2228
2447
|
validateExamples(
|
|
2229
2448
|
this._key,
|
|
2230
2449
|
this._bodySchema,
|
|
@@ -2256,6 +2475,7 @@ var RouteBuilder = class {
|
|
|
2256
2475
|
providerName: this._providerName,
|
|
2257
2476
|
providerConfig: this._providerConfig,
|
|
2258
2477
|
validateFn: this._validateFn,
|
|
2478
|
+
settlement: this._settlement,
|
|
2259
2479
|
mppInfo: this._mppInfo
|
|
2260
2480
|
};
|
|
2261
2481
|
this._registry.register(entry);
|
|
@@ -2391,6 +2611,7 @@ function toDiscoveryResource(method, url, mode) {
|
|
|
2391
2611
|
}
|
|
2392
2612
|
|
|
2393
2613
|
// src/discovery/openapi.ts
|
|
2614
|
+
init_constants();
|
|
2394
2615
|
import { NextResponse as NextResponse4 } from "next/server";
|
|
2395
2616
|
function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
|
|
2396
2617
|
const normalizedBase = baseUrl.replace(/\/+$/, "");
|
|
@@ -2533,7 +2754,7 @@ function toProtocolObject(protocol, mppInfo) {
|
|
|
2533
2754
|
mpp: {
|
|
2534
2755
|
method: mppInfo?.method ?? "tempo",
|
|
2535
2756
|
intent: mppInfo?.intent ?? "charge",
|
|
2536
|
-
currency: mppInfo?.currency ??
|
|
2757
|
+
currency: mppInfo?.currency ?? TEMPO_USDC_CURRENCY
|
|
2537
2758
|
}
|
|
2538
2759
|
};
|
|
2539
2760
|
}
|
|
@@ -2648,6 +2869,8 @@ function mppFromEnv(env, options = {}) {
|
|
|
2648
2869
|
const secretKey = env.MPP_SECRET_KEY;
|
|
2649
2870
|
const currency = env.MPP_CURRENCY;
|
|
2650
2871
|
const rpcUrl = env.TEMPO_RPC_URL;
|
|
2872
|
+
const feePayerKey = options.feePayerKey ?? env.MPP_FEE_PAYER_KEY;
|
|
2873
|
+
const feePayerKeySource = options.feePayerKey !== void 0 ? "feePayerKey" : "MPP_FEE_PAYER_KEY";
|
|
2651
2874
|
const hasAnyMppEnv = Boolean(secretKey || currency || rpcUrl || options.require);
|
|
2652
2875
|
if (!hasAnyMppEnv) return void 0;
|
|
2653
2876
|
const missing = [
|
|
@@ -2658,12 +2881,21 @@ function mppFromEnv(env, options = {}) {
|
|
|
2658
2881
|
if (missing.length > 0) {
|
|
2659
2882
|
throw new Error(`MPP env is incomplete. Missing: ${missing.join(", ")}`);
|
|
2660
2883
|
}
|
|
2884
|
+
if (!isEvmAddress(currency)) {
|
|
2885
|
+
throw new Error("MPP_CURRENCY must be a 0x-prefixed 20-byte Tempo currency address");
|
|
2886
|
+
}
|
|
2887
|
+
if (options.recipient && !isEvmAddress(options.recipient)) {
|
|
2888
|
+
throw new Error("MPP recipient must be a 0x-prefixed EVM address");
|
|
2889
|
+
}
|
|
2890
|
+
if (feePayerKey && !isEvmPrivateKey(feePayerKey)) {
|
|
2891
|
+
throw new Error(`${feePayerKeySource} must be a 0x-prefixed 32-byte EVM private key`);
|
|
2892
|
+
}
|
|
2661
2893
|
return {
|
|
2662
2894
|
secretKey,
|
|
2663
2895
|
currency,
|
|
2664
2896
|
rpcUrl,
|
|
2665
2897
|
...options.recipient ? { recipient: options.recipient } : {},
|
|
2666
|
-
...
|
|
2898
|
+
...feePayerKey ? { feePayerKey } : {},
|
|
2667
2899
|
...options.useDefaultStore !== void 0 ? { useDefaultStore: options.useDefaultStore } : {}
|
|
2668
2900
|
};
|
|
2669
2901
|
}
|
|
@@ -2801,13 +3033,26 @@ function validateMppConfig(config, env) {
|
|
|
2801
3033
|
protocol: "mpp",
|
|
2802
3034
|
message: "MPP requires currency. Set MPP_CURRENCY or pass mpp.currency."
|
|
2803
3035
|
});
|
|
3036
|
+
} else if (!isEvmAddress(mpp.currency)) {
|
|
3037
|
+
issues.push({
|
|
3038
|
+
code: "invalid_mpp_currency",
|
|
3039
|
+
protocol: "mpp",
|
|
3040
|
+
message: "MPP currency must be a 0x-prefixed 20-byte Tempo currency address. Use TEMPO_USDC_CURRENCY for Tempo USDC."
|
|
3041
|
+
});
|
|
2804
3042
|
}
|
|
2805
|
-
|
|
3043
|
+
const mppRecipient = mpp.recipient ?? config.payeeAddress;
|
|
3044
|
+
if (!mppRecipient) {
|
|
2806
3045
|
issues.push({
|
|
2807
3046
|
code: "missing_mpp_recipient",
|
|
2808
3047
|
protocol: "mpp",
|
|
2809
3048
|
message: "MPP requires a recipient address. Set mpp.recipient or payeeAddress in your router config."
|
|
2810
3049
|
});
|
|
3050
|
+
} else if (!isEvmAddress(mppRecipient)) {
|
|
3051
|
+
issues.push({
|
|
3052
|
+
code: "invalid_mpp_recipient",
|
|
3053
|
+
protocol: "mpp",
|
|
3054
|
+
message: "MPP recipient must be a 0x-prefixed EVM address. Solana recipients require x402."
|
|
3055
|
+
});
|
|
2811
3056
|
}
|
|
2812
3057
|
const placeholder = findPlaceholderPayee([mpp.recipient, config.payeeAddress]);
|
|
2813
3058
|
if (placeholder) {
|
|
@@ -2824,6 +3069,13 @@ function validateMppConfig(config, env) {
|
|
|
2824
3069
|
message: "MPP requires an authenticated Tempo RPC URL. Set TEMPO_RPC_URL env var or pass rpcUrl in the mpp config object."
|
|
2825
3070
|
});
|
|
2826
3071
|
}
|
|
3072
|
+
if (mpp.feePayerKey && !isEvmPrivateKey(mpp.feePayerKey)) {
|
|
3073
|
+
issues.push({
|
|
3074
|
+
code: "invalid_mpp_fee_payer_key",
|
|
3075
|
+
protocol: "mpp",
|
|
3076
|
+
message: "MPP feePayerKey must be a 0x-prefixed 32-byte EVM private key."
|
|
3077
|
+
});
|
|
3078
|
+
}
|
|
2827
3079
|
if (mpp.useDefaultStore && !mpp.store && (!env.KV_REST_API_URL || !env.KV_REST_API_TOKEN)) {
|
|
2828
3080
|
issues.push({
|
|
2829
3081
|
code: "missing_mpp_default_store_env",
|
|
@@ -2841,8 +3093,14 @@ function usesDefaultEvmFacilitator(config) {
|
|
|
2841
3093
|
function isSupportedX402Network(network) {
|
|
2842
3094
|
return isEvmNetwork(network) || isSolanaNetwork(network);
|
|
2843
3095
|
}
|
|
3096
|
+
function isEvmAddress(value) {
|
|
3097
|
+
return /^0x[a-fA-F0-9]{40}$/.test(value);
|
|
3098
|
+
}
|
|
3099
|
+
function isEvmPrivateKey(value) {
|
|
3100
|
+
return /^0x[a-fA-F0-9]{64}$/.test(value);
|
|
3101
|
+
}
|
|
2844
3102
|
function findPlaceholderPayee(values) {
|
|
2845
|
-
return values.find((value) => value
|
|
3103
|
+
return values.find((value) => value !== void 0 && /^0x0{40}$/i.test(value)) ?? null;
|
|
2846
3104
|
}
|
|
2847
3105
|
|
|
2848
3106
|
// src/index.ts
|
|
@@ -2891,6 +3149,7 @@ function createRouter(config) {
|
|
|
2891
3149
|
nonceStore,
|
|
2892
3150
|
entitlementStore,
|
|
2893
3151
|
payeeAddress: config.payeeAddress ?? "",
|
|
3152
|
+
mppRecipient: config.mpp?.recipient ?? config.payeeAddress,
|
|
2894
3153
|
network,
|
|
2895
3154
|
x402FacilitatorsByNetwork: void 0,
|
|
2896
3155
|
x402Accepts,
|