@relai-fi/x402 0.6.4 → 0.6.6
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 +116 -0
- package/dist/bridge.cjs +109 -0
- package/dist/bridge.cjs.map +1 -0
- package/dist/bridge.d.cts +78 -0
- package/dist/bridge.d.ts +78 -0
- package/dist/bridge.js +80 -0
- package/dist/bridge.js.map +1 -0
- package/dist/client.cjs +209 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +12 -0
- package/dist/client.d.ts +12 -0
- package/dist/client.js +209 -1
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +327 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +327 -74
- package/dist/index.js.map +1 -1
- package/dist/mpp/bridge-client.cjs +23922 -0
- package/dist/mpp/bridge-client.cjs.map +1 -0
- package/dist/mpp/bridge-client.d.cts +58 -0
- package/dist/mpp/bridge-client.d.ts +58 -0
- package/dist/mpp/bridge-client.js +23892 -0
- package/dist/mpp/bridge-client.js.map +1 -0
- package/dist/mpp/bridge-method.cjs +13202 -0
- package/dist/mpp/bridge-method.cjs.map +1 -0
- package/dist/mpp/bridge-method.d.cts +69 -0
- package/dist/mpp/bridge-method.d.ts +69 -0
- package/dist/mpp/bridge-method.js +13181 -0
- package/dist/mpp/bridge-method.js.map +1 -0
- package/dist/mpp/bridge-server.cjs +13887 -0
- package/dist/mpp/bridge-server.cjs.map +1 -0
- package/dist/mpp/bridge-server.d.cts +62 -0
- package/dist/mpp/bridge-server.d.ts +62 -0
- package/dist/mpp/bridge-server.js +13866 -0
- package/dist/mpp/bridge-server.js.map +1 -0
- package/dist/mpp/evm-server.cjs +49 -33
- package/dist/mpp/evm-server.cjs.map +1 -1
- package/dist/mpp/evm-server.js +49 -33
- package/dist/mpp/evm-server.js.map +1 -1
- package/dist/mpp/verify-erc20.cjs +71 -0
- package/dist/mpp/verify-erc20.cjs.map +1 -0
- package/dist/mpp/verify-erc20.d.cts +27 -0
- package/dist/mpp/verify-erc20.d.ts +27 -0
- package/dist/mpp/verify-erc20.js +46 -0
- package/dist/mpp/verify-erc20.js.map +1 -0
- package/dist/mpp/verify-spl.cjs +96 -0
- package/dist/mpp/verify-spl.cjs.map +1 -0
- package/dist/mpp/verify-spl.d.cts +30 -0
- package/dist/mpp/verify-spl.d.ts +30 -0
- package/dist/mpp/verify-spl.js +71 -0
- package/dist/mpp/verify-spl.js.map +1 -0
- package/dist/mpp/with-bridge.cjs +23956 -0
- package/dist/mpp/with-bridge.cjs.map +1 -0
- package/dist/mpp/with-bridge.d.cts +53 -0
- package/dist/mpp/with-bridge.d.ts +53 -0
- package/dist/mpp/with-bridge.js +23926 -0
- package/dist/mpp/with-bridge.js.map +1 -0
- package/dist/react/index.cjs +209 -1
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.js +209 -1
- package/dist/react/index.js.map +1 -1
- package/dist/server.cjs +8 -40
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +8 -40
- package/dist/server.js.map +1 -1
- package/package.json +32 -1
package/dist/react/index.cjs
CHANGED
|
@@ -268,6 +268,71 @@ function normalizeNetwork(network) {
|
|
|
268
268
|
return null;
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
+
// src/bridge.ts
|
|
272
|
+
var RELAI_API_BASE = "https://api.relai.fi";
|
|
273
|
+
var _cacheMap = /* @__PURE__ */ new Map();
|
|
274
|
+
var CACHE_TTL = 5 * 60 * 1e3;
|
|
275
|
+
async function getBridgeInfo(baseUrl = RELAI_API_BASE) {
|
|
276
|
+
const key = baseUrl.replace(/\/$/, "");
|
|
277
|
+
const now = Date.now();
|
|
278
|
+
const cached = _cacheMap.get(key);
|
|
279
|
+
if (cached && now - cached.time < CACHE_TTL) return cached.info;
|
|
280
|
+
const url = `${key}/bridge/info`;
|
|
281
|
+
const res = await fetch(url);
|
|
282
|
+
if (!res.ok) {
|
|
283
|
+
if (cached) return cached.info;
|
|
284
|
+
throw new Error(`[relai:bridge] Failed to fetch ${url}: ${res.status}`);
|
|
285
|
+
}
|
|
286
|
+
const data = await res.json();
|
|
287
|
+
const info = {
|
|
288
|
+
settleEndpoint: data.settleEndpoint,
|
|
289
|
+
supportedSourceChains: data.supportedSourceChains || [],
|
|
290
|
+
supportedSourceAssets: data.supportedSourceAssets || [],
|
|
291
|
+
payTo: data.payTo || {},
|
|
292
|
+
feePayerSvm: data.feePayerSvm ?? null,
|
|
293
|
+
feeBps: data.feeBps ?? 100,
|
|
294
|
+
paymentFacilitator: data.paymentFacilitator || "https://facilitator.x402.fi"
|
|
295
|
+
};
|
|
296
|
+
_cacheMap.set(key, { info, time: now });
|
|
297
|
+
return info;
|
|
298
|
+
}
|
|
299
|
+
async function settleBridge(settleEndpoint, body) {
|
|
300
|
+
const res = await fetch(settleEndpoint, {
|
|
301
|
+
method: "POST",
|
|
302
|
+
headers: { "Content-Type": "application/json" },
|
|
303
|
+
body: JSON.stringify(body)
|
|
304
|
+
});
|
|
305
|
+
if (!res.ok) {
|
|
306
|
+
const err = await res.json().catch(() => ({}));
|
|
307
|
+
throw new Error(`[relai:bridge] settle failed: ${err.error || res.status}${err.details ? " \u2014 " + err.details : ""}`);
|
|
308
|
+
}
|
|
309
|
+
return res.json();
|
|
310
|
+
}
|
|
311
|
+
function selectSourceChain(supportedChains, hasEvmWallet, hasSolanaWallet, preferredSourceChainId) {
|
|
312
|
+
if (preferredSourceChainId && hasEvmWallet) {
|
|
313
|
+
const preferred = `eip155:${preferredSourceChainId}`;
|
|
314
|
+
if (supportedChains.includes(preferred)) {
|
|
315
|
+
return { type: "evm", chain: preferred };
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (hasSolanaWallet) {
|
|
319
|
+
const sol = supportedChains.find((c) => c.startsWith("solana:"));
|
|
320
|
+
if (sol) return { type: "solana", chain: sol };
|
|
321
|
+
}
|
|
322
|
+
if (hasEvmWallet) {
|
|
323
|
+
for (const chain of supportedChains) {
|
|
324
|
+
if (chain.startsWith("eip155:")) {
|
|
325
|
+
return { type: "evm", chain };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
function computeSourceAmount(targetAmount, feeBps) {
|
|
332
|
+
const fee = targetAmount * BigInt(feeBps) / 10000n;
|
|
333
|
+
return targetAmount + fee;
|
|
334
|
+
}
|
|
335
|
+
|
|
271
336
|
// src/client.ts
|
|
272
337
|
var PERMIT_NETWORKS = /* @__PURE__ */ new Set([]);
|
|
273
338
|
var DEFAULT_EVM_RPC_URLS = {
|
|
@@ -294,7 +359,8 @@ function createX402Client(config) {
|
|
|
294
359
|
integritas,
|
|
295
360
|
verbose = false,
|
|
296
361
|
defaultHeaders = {},
|
|
297
|
-
mpp
|
|
362
|
+
mpp,
|
|
363
|
+
bridge: bridgeConfig
|
|
298
364
|
} = config;
|
|
299
365
|
const relayWsEnabled = relayWs?.enabled === true;
|
|
300
366
|
const relayWsPreflightTimeoutMs = relayWs?.preflightTimeoutMs ?? 5e3;
|
|
@@ -713,6 +779,38 @@ function createX402Client(config) {
|
|
|
713
779
|
}
|
|
714
780
|
return null;
|
|
715
781
|
}
|
|
782
|
+
function extractMppChallengeFromWsError(error) {
|
|
783
|
+
if (isRecord(error) && typeof error.mppChallenge === "string") {
|
|
784
|
+
return error.mppChallenge;
|
|
785
|
+
}
|
|
786
|
+
if (isRecord(error.data) && typeof error.data.mppChallenge === "string") {
|
|
787
|
+
return error.data.mppChallenge;
|
|
788
|
+
}
|
|
789
|
+
const metadata = isRecord(error) ? error.responseHeaders : void 0;
|
|
790
|
+
if (isRecord(metadata)) {
|
|
791
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
792
|
+
if (key.toLowerCase() === "www-authenticate" && typeof value === "string") {
|
|
793
|
+
if (/^Payment\s+/i.test(value.trim())) {
|
|
794
|
+
return value;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
return null;
|
|
800
|
+
}
|
|
801
|
+
function buildSyntheticMppResponse(mppChallenge, wsError) {
|
|
802
|
+
const headers = new Headers();
|
|
803
|
+
headers.set("WWW-Authenticate", mppChallenge);
|
|
804
|
+
const responseHeaders = isRecord(wsError) ? wsError.responseHeaders : void 0;
|
|
805
|
+
if (isRecord(responseHeaders)) {
|
|
806
|
+
for (const [key, value] of Object.entries(responseHeaders)) {
|
|
807
|
+
if (typeof value === "string" && key.toLowerCase() !== "www-authenticate") {
|
|
808
|
+
headers.set(key, value);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return new Response(null, { status: 402, headers });
|
|
813
|
+
}
|
|
716
814
|
function buildWsResponse(wsResponse) {
|
|
717
815
|
const statusFromMetadata = isRecord(wsResponse.metadata) && typeof wsResponse.metadata.status === "number" ? wsResponse.metadata.status : 200;
|
|
718
816
|
const status = Number.isInteger(statusFromMetadata) && statusFromMetadata >= 100 && statusFromMetadata <= 599 ? statusFromMetadata : 200;
|
|
@@ -1207,6 +1305,41 @@ function createX402Client(config) {
|
|
|
1207
1305
|
if (Number(wsPreflightResponse.error.code) !== 402) {
|
|
1208
1306
|
throw new Error(wsPreflightResponse.error.message || "[relai-x402] WebSocket relay request failed");
|
|
1209
1307
|
}
|
|
1308
|
+
if (mpp) {
|
|
1309
|
+
const mppChallenge = extractMppChallengeFromWsError(wsPreflightResponse.error);
|
|
1310
|
+
if (mppChallenge) {
|
|
1311
|
+
log("MPP challenge detected in WS 402 response");
|
|
1312
|
+
try {
|
|
1313
|
+
const syntheticResponse = buildSyntheticMppResponse(mppChallenge, wsPreflightResponse.error);
|
|
1314
|
+
const credential = await mpp.createCredential(syntheticResponse);
|
|
1315
|
+
if (credential) {
|
|
1316
|
+
log("MPP credential created, retrying via WS with Authorization: Payment");
|
|
1317
|
+
wsPaymentPhaseStarted = true;
|
|
1318
|
+
const wsMppResponse = await relayCallOverWebSocket({
|
|
1319
|
+
relayUrl: url,
|
|
1320
|
+
requestMethod,
|
|
1321
|
+
requestHeaders: {
|
|
1322
|
+
...requestHeaders,
|
|
1323
|
+
"Authorization": credential.startsWith("Payment ") ? credential : `Payment ${credential}`
|
|
1324
|
+
},
|
|
1325
|
+
requestBody,
|
|
1326
|
+
timeoutMs: relayWsPaymentTimeoutMs
|
|
1327
|
+
});
|
|
1328
|
+
if (!wsMppResponse.error) {
|
|
1329
|
+
return buildWsResponse(wsMppResponse);
|
|
1330
|
+
}
|
|
1331
|
+
if (Number(wsMppResponse.error.code) !== 402) {
|
|
1332
|
+
throw new Error(wsMppResponse.error.message || "[relai-x402] WebSocket MPP retry failed");
|
|
1333
|
+
}
|
|
1334
|
+
log("MPP retry via WS still returned 402, falling through to x402 WS flow");
|
|
1335
|
+
wsPaymentPhaseStarted = false;
|
|
1336
|
+
}
|
|
1337
|
+
} catch (mppWsErr) {
|
|
1338
|
+
if (wsPaymentPhaseStarted) throw mppWsErr;
|
|
1339
|
+
log(`MPP over WS failed (${mppWsErr instanceof Error ? mppWsErr.message : mppWsErr}), falling through to x402 WS flow`);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1210
1343
|
const wsRequirements = extractPaymentRequirementsFromWsError(wsPreflightResponse.error);
|
|
1211
1344
|
if (!wsRequirements) {
|
|
1212
1345
|
throw new Error(
|
|
@@ -1224,6 +1357,17 @@ function createX402Client(config) {
|
|
|
1224
1357
|
}
|
|
1225
1358
|
const wsSelected = selectAccept(wsAccepts);
|
|
1226
1359
|
if (!wsSelected) {
|
|
1360
|
+
const wsBridge = getBridgeExtension(wsRequirements);
|
|
1361
|
+
if (wsBridge && selectBridgeSource(wsBridge)) {
|
|
1362
|
+
log("No direct wallet match in WS flow \u2014 attempting bridge extension");
|
|
1363
|
+
wsPaymentPhaseStarted = true;
|
|
1364
|
+
const bridgePaymentHeader = await executeBridgePayment(wsBridge, wsAccepts, wsRequirements, url);
|
|
1365
|
+
log("Retrying with X-PAYMENT header (bridge via WS fallback to HTTP)");
|
|
1366
|
+
return fetch(input, {
|
|
1367
|
+
...stripInternalInit(init) || {},
|
|
1368
|
+
headers: { ...requestHeaders, "X-PAYMENT": bridgePaymentHeader }
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1227
1371
|
throw new Error(buildNoWalletError(wsAccepts, true));
|
|
1228
1372
|
}
|
|
1229
1373
|
const { accept: accept2, chain: chain2 } = wsSelected;
|
|
@@ -1331,6 +1475,70 @@ function createX402Client(config) {
|
|
|
1331
1475
|
headers: { ...requestHeaders, "X-PAYMENT": paymentHeader }
|
|
1332
1476
|
});
|
|
1333
1477
|
}
|
|
1478
|
+
if (bridgeConfig?.enabled) {
|
|
1479
|
+
log("No direct wallet match \u2014 attempting auto-bridge via RelAI API");
|
|
1480
|
+
try {
|
|
1481
|
+
const info = await getBridgeInfo(bridgeConfig.baseUrl);
|
|
1482
|
+
const targetAccept = accepts[0];
|
|
1483
|
+
const hasEvm = !!effectiveWallets.evm;
|
|
1484
|
+
const hasSol = !!hasSolanaWallet;
|
|
1485
|
+
const source = selectSourceChain(info.supportedSourceChains, hasEvm, hasSol);
|
|
1486
|
+
if (source) {
|
|
1487
|
+
const bridgePayTo = info.payTo[source.chain];
|
|
1488
|
+
if (bridgePayTo) {
|
|
1489
|
+
const targetAmount = targetAccept.amount || targetAccept.maxAmountRequired;
|
|
1490
|
+
const sourceAmount = computeSourceAmount(BigInt(targetAmount), info.feeBps).toString();
|
|
1491
|
+
const sourceChainIdx = info.supportedSourceChains.indexOf(source.chain);
|
|
1492
|
+
const sourceAsset = sourceChainIdx >= 0 && info.supportedSourceAssets[sourceChainIdx] || (source.type === "evm" ? info.supportedSourceAssets.find((a) => a.startsWith("0x")) || targetAccept.asset : info.supportedSourceAssets.find((a) => !a.startsWith("0x")) || "");
|
|
1493
|
+
let sourcePaymentHeader;
|
|
1494
|
+
const sourceAccept = {
|
|
1495
|
+
scheme: "exact",
|
|
1496
|
+
network: source.chain,
|
|
1497
|
+
asset: sourceAsset,
|
|
1498
|
+
payTo: bridgePayTo,
|
|
1499
|
+
amount: sourceAmount,
|
|
1500
|
+
extra: {
|
|
1501
|
+
...targetAccept.extra || {},
|
|
1502
|
+
...source.type === "solana" && info.feePayerSvm ? { feePayer: info.feePayerSvm } : {}
|
|
1503
|
+
}
|
|
1504
|
+
};
|
|
1505
|
+
if (source.type === "solana" && hasSolanaWallet) {
|
|
1506
|
+
sourcePaymentHeader = await buildSolanaPayment(sourceAccept, requirements, url);
|
|
1507
|
+
} else if (source.type === "evm") {
|
|
1508
|
+
const evmNetwork = normalizeNetwork(source.chain);
|
|
1509
|
+
const usePermit = evmNetwork && PERMIT_NETWORKS.has(evmNetwork);
|
|
1510
|
+
sourcePaymentHeader = usePermit ? await buildEvmPermitPayment(sourceAccept, requirements, url) : await buildEvmPayment(sourceAccept, requirements, url);
|
|
1511
|
+
} else {
|
|
1512
|
+
throw new Error(`[relai-x402] No wallet for source chain type: ${source.type}`);
|
|
1513
|
+
}
|
|
1514
|
+
const settleData = await settleBridge(info.settleEndpoint, {
|
|
1515
|
+
sourcePayment: sourcePaymentHeader,
|
|
1516
|
+
sourceChain: source.chain,
|
|
1517
|
+
targetAccept: {
|
|
1518
|
+
scheme: "exact",
|
|
1519
|
+
network: targetAccept.network,
|
|
1520
|
+
asset: targetAccept.asset,
|
|
1521
|
+
payTo: targetAccept.payTo,
|
|
1522
|
+
amount: targetAmount
|
|
1523
|
+
},
|
|
1524
|
+
requirements,
|
|
1525
|
+
resource: url,
|
|
1526
|
+
paymentFacilitator: info.paymentFacilitator
|
|
1527
|
+
});
|
|
1528
|
+
if (settleData.xPayment) {
|
|
1529
|
+
log(`Auto-bridge settled: target=${settleData.targetTxId}`);
|
|
1530
|
+
return fetch(input, {
|
|
1531
|
+
...requestInitWithHeaders,
|
|
1532
|
+
headers: { ...requestHeaders, "X-PAYMENT": settleData.xPayment }
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
throw new Error("[relai-x402] Bridge settle did not return xPayment");
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
} catch (bridgeErr) {
|
|
1539
|
+
log(`Auto-bridge failed: ${bridgeErr instanceof Error ? bridgeErr.message : bridgeErr}`);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1334
1542
|
throw new Error(buildNoWalletError(accepts, false));
|
|
1335
1543
|
}
|
|
1336
1544
|
const { accept, chain } = selected;
|