@piprail/sdk 1.5.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +75 -0
- package/DISCOVERY.md +420 -0
- package/ERRORS.md +9 -0
- package/README.md +75 -4
- package/STANDARDS.md +4 -4
- package/dist/index.cjs +611 -28
- package/dist/index.d.cts +376 -4
- package/dist/index.d.ts +376 -4
- package/dist/index.js +588 -5
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -737,6 +737,17 @@ function makeEvmNetwork(resolved) {
|
|
|
737
737
|
async recipientReady() {
|
|
738
738
|
return { ready: "n/a" };
|
|
739
739
|
},
|
|
740
|
+
// Discovery only (ownership proofs / SIWX) — never the payment path. Signs
|
|
741
|
+
// through the wallet client so it works for both { privateKey } (local) and
|
|
742
|
+
// bring-your-own { walletClient } (JSON-RPC) accounts. eip191 → recoverable
|
|
743
|
+
// with viem's recoverMessageAddress (how x402scan verifies origin ownership).
|
|
744
|
+
discoverySigner(wallet) {
|
|
745
|
+
const a = wallet._native;
|
|
746
|
+
return {
|
|
747
|
+
address: a.account.address,
|
|
748
|
+
signMessage: (message) => a.walletClient.signMessage({ account: a.account, message })
|
|
749
|
+
};
|
|
750
|
+
},
|
|
740
751
|
async verify(ref, accept) {
|
|
741
752
|
return verifyEvm({
|
|
742
753
|
publicClient,
|
|
@@ -888,6 +899,334 @@ async function resolveNetwork2(opts) {
|
|
|
888
899
|
return resolveNetwork(opts);
|
|
889
900
|
}
|
|
890
901
|
|
|
902
|
+
// src/indexes.ts
|
|
903
|
+
var BAZAAR_URL = "https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources";
|
|
904
|
+
var INDEX402_SEARCH = "https://402index.io/api/v1/services";
|
|
905
|
+
var INDEX402_REGISTER = "https://402index.io/api/v1/register";
|
|
906
|
+
var X402SCAN_REGISTER = "https://www.x402scan.com/api/x402/registry/register";
|
|
907
|
+
var USER_AGENT = "@piprail/sdk (+https://piprail.com)";
|
|
908
|
+
function clientHeaders(extra = {}) {
|
|
909
|
+
return { "user-agent": USER_AGENT, ...extra };
|
|
910
|
+
}
|
|
911
|
+
var SLUG_TO_CAIP2 = {
|
|
912
|
+
// EVM (the common index-reported slugs; others fall through to net.supports)
|
|
913
|
+
ethereum: "eip155:1",
|
|
914
|
+
base: "eip155:8453",
|
|
915
|
+
polygon: "eip155:137",
|
|
916
|
+
arbitrum: "eip155:42161",
|
|
917
|
+
optimism: "eip155:10",
|
|
918
|
+
avalanche: "eip155:43114",
|
|
919
|
+
bnb: "eip155:56",
|
|
920
|
+
bsc: "eip155:56",
|
|
921
|
+
// non-EVM families — values mirror each driver's bound caip2 exactly
|
|
922
|
+
solana: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
923
|
+
ton: "ton:-239",
|
|
924
|
+
tron: "tron:mainnet",
|
|
925
|
+
near: "near:mainnet",
|
|
926
|
+
sui: "sui:mainnet",
|
|
927
|
+
aptos: "aptos:1",
|
|
928
|
+
algorand: "algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73k",
|
|
929
|
+
stellar: "stellar:pubnet",
|
|
930
|
+
xrpl: "xrpl:0"
|
|
931
|
+
};
|
|
932
|
+
function normalizeNetwork(network) {
|
|
933
|
+
if (network.includes(":")) return network;
|
|
934
|
+
return _nullishCoalesce(SLUG_TO_CAIP2[network.toLowerCase()], () => ( network));
|
|
935
|
+
}
|
|
936
|
+
async function searchOpenIndexes(opts = {}) {
|
|
937
|
+
const sources = _nullishCoalesce(opts.sources, () => ( ["bazaar", "402index"]));
|
|
938
|
+
const limit = _nullishCoalesce(opts.limit, () => ( 20));
|
|
939
|
+
const results = await Promise.all(
|
|
940
|
+
sources.map((source) => {
|
|
941
|
+
if (source === "bazaar") return safeSearch(() => searchBazaar(opts.query, limit, opts.signal));
|
|
942
|
+
if (source === "402index") return safeSearch(() => search402Index(opts.query, limit, opts.signal));
|
|
943
|
+
return Promise.resolve([]);
|
|
944
|
+
})
|
|
945
|
+
);
|
|
946
|
+
return dedupeByResource(results.flat());
|
|
947
|
+
}
|
|
948
|
+
async function safeSearch(run) {
|
|
949
|
+
try {
|
|
950
|
+
return await run();
|
|
951
|
+
} catch (e12) {
|
|
952
|
+
return [];
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
function dedupeByResource(items) {
|
|
956
|
+
const seen = /* @__PURE__ */ new Set();
|
|
957
|
+
const out = [];
|
|
958
|
+
for (const it of items) {
|
|
959
|
+
const key = it.resource;
|
|
960
|
+
if (!key || seen.has(key)) continue;
|
|
961
|
+
seen.add(key);
|
|
962
|
+
out.push(it);
|
|
963
|
+
}
|
|
964
|
+
return out;
|
|
965
|
+
}
|
|
966
|
+
async function searchBazaar(query, limit, signal) {
|
|
967
|
+
const res = await fetch(`${BAZAAR_URL}?limit=${encodeURIComponent(String(limit))}`, {
|
|
968
|
+
headers: clientHeaders({ accept: "application/json" }),
|
|
969
|
+
...signal ? { signal } : {}
|
|
970
|
+
});
|
|
971
|
+
if (!res.ok) return [];
|
|
972
|
+
const body = await res.json();
|
|
973
|
+
const items = Array.isArray(body.items) ? body.items : [];
|
|
974
|
+
const mapped = items.map(mapBazaarItem).filter((r) => r !== null);
|
|
975
|
+
return query ? mapped.filter((r) => matchesQuery(r, query)) : mapped;
|
|
976
|
+
}
|
|
977
|
+
function mapBazaarItem(raw) {
|
|
978
|
+
if (!raw || typeof raw !== "object") return null;
|
|
979
|
+
const o = raw;
|
|
980
|
+
const resource = pickString(o, "resource", "url", "endpoint");
|
|
981
|
+
if (!resource) return null;
|
|
982
|
+
const meta = o.metadata && typeof o.metadata === "object" ? o.metadata : {};
|
|
983
|
+
return {
|
|
984
|
+
resource,
|
|
985
|
+
source: "bazaar",
|
|
986
|
+
rails: mapRails(o.accepts),
|
|
987
|
+
...optionalString("name", pickString(meta, "name", "title")),
|
|
988
|
+
...optionalString("description", _nullishCoalesce(pickString(meta, "description"), () => ( pickString(o, "description")))),
|
|
989
|
+
...optionalString("category", pickString(meta, "category"))
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
async function search402Index(query, limit, signal) {
|
|
993
|
+
const qs = new URLSearchParams({ limit: String(limit) });
|
|
994
|
+
if (query) qs.set("q", query);
|
|
995
|
+
const res = await fetch(`${INDEX402_SEARCH}?${qs.toString()}`, {
|
|
996
|
+
headers: clientHeaders({ accept: "application/json" }),
|
|
997
|
+
...signal ? { signal } : {}
|
|
998
|
+
});
|
|
999
|
+
if (!res.ok) return [];
|
|
1000
|
+
const body = await res.json();
|
|
1001
|
+
const list = firstArray(body, "services", "results", "items", "data");
|
|
1002
|
+
return list.map(map402IndexItem).filter((r) => r !== null).filter((r) => r.rails.length > 0);
|
|
1003
|
+
}
|
|
1004
|
+
function map402IndexItem(raw) {
|
|
1005
|
+
if (!raw || typeof raw !== "object") return null;
|
|
1006
|
+
const o = raw;
|
|
1007
|
+
const resource = pickString(o, "url", "resource", "endpoint");
|
|
1008
|
+
if (!resource) return null;
|
|
1009
|
+
const protocol = (_nullishCoalesce(pickString(o, "protocol"), () => ( "x402"))).toLowerCase();
|
|
1010
|
+
if (protocol !== "x402") return null;
|
|
1011
|
+
const rails = Array.isArray(o.accepts) ? mapRails(o.accepts) : railFrom402IndexFields(o);
|
|
1012
|
+
const priceUsd = pickNumber(o, "price_usd", "priceUsd", "price");
|
|
1013
|
+
return {
|
|
1014
|
+
resource,
|
|
1015
|
+
source: "402index",
|
|
1016
|
+
rails,
|
|
1017
|
+
...priceUsd !== void 0 ? { priceUsd } : {},
|
|
1018
|
+
...optionalString("name", pickString(o, "name", "title")),
|
|
1019
|
+
...optionalString("description", pickString(o, "description")),
|
|
1020
|
+
...optionalString("category", pickString(o, "category", "tag"))
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
function railFrom402IndexFields(o) {
|
|
1024
|
+
const network = pickString(o, "payment_network", "network");
|
|
1025
|
+
const asset = pickString(o, "payment_asset", "asset", "token");
|
|
1026
|
+
if (!network && !asset) return [];
|
|
1027
|
+
return [
|
|
1028
|
+
{
|
|
1029
|
+
scheme: "exact",
|
|
1030
|
+
network: _nullishCoalesce(network, () => ( "unknown")),
|
|
1031
|
+
...asset ? { asset } : {},
|
|
1032
|
+
...optionalString("symbol", asset)
|
|
1033
|
+
}
|
|
1034
|
+
];
|
|
1035
|
+
}
|
|
1036
|
+
async function register402Index(input) {
|
|
1037
|
+
try {
|
|
1038
|
+
const payload = {
|
|
1039
|
+
url: input.url,
|
|
1040
|
+
name: _nullishCoalesce(input.name, () => ( hostOf(input.url))),
|
|
1041
|
+
protocol: "x402",
|
|
1042
|
+
...input.description ? { description: input.description } : {},
|
|
1043
|
+
...typeof input.priceUsd === "number" ? { price_usd: input.priceUsd } : {},
|
|
1044
|
+
...input.asset ? { payment_asset: input.asset } : {},
|
|
1045
|
+
...input.network ? { payment_network: input.network } : {},
|
|
1046
|
+
...input.method ? { http_method: input.method.toUpperCase() } : {},
|
|
1047
|
+
...input.attribution ? { via: "@piprail/sdk" } : {}
|
|
1048
|
+
};
|
|
1049
|
+
const res = await fetch(INDEX402_REGISTER, {
|
|
1050
|
+
method: "POST",
|
|
1051
|
+
headers: clientHeaders({ "content-type": "application/json", accept: "application/json" }),
|
|
1052
|
+
body: JSON.stringify(payload)
|
|
1053
|
+
});
|
|
1054
|
+
if (res.ok) {
|
|
1055
|
+
return { source: "402index", ok: true, status: res.status, detail: "Listed on 402 Index (searchable at 402index.io)." };
|
|
1056
|
+
}
|
|
1057
|
+
const why = await readIndexError(res);
|
|
1058
|
+
return {
|
|
1059
|
+
source: "402index",
|
|
1060
|
+
ok: false,
|
|
1061
|
+
status: res.status,
|
|
1062
|
+
detail: why ? `402 Index rejected it (HTTP ${res.status}): ${why}` : `402 Index returned HTTP ${res.status}.`
|
|
1063
|
+
};
|
|
1064
|
+
} catch (err) {
|
|
1065
|
+
return { source: "402index", ok: false, detail: errMsg(err) };
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
async function readIndexError(res) {
|
|
1069
|
+
try {
|
|
1070
|
+
const body = await res.json();
|
|
1071
|
+
const parts = [body.error, body.detail, body.message].filter(
|
|
1072
|
+
(p) => typeof p === "string" && p.length > 0
|
|
1073
|
+
);
|
|
1074
|
+
return parts.length ? [...new Set(parts)].join(" \u2014 ") : void 0;
|
|
1075
|
+
} catch (e13) {
|
|
1076
|
+
return void 0;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
async function registerX402Scan(input, signer) {
|
|
1080
|
+
try {
|
|
1081
|
+
const challengeRes = await fetch(X402SCAN_REGISTER, {
|
|
1082
|
+
method: "POST",
|
|
1083
|
+
headers: clientHeaders({ "content-type": "application/json", accept: "application/json" }),
|
|
1084
|
+
body: JSON.stringify({ url: input.url })
|
|
1085
|
+
});
|
|
1086
|
+
if (challengeRes.status !== 402) {
|
|
1087
|
+
return {
|
|
1088
|
+
source: "x402scan",
|
|
1089
|
+
ok: challengeRes.ok,
|
|
1090
|
+
status: challengeRes.status,
|
|
1091
|
+
detail: challengeRes.ok ? "Listed on x402scan." : `x402scan returned HTTP ${challengeRes.status} (expected a SIWX 402 challenge).`
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
const info = await readSiwxInfo(challengeRes);
|
|
1095
|
+
if (!info) {
|
|
1096
|
+
return { source: "x402scan", ok: false, status: 402, detail: "x402scan SIWX challenge was unparseable." };
|
|
1097
|
+
}
|
|
1098
|
+
const resolvedInfo = { ...info, issuedAt: _nullishCoalesce(info.issuedAt, () => ( (/* @__PURE__ */ new Date()).toISOString())) };
|
|
1099
|
+
const message = formatSiweMessage(resolvedInfo, signer.address);
|
|
1100
|
+
const signature = await signer.signMessage(message);
|
|
1101
|
+
const header = encodeBase642(
|
|
1102
|
+
JSON.stringify({ ...resolvedInfo, address: signer.address, type: "eip191", message, signature })
|
|
1103
|
+
);
|
|
1104
|
+
const res = await fetch(X402SCAN_REGISTER, {
|
|
1105
|
+
method: "POST",
|
|
1106
|
+
headers: clientHeaders({
|
|
1107
|
+
"content-type": "application/json",
|
|
1108
|
+
accept: "application/json",
|
|
1109
|
+
"sign-in-with-x": header
|
|
1110
|
+
}),
|
|
1111
|
+
body: JSON.stringify({ url: input.url })
|
|
1112
|
+
});
|
|
1113
|
+
if (res.ok) {
|
|
1114
|
+
return { source: "x402scan", ok: true, status: res.status, detail: "Listed on x402scan (SIWX)." };
|
|
1115
|
+
}
|
|
1116
|
+
const why = await readIndexError(res);
|
|
1117
|
+
return {
|
|
1118
|
+
source: "x402scan",
|
|
1119
|
+
ok: false,
|
|
1120
|
+
status: res.status,
|
|
1121
|
+
detail: why ? `x402scan rejected it (HTTP ${res.status}): ${why}` : `x402scan returned HTTP ${res.status} after signing.`
|
|
1122
|
+
};
|
|
1123
|
+
} catch (err) {
|
|
1124
|
+
return { source: "x402scan", ok: false, detail: errMsg(err) };
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
async function readSiwxInfo(res) {
|
|
1128
|
+
try {
|
|
1129
|
+
const body = await res.json();
|
|
1130
|
+
const ext = body.extensions;
|
|
1131
|
+
const siwx = _optionalChain([ext, 'optionalAccess', _2 => _2["sign-in-with-x"]]);
|
|
1132
|
+
const info = _nullishCoalesce(_optionalChain([siwx, 'optionalAccess', _3 => _3.info]), () => ( siwx));
|
|
1133
|
+
if (info && typeof info.domain === "string" && info.domain.length > 0 && typeof info.nonce === "string" && info.nonce.length > 0 && typeof info.uri === "string" && info.uri.length > 0) {
|
|
1134
|
+
return info;
|
|
1135
|
+
}
|
|
1136
|
+
return null;
|
|
1137
|
+
} catch (e14) {
|
|
1138
|
+
return null;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
function formatSiweMessage(info, address) {
|
|
1142
|
+
const chainId = info.chainId ? caip2ToChainId(info.chainId) : 1;
|
|
1143
|
+
const statement = info.statement && info.statement.trim() ? info.statement : void 0;
|
|
1144
|
+
const lines = [
|
|
1145
|
+
`${info.domain} wants you to sign in with your Ethereum account:`,
|
|
1146
|
+
address,
|
|
1147
|
+
"",
|
|
1148
|
+
...statement ? [statement, ""] : [""],
|
|
1149
|
+
`URI: ${info.uri}`,
|
|
1150
|
+
"Version: 1",
|
|
1151
|
+
`Chain ID: ${chainId}`,
|
|
1152
|
+
`Nonce: ${info.nonce}`,
|
|
1153
|
+
`Issued At: ${info.issuedAt}`,
|
|
1154
|
+
...info.expirationTime ? [`Expiration Time: ${info.expirationTime}`] : []
|
|
1155
|
+
];
|
|
1156
|
+
return lines.join("\n");
|
|
1157
|
+
}
|
|
1158
|
+
function caip2ToChainId(caip2) {
|
|
1159
|
+
const m = /^eip155:(\d+)$/.exec(caip2);
|
|
1160
|
+
const n = m ? Number(m[1]) : Number(caip2);
|
|
1161
|
+
return Number.isSafeInteger(n) && n > 0 ? n : 1;
|
|
1162
|
+
}
|
|
1163
|
+
function mapRails(accepts) {
|
|
1164
|
+
if (!Array.isArray(accepts)) return [];
|
|
1165
|
+
const out = [];
|
|
1166
|
+
for (const raw of accepts) {
|
|
1167
|
+
if (!raw || typeof raw !== "object") continue;
|
|
1168
|
+
const a = raw;
|
|
1169
|
+
const network = pickString(a, "network");
|
|
1170
|
+
if (!network) continue;
|
|
1171
|
+
const extra = a.extra && typeof a.extra === "object" ? a.extra : {};
|
|
1172
|
+
out.push({
|
|
1173
|
+
scheme: _nullishCoalesce(pickString(a, "scheme"), () => ( "exact")),
|
|
1174
|
+
network,
|
|
1175
|
+
...optionalString("asset", pickString(a, "asset")),
|
|
1176
|
+
...optionalString("amount", pickString(a, "amount", "maxAmountRequired")),
|
|
1177
|
+
...optionalString("payTo", pickString(a, "payTo")),
|
|
1178
|
+
...optionalString("symbol", pickString(extra, "symbol"))
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
return out;
|
|
1182
|
+
}
|
|
1183
|
+
function matchesQuery(r, query) {
|
|
1184
|
+
const q = query.toLowerCase();
|
|
1185
|
+
return r.resource.toLowerCase().includes(q) || (_nullishCoalesce(_optionalChain([r, 'access', _4 => _4.name, 'optionalAccess', _5 => _5.toLowerCase, 'call', _6 => _6(), 'access', _7 => _7.includes, 'call', _8 => _8(q)]), () => ( false))) || (_nullishCoalesce(_optionalChain([r, 'access', _9 => _9.description, 'optionalAccess', _10 => _10.toLowerCase, 'call', _11 => _11(), 'access', _12 => _12.includes, 'call', _13 => _13(q)]), () => ( false)));
|
|
1186
|
+
}
|
|
1187
|
+
function pickString(o, ...keys) {
|
|
1188
|
+
for (const k of keys) {
|
|
1189
|
+
const v = o[k];
|
|
1190
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
1191
|
+
}
|
|
1192
|
+
return void 0;
|
|
1193
|
+
}
|
|
1194
|
+
function pickNumber(o, ...keys) {
|
|
1195
|
+
for (const k of keys) {
|
|
1196
|
+
const v = o[k];
|
|
1197
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
1198
|
+
if (typeof v === "string" && /^\d+(\.\d+)?$/.test(v.trim())) {
|
|
1199
|
+
const n = Number(v.trim());
|
|
1200
|
+
if (Number.isFinite(n)) return n;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
return void 0;
|
|
1204
|
+
}
|
|
1205
|
+
function optionalString(field, value) {
|
|
1206
|
+
return value !== void 0 ? { [field]: value } : {};
|
|
1207
|
+
}
|
|
1208
|
+
function firstArray(o, ...keys) {
|
|
1209
|
+
for (const k of keys) {
|
|
1210
|
+
if (Array.isArray(o[k])) return o[k];
|
|
1211
|
+
}
|
|
1212
|
+
return Array.isArray(o) ? o : [];
|
|
1213
|
+
}
|
|
1214
|
+
function hostOf(url) {
|
|
1215
|
+
try {
|
|
1216
|
+
return new URL(url).hostname;
|
|
1217
|
+
} catch (e15) {
|
|
1218
|
+
return url;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
function errMsg(err) {
|
|
1222
|
+
return err instanceof Error ? err.message : String(err);
|
|
1223
|
+
}
|
|
1224
|
+
function encodeBase642(str) {
|
|
1225
|
+
if (typeof btoa === "function") return btoa(str);
|
|
1226
|
+
if (typeof Buffer !== "undefined") return Buffer.from(str, "utf8").toString("base64");
|
|
1227
|
+
throw new Error("No base64 encoder available in this runtime.");
|
|
1228
|
+
}
|
|
1229
|
+
|
|
891
1230
|
// src/policy.ts
|
|
892
1231
|
var ALLOW = { allowed: true };
|
|
893
1232
|
var deny = (reason) => ({ allowed: false, reason });
|
|
@@ -923,7 +1262,12 @@ function evaluatePolicy(intent, policy, spentForAssetBase) {
|
|
|
923
1262
|
}
|
|
924
1263
|
if (policy.tokens) {
|
|
925
1264
|
const sym = intent.recognized ? intent.symbol : void 0;
|
|
926
|
-
|
|
1265
|
+
const isNative = intent.asset === "native";
|
|
1266
|
+
const matches = policy.tokens.some((t) => {
|
|
1267
|
+
if (isNative && t.toUpperCase() === "NATIVE") return true;
|
|
1268
|
+
return sym ? t.toUpperCase() === sym.toUpperCase() : false;
|
|
1269
|
+
});
|
|
1270
|
+
if (!matches) {
|
|
927
1271
|
return deny(
|
|
928
1272
|
`token ${_nullishCoalesce(intent.symbol, () => ( intent.asset))} is not in the allowed set (policy.tokens).`
|
|
929
1273
|
);
|
|
@@ -976,7 +1320,7 @@ var SpendLedger = (_class = class {constructor() { _class.prototype.__init.call(
|
|
|
976
1320
|
}
|
|
977
1321
|
/** Running total (base units) already spent on this (network, asset). */
|
|
978
1322
|
totalFor(network, asset) {
|
|
979
|
-
return _nullishCoalesce(_optionalChain([this, 'access',
|
|
1323
|
+
return _nullishCoalesce(_optionalChain([this, 'access', _14 => _14.buckets, 'access', _15 => _15.get, 'call', _16 => _16(keyFor(network, asset)), 'optionalAccess', _17 => _17.total]), () => ( 0n));
|
|
980
1324
|
}
|
|
981
1325
|
/** An immutable snapshot of all spend so far. */
|
|
982
1326
|
summary() {
|
|
@@ -1025,7 +1369,7 @@ var PipRailClient = (_class2 = class {
|
|
|
1025
1369
|
safeEmit(event) {
|
|
1026
1370
|
try {
|
|
1027
1371
|
this.onEvent(event);
|
|
1028
|
-
} catch (
|
|
1372
|
+
} catch (e16) {
|
|
1029
1373
|
}
|
|
1030
1374
|
}
|
|
1031
1375
|
/** Auto-mount the chain's driver, resolve the network, and bind the wallet — once. */
|
|
@@ -1050,7 +1394,7 @@ var PipRailClient = (_class2 = class {
|
|
|
1050
1394
|
* as-is) or a plain object (serialised as JSON).
|
|
1051
1395
|
*/
|
|
1052
1396
|
post(url, body, init) {
|
|
1053
|
-
const headers = new Headers(_optionalChain([init, 'optionalAccess',
|
|
1397
|
+
const headers = new Headers(_optionalChain([init, 'optionalAccess', _18 => _18.headers]));
|
|
1054
1398
|
let payload;
|
|
1055
1399
|
if (body === void 0 || body === null) {
|
|
1056
1400
|
payload = void 0;
|
|
@@ -1081,7 +1425,7 @@ var PipRailClient = (_class2 = class {
|
|
|
1081
1425
|
* "0.05 USDC on Base, within budget → pay it." No funds move.
|
|
1082
1426
|
*/
|
|
1083
1427
|
async quote(url, init) {
|
|
1084
|
-
const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess',
|
|
1428
|
+
const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _19 => _19.method]), () => ( "GET")) });
|
|
1085
1429
|
if (res.status !== 402) return null;
|
|
1086
1430
|
const { quote } = await this.resolveChallenge(url, res);
|
|
1087
1431
|
return quote;
|
|
@@ -1100,7 +1444,7 @@ var PipRailClient = (_class2 = class {
|
|
|
1100
1444
|
* on Tron, where a USD₮ transfer can cost real TRX.
|
|
1101
1445
|
*/
|
|
1102
1446
|
async estimateCost(url, init) {
|
|
1103
|
-
const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess',
|
|
1447
|
+
const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _20 => _20.method]), () => ( "GET")) });
|
|
1104
1448
|
if (res.status !== 402) return null;
|
|
1105
1449
|
const { net, accept, quote } = await this.resolveChallenge(url, res);
|
|
1106
1450
|
const cost = await net.estimateCost(accept);
|
|
@@ -1131,7 +1475,7 @@ var PipRailClient = (_class2 = class {
|
|
|
1131
1475
|
* the plan yourself. No funds move.
|
|
1132
1476
|
*/
|
|
1133
1477
|
async planPayment(url, init) {
|
|
1134
|
-
const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess',
|
|
1478
|
+
const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _21 => _21.method]), () => ( "GET")) });
|
|
1135
1479
|
if (res.status !== 402) return null;
|
|
1136
1480
|
const challenge = await parseChallenge(res);
|
|
1137
1481
|
if (!challenge) {
|
|
@@ -1149,6 +1493,100 @@ var PipRailClient = (_class2 = class {
|
|
|
1149
1493
|
const plan = await this.planPayment(url, init);
|
|
1150
1494
|
return plan == null ? true : plan.payable;
|
|
1151
1495
|
}
|
|
1496
|
+
/* ------------------------- discovery (find + list) ------------------------- */
|
|
1497
|
+
/**
|
|
1498
|
+
* Find payable resources on the OPEN x402 indexes — WITHOUT paying. Reads the
|
|
1499
|
+
* free indexes (CDP Bazaar + 402 Index by default), merges + dedupes them, and
|
|
1500
|
+
* by default returns only resources payable on THIS client's chain
|
|
1501
|
+
* (`network: 'self'`). Each result carries its advertised `rails[]`; feed a
|
|
1502
|
+
* chosen `resource` straight into `quote()` → `planPayment()` → `fetch()`.
|
|
1503
|
+
*
|
|
1504
|
+
* Nothing PipRail-hosted: these are third-party open directories. Never throws
|
|
1505
|
+
* for a read problem — an index that's down or changed simply contributes
|
|
1506
|
+
* nothing. Honest caveat: index results are cross-scheme (mostly the
|
|
1507
|
+
* mainstream `exact` scheme); `fetch()` pays only `onchain-proof` rails
|
|
1508
|
+
* directly (pay `exact` resources with the experimental `drivers/evm/exact.ts`).
|
|
1509
|
+
*/
|
|
1510
|
+
async discover(opts = {}) {
|
|
1511
|
+
const found = await searchOpenIndexes({
|
|
1512
|
+
...opts.query !== void 0 ? { query: opts.query } : {},
|
|
1513
|
+
...opts.sources ? { sources: opts.sources } : {},
|
|
1514
|
+
...opts.limit !== void 0 ? { limit: opts.limit } : {}
|
|
1515
|
+
});
|
|
1516
|
+
const scope = _nullishCoalesce(opts.network, () => ( "self"));
|
|
1517
|
+
let out = found;
|
|
1518
|
+
if (scope === "self") {
|
|
1519
|
+
const { net } = await this.ensure();
|
|
1520
|
+
out = out.filter((r) => r.rails.some((rail) => railOnNetwork(rail, (n) => net.supports(n))));
|
|
1521
|
+
} else if (scope !== "any") {
|
|
1522
|
+
const target = normalizeNetwork(scope);
|
|
1523
|
+
out = out.filter((r) => r.rails.some((rail) => railOnNetwork(rail, (n) => n === target)));
|
|
1524
|
+
}
|
|
1525
|
+
if (opts.maxPrice !== void 0) {
|
|
1526
|
+
const max = opts.maxPrice;
|
|
1527
|
+
out = out.filter((r) => r.priceUsd === void 0 || r.priceUsd <= max);
|
|
1528
|
+
}
|
|
1529
|
+
return out;
|
|
1530
|
+
}
|
|
1531
|
+
/**
|
|
1532
|
+
* List a resource you run on the OPEN x402 registries, so agents can find it.
|
|
1533
|
+
* Default target is **402 Index** — one POST, no auth, no signature, no payment
|
|
1534
|
+
* (searchable within seconds). Add `'x402scan'` to also register via SIWX (one
|
|
1535
|
+
* wallet signature; EVM + a Base/Solana rail). Returns one {@link RegisterOutcome}
|
|
1536
|
+
* per target — a target the chain can't satisfy comes back `{ ok:false, detail }`,
|
|
1537
|
+
* never a throw. An explicit, developer-invoked action; it moves no funds, and
|
|
1538
|
+
* nothing is PipRail-hosted — you're listing on third-party open directories.
|
|
1539
|
+
*/
|
|
1540
|
+
async register(url, opts = {}) {
|
|
1541
|
+
const targets = _nullishCoalesce(opts.targets, () => ( ["402index"]));
|
|
1542
|
+
const networkSlug = _nullishCoalesce(opts.network, () => ( (typeof this.opts.chain === "string" ? this.opts.chain : void 0)));
|
|
1543
|
+
const outcomes = [];
|
|
1544
|
+
for (const target of targets) {
|
|
1545
|
+
if (target === "402index") {
|
|
1546
|
+
outcomes.push(
|
|
1547
|
+
await register402Index({
|
|
1548
|
+
url,
|
|
1549
|
+
...opts.name ? { name: opts.name } : {},
|
|
1550
|
+
...opts.description ? { description: opts.description } : {},
|
|
1551
|
+
...opts.priceUsd !== void 0 ? { priceUsd: opts.priceUsd } : {},
|
|
1552
|
+
...opts.asset ? { asset: opts.asset } : {},
|
|
1553
|
+
...networkSlug ? { network: networkSlug } : {},
|
|
1554
|
+
...opts.method ? { method: opts.method } : {},
|
|
1555
|
+
...opts.attribution ? { attribution: true } : {}
|
|
1556
|
+
})
|
|
1557
|
+
);
|
|
1558
|
+
} else if (target === "x402scan") {
|
|
1559
|
+
const signer = await this.discoverySigner();
|
|
1560
|
+
if (!signer) {
|
|
1561
|
+
outcomes.push({
|
|
1562
|
+
source: "x402scan",
|
|
1563
|
+
ok: false,
|
|
1564
|
+
detail: "x402scan registration needs an EVM signer; this chain family has no discoverySigner. Use 402 Index (the default), which needs no signature."
|
|
1565
|
+
});
|
|
1566
|
+
continue;
|
|
1567
|
+
}
|
|
1568
|
+
outcomes.push(await registerX402Scan({ url }, signer));
|
|
1569
|
+
} else {
|
|
1570
|
+
outcomes.push({
|
|
1571
|
+
source: "bazaar",
|
|
1572
|
+
ok: false,
|
|
1573
|
+
detail: "CDP Bazaar has no register endpoint \u2014 it catalogs a resource only when its facilitator settles a payment (PipRail uses no facilitator). List on 402 Index / x402scan instead."
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
return outcomes;
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* The discovery signer for the bound wallet (its address + a message signer),
|
|
1581
|
+
* or `null` if the chain family doesn't support it (EVM does today). For
|
|
1582
|
+
* discovery only — ownership proofs (sign the bare origin string and pass it to
|
|
1583
|
+
* `buildOpenApi({ ownershipProofs })`) and SIWX registration. Never signs a
|
|
1584
|
+
* payment.
|
|
1585
|
+
*/
|
|
1586
|
+
async discoverySigner() {
|
|
1587
|
+
const { net, wallet } = await this.ensure();
|
|
1588
|
+
return net.discoverySigner ? net.discoverySigner(wallet) : null;
|
|
1589
|
+
}
|
|
1152
1590
|
/**
|
|
1153
1591
|
* Lower-level: drive any HTTP method through the 402 flow.
|
|
1154
1592
|
*
|
|
@@ -1157,7 +1595,7 @@ var PipRailClient = (_class2 = class {
|
|
|
1157
1595
|
* streams throw `NonReplayableBodyError`.
|
|
1158
1596
|
*/
|
|
1159
1597
|
async fetch(url, init) {
|
|
1160
|
-
const body = _optionalChain([init, 'optionalAccess',
|
|
1598
|
+
const body = _optionalChain([init, 'optionalAccess', _22 => _22.body]);
|
|
1161
1599
|
if (body !== void 0 && body !== null && !isReplayableBodyInit(body)) {
|
|
1162
1600
|
throw new (0, _chunkIQGT65WScjs.NonReplayableBodyError)(
|
|
1163
1601
|
"fetch(): init.body is not replayable. Pass a string, FormData, URLSearchParams, ArrayBuffer, or Blob \u2014 not a ReadableStream."
|
|
@@ -1169,7 +1607,7 @@ var PipRailClient = (_class2 = class {
|
|
|
1169
1607
|
const { net, wallet, challenge } = resolved;
|
|
1170
1608
|
let accept = resolved.accept;
|
|
1171
1609
|
let quote = resolved.quote;
|
|
1172
|
-
const autoRoute = _nullishCoalesce(_nullishCoalesce(_optionalChain([init, 'optionalAccess',
|
|
1610
|
+
const autoRoute = _nullishCoalesce(_nullishCoalesce(_optionalChain([init, 'optionalAccess', _23 => _23.autoRoute]), () => ( this.opts.autoRoute)), () => ( false));
|
|
1173
1611
|
if (autoRoute) {
|
|
1174
1612
|
const plan = await this.planFromChallenge(net, wallet, challenge, url);
|
|
1175
1613
|
if (!plan.best) {
|
|
@@ -1328,11 +1766,11 @@ var PipRailClient = (_class2 = class {
|
|
|
1328
1766
|
}
|
|
1329
1767
|
const amountBase = BigInt(accept.amount);
|
|
1330
1768
|
const described = net.describeAsset(accept.asset);
|
|
1331
|
-
const decimals = _nullishCoalesce(_optionalChain([described, 'optionalAccess',
|
|
1332
|
-
const symbol = _nullishCoalesce(_optionalChain([described, 'optionalAccess',
|
|
1769
|
+
const decimals = _nullishCoalesce(_optionalChain([described, 'optionalAccess', _24 => _24.decimals]), () => ( accept.extra.decimals));
|
|
1770
|
+
const symbol = _nullishCoalesce(_optionalChain([described, 'optionalAccess', _25 => _25.symbol]), () => ( accept.extra.symbol));
|
|
1333
1771
|
const amountFormatted = _chunkIQGT65WScjs.formatUnits.call(void 0, amountBase, decimals);
|
|
1334
1772
|
const intent = {
|
|
1335
|
-
host:
|
|
1773
|
+
host: hostOf2(url),
|
|
1336
1774
|
chain: this.opts.chain,
|
|
1337
1775
|
network: accept.network,
|
|
1338
1776
|
asset: accept.asset,
|
|
@@ -1395,7 +1833,7 @@ var PipRailClient = (_class2 = class {
|
|
|
1395
1833
|
this.ledger.record(
|
|
1396
1834
|
{
|
|
1397
1835
|
url: quote.url,
|
|
1398
|
-
host:
|
|
1836
|
+
host: hostOf2(quote.url),
|
|
1399
1837
|
network: quote.network,
|
|
1400
1838
|
asset: quote.asset,
|
|
1401
1839
|
amountBase: quote.amount,
|
|
@@ -1438,7 +1876,7 @@ var PipRailClient = (_class2 = class {
|
|
|
1438
1876
|
accepted: accept,
|
|
1439
1877
|
payload: { nonce: accept.extra.nonce, txHash: ref }
|
|
1440
1878
|
};
|
|
1441
|
-
const headers = new Headers(_optionalChain([originalInit, 'optionalAccess',
|
|
1879
|
+
const headers = new Headers(_optionalChain([originalInit, 'optionalAccess', _26 => _26.headers]));
|
|
1442
1880
|
headers.set(HEADER_SIGNATURE, buildSignatureHeader(signature));
|
|
1443
1881
|
let lastResponse = null;
|
|
1444
1882
|
let lastReason = null;
|
|
@@ -1453,7 +1891,7 @@ var PipRailClient = (_class2 = class {
|
|
|
1453
1891
|
() => timeoutController.abort(),
|
|
1454
1892
|
this.retryTimeoutMs
|
|
1455
1893
|
);
|
|
1456
|
-
const signal = _optionalChain([originalInit, 'optionalAccess',
|
|
1894
|
+
const signal = _optionalChain([originalInit, 'optionalAccess', _27 => _27.signal]) && typeof AbortSignal.any === "function" ? AbortSignal.any([timeoutController.signal, originalInit.signal]) : timeoutController.signal;
|
|
1457
1895
|
try {
|
|
1458
1896
|
lastResponse = await fetch(url, {
|
|
1459
1897
|
..._nullishCoalesce(originalInit, () => ( {})),
|
|
@@ -1493,7 +1931,7 @@ var PipRailClient = (_class2 = class {
|
|
|
1493
1931
|
function safeBig(s) {
|
|
1494
1932
|
try {
|
|
1495
1933
|
return BigInt(s);
|
|
1496
|
-
} catch (
|
|
1934
|
+
} catch (e17) {
|
|
1497
1935
|
return 0n;
|
|
1498
1936
|
}
|
|
1499
1937
|
}
|
|
@@ -1526,10 +1964,10 @@ function buildFundingHint(options, chainLabel) {
|
|
|
1526
1964
|
return `Couldn't fully read your wallet on ${chainLabel} (RPC throttled) \u2014 retry; you may already be able to pay ${target.quote.amountFormatted} ${sym}.`;
|
|
1527
1965
|
}
|
|
1528
1966
|
const parts = [];
|
|
1529
|
-
if (target.blockers.includes("INSUFFICIENT_TOKEN") && _optionalChain([target, 'access',
|
|
1967
|
+
if (target.blockers.includes("INSUFFICIENT_TOKEN") && _optionalChain([target, 'access', _28 => _28.shortfall, 'optionalAccess', _29 => _29.token])) {
|
|
1530
1968
|
parts.push(`top up ${target.shortfall.token} ${sym}`);
|
|
1531
1969
|
}
|
|
1532
|
-
if (target.blockers.includes("INSUFFICIENT_GAS") && _optionalChain([target, 'access',
|
|
1970
|
+
if (target.blockers.includes("INSUFFICIENT_GAS") && _optionalChain([target, 'access', _30 => _30.shortfall, 'optionalAccess', _31 => _31.native])) {
|
|
1533
1971
|
parts.push(`add ~${target.shortfall.native} ${target.cost.feeSymbol} for gas`);
|
|
1534
1972
|
}
|
|
1535
1973
|
return parts.length ? `Can't settle on ${chainLabel}: ${parts.join(" and ")} (to pay ${target.quote.amountFormatted} ${sym}).` : `Can't settle on ${chainLabel} for ${target.quote.amountFormatted} ${sym}.`;
|
|
@@ -1543,7 +1981,7 @@ async function planAcross(clients, url, init) {
|
|
|
1543
1981
|
const status = best ? "ready" : options.some((o) => o.state === "unknown") ? "unknown" : "blocked";
|
|
1544
1982
|
return {
|
|
1545
1983
|
url,
|
|
1546
|
-
network: _nullishCoalesce(_optionalChain([best, 'optionalAccess',
|
|
1984
|
+
network: _nullishCoalesce(_optionalChain([best, 'optionalAccess', _32 => _32.accept, 'access', _33 => _33.network]), () => ( live[0].network)),
|
|
1547
1985
|
status,
|
|
1548
1986
|
payable: best !== null,
|
|
1549
1987
|
best,
|
|
@@ -1552,10 +1990,14 @@ async function planAcross(clients, url, init) {
|
|
|
1552
1990
|
fundingHint: best ? null : _nullishCoalesce(live.map((p) => p.fundingHint).find(Boolean), () => ( null))
|
|
1553
1991
|
};
|
|
1554
1992
|
}
|
|
1555
|
-
function
|
|
1993
|
+
function railOnNetwork(rail, matches) {
|
|
1994
|
+
const n = normalizeNetwork(rail.network);
|
|
1995
|
+
return !n.includes(":") || matches(n);
|
|
1996
|
+
}
|
|
1997
|
+
function hostOf2(url) {
|
|
1556
1998
|
try {
|
|
1557
1999
|
return new URL(url).hostname;
|
|
1558
|
-
} catch (
|
|
2000
|
+
} catch (e18) {
|
|
1559
2001
|
return url;
|
|
1560
2002
|
}
|
|
1561
2003
|
}
|
|
@@ -1578,7 +2020,7 @@ async function readInvalidReason(response) {
|
|
|
1578
2020
|
detail: typeof body.detail === "string" ? body.detail : ""
|
|
1579
2021
|
};
|
|
1580
2022
|
}
|
|
1581
|
-
} catch (
|
|
2023
|
+
} catch (e19) {
|
|
1582
2024
|
}
|
|
1583
2025
|
return null;
|
|
1584
2026
|
}
|
|
@@ -1589,12 +2031,48 @@ async function readBody(res) {
|
|
|
1589
2031
|
if (!text) return null;
|
|
1590
2032
|
try {
|
|
1591
2033
|
return JSON.parse(text);
|
|
1592
|
-
} catch (
|
|
2034
|
+
} catch (e20) {
|
|
1593
2035
|
return text;
|
|
1594
2036
|
}
|
|
1595
2037
|
}
|
|
1596
2038
|
function paymentTools(client) {
|
|
1597
2039
|
return [
|
|
2040
|
+
{
|
|
2041
|
+
name: "piprail_discover",
|
|
2042
|
+
description: `Find x402 payment-gated resources on the OPEN indexes (a phone book of payable APIs) WITHOUT paying. Use it to answer "what can I buy?" \u2014 search by topic, then quote/plan/pay a chosen one. By default returns only resources payable on your wallet's chain (network='self'); pass 'any' for every chain. Results are cross-scheme: ALWAYS call piprail_quote_payment on a chosen resource (it re-checks the live price) before piprail_pay_request.`,
|
|
2043
|
+
parameters: {
|
|
2044
|
+
type: "object",
|
|
2045
|
+
properties: {
|
|
2046
|
+
query: { type: "string", description: "Free-text topic to search for (optional)." },
|
|
2047
|
+
network: {
|
|
2048
|
+
type: "string",
|
|
2049
|
+
description: "CAIP-2 id, 'self' (your chain \u2014 default), or 'any' (all chains)."
|
|
2050
|
+
},
|
|
2051
|
+
maxPrice: { type: "number", description: "Drop results advertised above this USD price." },
|
|
2052
|
+
limit: { type: "number", description: "Max results per index (default 20)." }
|
|
2053
|
+
},
|
|
2054
|
+
additionalProperties: false
|
|
2055
|
+
},
|
|
2056
|
+
invoke: async (args) => {
|
|
2057
|
+
const opts = {};
|
|
2058
|
+
if (typeof args.query === "string") opts.query = args.query;
|
|
2059
|
+
if (typeof args.network === "string") opts.network = args.network;
|
|
2060
|
+
if (typeof args.maxPrice === "number") opts.maxPrice = args.maxPrice;
|
|
2061
|
+
if (typeof args.limit === "number") opts.limit = args.limit;
|
|
2062
|
+
const found = await client.discover(opts);
|
|
2063
|
+
return {
|
|
2064
|
+
count: found.length,
|
|
2065
|
+
resources: found.map((r) => ({
|
|
2066
|
+
resource: r.resource,
|
|
2067
|
+
name: r.name,
|
|
2068
|
+
description: r.description,
|
|
2069
|
+
source: r.source,
|
|
2070
|
+
priceUsd: r.priceUsd,
|
|
2071
|
+
networks: [...new Set(r.rails.map((rail) => rail.network))]
|
|
2072
|
+
}))
|
|
2073
|
+
};
|
|
2074
|
+
}
|
|
2075
|
+
},
|
|
1598
2076
|
{
|
|
1599
2077
|
name: "piprail_quote_payment",
|
|
1600
2078
|
description: "Get the price of an x402 payment-gated URL WITHOUT paying. Returns the amount, token, chain, recipient, and whether it is within the spend policy. Returns { gated: false } when the URL needs no payment. Call this first to decide whether a resource is worth buying.",
|
|
@@ -1698,6 +2176,29 @@ function paymentTools(client) {
|
|
|
1698
2176
|
throw err;
|
|
1699
2177
|
}
|
|
1700
2178
|
}
|
|
2179
|
+
},
|
|
2180
|
+
{
|
|
2181
|
+
name: "piprail_register",
|
|
2182
|
+
description: "List an x402 payment-gated resource YOU run on the open indexes so other agents can discover it. Default target is 402 Index \u2014 no auth, no signature, no payment; searchable within seconds. Returns one outcome per index ({ source, ok, detail }); a step the chain can't satisfy comes back ok:false with the reason. Moves no funds; nothing is PipRail-hosted.",
|
|
2183
|
+
parameters: {
|
|
2184
|
+
type: "object",
|
|
2185
|
+
properties: {
|
|
2186
|
+
url: { type: "string", description: "Full URL of the resource to list." },
|
|
2187
|
+
name: { type: "string", description: "Display name (defaults to the host)." },
|
|
2188
|
+
description: { type: "string", description: "What the resource offers." },
|
|
2189
|
+
priceUsd: { type: "number", description: "Advertised price in USD (metadata)." }
|
|
2190
|
+
},
|
|
2191
|
+
required: ["url"],
|
|
2192
|
+
additionalProperties: false
|
|
2193
|
+
},
|
|
2194
|
+
invoke: async (args) => {
|
|
2195
|
+
const opts = {};
|
|
2196
|
+
if (typeof args.name === "string") opts.name = args.name;
|
|
2197
|
+
if (typeof args.description === "string") opts.description = args.description;
|
|
2198
|
+
if (typeof args.priceUsd === "number") opts.priceUsd = args.priceUsd;
|
|
2199
|
+
const outcomes = await client.register(String(args.url), opts);
|
|
2200
|
+
return { outcomes };
|
|
2201
|
+
}
|
|
1701
2202
|
}
|
|
1702
2203
|
];
|
|
1703
2204
|
}
|
|
@@ -1800,6 +2301,25 @@ function createPaymentGate(options) {
|
|
|
1800
2301
|
const { challenge: c, requiredHeader } = await challenge();
|
|
1801
2302
|
return { kind: "challenge", challenge: c, requiredHeader, statusCode: 402 };
|
|
1802
2303
|
}
|
|
2304
|
+
async function describe(resourceUrl = "") {
|
|
2305
|
+
const specs = await ready();
|
|
2306
|
+
const accepts = specs.map((s) => ({
|
|
2307
|
+
scheme: "onchain-proof",
|
|
2308
|
+
network: s.net.network,
|
|
2309
|
+
asset: s.asset,
|
|
2310
|
+
payTo: s.payTo,
|
|
2311
|
+
amount: s.amountBase.toString(),
|
|
2312
|
+
amountFormatted: s.amountFormatted,
|
|
2313
|
+
decimals: s.decimals,
|
|
2314
|
+
maxTimeoutSeconds,
|
|
2315
|
+
...s.symbol ? { symbol: s.symbol } : {}
|
|
2316
|
+
}));
|
|
2317
|
+
return {
|
|
2318
|
+
url: resourceUrl,
|
|
2319
|
+
...options.description ? { description: options.description } : {},
|
|
2320
|
+
accepts
|
|
2321
|
+
};
|
|
2322
|
+
}
|
|
1803
2323
|
async function verify(paymentSignature) {
|
|
1804
2324
|
const raw = normaliseHeader(paymentSignature);
|
|
1805
2325
|
if (!raw) return asChallenge();
|
|
@@ -1842,7 +2362,7 @@ function createPaymentGate(options) {
|
|
|
1842
2362
|
if (options.onPaid) {
|
|
1843
2363
|
try {
|
|
1844
2364
|
options.onPaid(result.receipt);
|
|
1845
|
-
} catch (
|
|
2365
|
+
} catch (e21) {
|
|
1846
2366
|
}
|
|
1847
2367
|
}
|
|
1848
2368
|
return {
|
|
@@ -1851,7 +2371,7 @@ function createPaymentGate(options) {
|
|
|
1851
2371
|
receiptHeader: buildReceiptHeader(result.receipt)
|
|
1852
2372
|
};
|
|
1853
2373
|
}
|
|
1854
|
-
return { challenge, verify };
|
|
2374
|
+
return { challenge, verify, describe };
|
|
1855
2375
|
}
|
|
1856
2376
|
function requirePayment(options) {
|
|
1857
2377
|
const gate = createPaymentGate(options);
|
|
@@ -1949,8 +2469,8 @@ async function buildExactAuthorization(params) {
|
|
|
1949
2469
|
};
|
|
1950
2470
|
const signature = await account.signTypedData({
|
|
1951
2471
|
domain: {
|
|
1952
|
-
name: _nullishCoalesce(_optionalChain([accept, 'access',
|
|
1953
|
-
version: _nullishCoalesce(_optionalChain([accept, 'access',
|
|
2472
|
+
name: _nullishCoalesce(_optionalChain([accept, 'access', _34 => _34.extra, 'optionalAccess', _35 => _35.name]), () => ( "USD Coin")),
|
|
2473
|
+
version: _nullishCoalesce(_optionalChain([accept, 'access', _36 => _36.extra, 'optionalAccess', _37 => _37.version]), () => ( "2")),
|
|
1954
2474
|
chainId,
|
|
1955
2475
|
verifyingContract: accept.asset
|
|
1956
2476
|
},
|
|
@@ -1982,6 +2502,69 @@ function encodeXPaymentHeader(input) {
|
|
|
1982
2502
|
return base64(JSON.stringify(payload));
|
|
1983
2503
|
}
|
|
1984
2504
|
|
|
2505
|
+
// src/discovery.ts
|
|
2506
|
+
var GENERATOR = "@piprail/sdk \xB7 https://piprail.com";
|
|
2507
|
+
function pathOf(url) {
|
|
2508
|
+
try {
|
|
2509
|
+
return new URL(url).pathname || "/";
|
|
2510
|
+
} catch (e22) {
|
|
2511
|
+
return url.startsWith("/") ? url : `/${url}`;
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
function buildOpenApi(input) {
|
|
2515
|
+
const paths = {};
|
|
2516
|
+
for (const r of input.resources) {
|
|
2517
|
+
const path = pathOf(r.url);
|
|
2518
|
+
const method = (_nullishCoalesce(r.method, () => ( "GET"))).toLowerCase();
|
|
2519
|
+
const op = {
|
|
2520
|
+
...r.description ? { summary: r.description } : {},
|
|
2521
|
+
responses: {
|
|
2522
|
+
"200": { description: "Paid \u2014 the resource." },
|
|
2523
|
+
"402": { description: "Payment required (x402)." }
|
|
2524
|
+
},
|
|
2525
|
+
"x-payment-info": {
|
|
2526
|
+
x402Version: 2,
|
|
2527
|
+
accepts: r.accepts,
|
|
2528
|
+
bazaar: { discoverable: true }
|
|
2529
|
+
}
|
|
2530
|
+
};
|
|
2531
|
+
paths[path] = { ..._nullishCoalesce(paths[path], () => ( {})), [method]: op };
|
|
2532
|
+
}
|
|
2533
|
+
return {
|
|
2534
|
+
openapi: "3.1.0",
|
|
2535
|
+
info: { title: _nullishCoalesce(input.title, () => ( "PipRail x402 resources")), version: _nullishCoalesce(input.version, () => ( "1.0.0")) },
|
|
2536
|
+
servers: [{ url: input.origin }],
|
|
2537
|
+
paths,
|
|
2538
|
+
// "Built with @piprail/sdk" — default on (opt out with attribution:false). At the
|
|
2539
|
+
// document ROOT so `info` stays exactly { title, version }.
|
|
2540
|
+
...input.attribution === false ? {} : { "x-generator": GENERATOR },
|
|
2541
|
+
...input.ownershipProofs && input.ownershipProofs.length > 0 ? { "x-agentcash-provenance": { ownershipProofs: input.ownershipProofs } } : {}
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
function buildWellKnownX402(input) {
|
|
2545
|
+
return {
|
|
2546
|
+
version: 1,
|
|
2547
|
+
resources: input.resources.map((r) => r.url),
|
|
2548
|
+
...input.ownershipProofs && input.ownershipProofs.length > 0 ? { ownershipProofs: input.ownershipProofs } : {}
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
function buildX402DnsTxt(input) {
|
|
2552
|
+
const descriptor = input.descriptor ? `descriptor=${input.descriptor};` : "";
|
|
2553
|
+
return {
|
|
2554
|
+
name: `_x402.${input.host}`,
|
|
2555
|
+
type: "TXT",
|
|
2556
|
+
value: `v=x4021;${descriptor}url=${input.discoveryUrl}`
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
|
|
2561
|
+
|
|
2562
|
+
|
|
2563
|
+
|
|
2564
|
+
|
|
2565
|
+
|
|
2566
|
+
|
|
2567
|
+
|
|
1985
2568
|
|
|
1986
2569
|
|
|
1987
2570
|
|
|
@@ -2021,4 +2604,4 @@ function encodeXPaymentHeader(input) {
|
|
|
2021
2604
|
|
|
2022
2605
|
|
|
2023
2606
|
|
|
2024
|
-
exports.CHAINS = CHAINS; exports.ConfirmationTimeoutError = _chunkIQGT65WScjs.ConfirmationTimeoutError; exports.EIP3009_TYPES = EIP3009_TYPES; exports.EXACT_NETWORK_SLUGS = EXACT_NETWORK_SLUGS; exports.InsufficientFundsError = _chunkIQGT65WScjs.InsufficientFundsError; exports.InvalidEnvelopeError = _chunkIQGT65WScjs.InvalidEnvelopeError; exports.MaxRetriesExceededError = _chunkIQGT65WScjs.MaxRetriesExceededError; exports.MissingDriverError = _chunkIQGT65WScjs.MissingDriverError; exports.NoCompatibleAcceptError = _chunkIQGT65WScjs.NoCompatibleAcceptError; exports.NonReplayableBodyError = _chunkIQGT65WScjs.NonReplayableBodyError; exports.PaymentDeclinedError = _chunkIQGT65WScjs.PaymentDeclinedError; exports.PaymentTimeoutError = _chunkIQGT65WScjs.PaymentTimeoutError; exports.PipRailClient = PipRailClient; exports.PipRailError = _chunkIQGT65WScjs.PipRailError; exports.RecipientNotReadyError = _chunkIQGT65WScjs.RecipientNotReadyError; exports.UnknownTokenError = _chunkIQGT65WScjs.UnknownTokenError; exports.UnsupportedNetworkError = _chunkIQGT65WScjs.UnsupportedNetworkError; exports.WrongChainError = _chunkIQGT65WScjs.WrongChainError; exports.WrongFamilyError = _chunkIQGT65WScjs.WrongFamilyError; exports.buildChallengeHeader = buildChallengeHeader; exports.buildExactAuthorization = buildExactAuthorization; exports.buildReceiptHeader = buildReceiptHeader; exports.buildSignatureHeader = buildSignatureHeader; exports.chainIdForExactNetwork = chainIdForExactNetwork; exports.createPaymentGate = createPaymentGate; exports.encodeXPaymentHeader = encodeXPaymentHeader; exports.evaluatePolicy = evaluatePolicy; exports.parseChallenge = parseChallenge; exports.parseExactRequirements = parseExactRequirements; exports.parseReceipt = parseReceipt; exports.parseSignatureHeader = parseSignatureHeader; exports.paymentTools = paymentTools; exports.pickAccept = pickAccept; exports.planAcross = planAcross; exports.registerDriver = registerDriver; exports.requirePayment = requirePayment; exports.resolveChain = resolveChain; exports.toInsufficientFundsError = _chunkIQGT65WScjs.toInsufficientFundsError; exports.toInvalidBody = toInvalidBody;
|
|
2607
|
+
exports.CHAINS = CHAINS; exports.ConfirmationTimeoutError = _chunkIQGT65WScjs.ConfirmationTimeoutError; exports.EIP3009_TYPES = EIP3009_TYPES; exports.EXACT_NETWORK_SLUGS = EXACT_NETWORK_SLUGS; exports.GENERATOR = GENERATOR; exports.InsufficientFundsError = _chunkIQGT65WScjs.InsufficientFundsError; exports.InvalidEnvelopeError = _chunkIQGT65WScjs.InvalidEnvelopeError; exports.MaxRetriesExceededError = _chunkIQGT65WScjs.MaxRetriesExceededError; exports.MissingDriverError = _chunkIQGT65WScjs.MissingDriverError; exports.NoCompatibleAcceptError = _chunkIQGT65WScjs.NoCompatibleAcceptError; exports.NonReplayableBodyError = _chunkIQGT65WScjs.NonReplayableBodyError; exports.PaymentDeclinedError = _chunkIQGT65WScjs.PaymentDeclinedError; exports.PaymentTimeoutError = _chunkIQGT65WScjs.PaymentTimeoutError; exports.PipRailClient = PipRailClient; exports.PipRailError = _chunkIQGT65WScjs.PipRailError; exports.RecipientNotReadyError = _chunkIQGT65WScjs.RecipientNotReadyError; exports.UnknownTokenError = _chunkIQGT65WScjs.UnknownTokenError; exports.UnsupportedNetworkError = _chunkIQGT65WScjs.UnsupportedNetworkError; exports.WrongChainError = _chunkIQGT65WScjs.WrongChainError; exports.WrongFamilyError = _chunkIQGT65WScjs.WrongFamilyError; exports.buildChallengeHeader = buildChallengeHeader; exports.buildExactAuthorization = buildExactAuthorization; exports.buildOpenApi = buildOpenApi; exports.buildReceiptHeader = buildReceiptHeader; exports.buildSignatureHeader = buildSignatureHeader; exports.buildWellKnownX402 = buildWellKnownX402; exports.buildX402DnsTxt = buildX402DnsTxt; exports.chainIdForExactNetwork = chainIdForExactNetwork; exports.createPaymentGate = createPaymentGate; exports.encodeXPaymentHeader = encodeXPaymentHeader; exports.evaluatePolicy = evaluatePolicy; exports.normalizeNetwork = normalizeNetwork; exports.parseChallenge = parseChallenge; exports.parseExactRequirements = parseExactRequirements; exports.parseReceipt = parseReceipt; exports.parseSignatureHeader = parseSignatureHeader; exports.paymentTools = paymentTools; exports.pickAccept = pickAccept; exports.planAcross = planAcross; exports.register402Index = register402Index; exports.registerDriver = registerDriver; exports.registerX402Scan = registerX402Scan; exports.requirePayment = requirePayment; exports.resolveChain = resolveChain; exports.searchOpenIndexes = searchOpenIndexes; exports.toInsufficientFundsError = _chunkIQGT65WScjs.toInsufficientFundsError; exports.toInvalidBody = toInvalidBody;
|