@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/index.cjs
CHANGED
|
@@ -7539,7 +7539,8 @@ var Relai = class {
|
|
|
7539
7539
|
}
|
|
7540
7540
|
const isRelAI = this.facilitatorUrl.includes("facilitator.x402.fi") || this.facilitatorUrl.includes("relai");
|
|
7541
7541
|
if (isRelAI) {
|
|
7542
|
-
const
|
|
7542
|
+
const isSolanaNetwork2 = caip2.startsWith("solana:");
|
|
7543
|
+
const relaiFeePayer = isSolanaNetwork2 ? "4x4ZhcqiT1FnirM8Ne97iVupkN4NcQgc2YYbE2jDZbZn" : "0x1892f72fdB3A966b2AD8595aA5f7741Ef72d6085";
|
|
7543
7544
|
this.feePayerCache.set(caip2, relaiFeePayer);
|
|
7544
7545
|
return relaiFeePayer;
|
|
7545
7546
|
}
|
|
@@ -7623,7 +7624,6 @@ var Relai = class {
|
|
|
7623
7624
|
const integritasMode = integritasFlow === "single" ? "single_signature_fee_included" : integritasFlow === "dual" ? "dual_signature_split" : void 0;
|
|
7624
7625
|
const paymentHeader = req.headers["x-payment"] || req.headers["payment-signature"] || req.headers["x-payment-signature"];
|
|
7625
7626
|
const authHeader = req.headers["authorization"] || "";
|
|
7626
|
-
console.log(`[Relai] MPP check: paymentHeader=${!!paymentHeader}, hasMpp=${!!self.mpp}, authHeader=${authHeader?.slice(0, 30)}`);
|
|
7627
7627
|
if (!paymentHeader && self.mpp && /^Payment\s+/i.test(authHeader)) {
|
|
7628
7628
|
try {
|
|
7629
7629
|
const mppAmount = resolvedPrice.toFixed(6);
|
|
@@ -7635,10 +7635,7 @@ var Relai = class {
|
|
|
7635
7635
|
const mppRequest = new Request(mppUrl, { method: req.method, headers: mppHeaders });
|
|
7636
7636
|
const chargeHandler = self.mpp.charge({ amount: mppAmount });
|
|
7637
7637
|
const mppResult = await chargeHandler(mppRequest);
|
|
7638
|
-
console.log(`[Relai] MPP charge result: status=${mppResult.status}, keys=${Object.keys(mppResult)}, hasChallenge=${!!mppResult.challenge}, hasWithReceipt=${!!mppResult.withReceipt}`);
|
|
7639
7638
|
if (mppResult.status === 402 && mppResult.challenge instanceof Response) {
|
|
7640
|
-
const retryAuth = mppResult.challenge.headers.get("www-authenticate");
|
|
7641
|
-
console.log(`[Relai] MPP re-challenged (credential not accepted). New WWW-Auth: ${retryAuth?.slice(0, 60)}`);
|
|
7642
7639
|
}
|
|
7643
7640
|
if (mppResult.status !== 200 && !mppResult.withReceipt && mppResult.status !== 402) {
|
|
7644
7641
|
if (self.plugins.length > 0) {
|
|
@@ -7681,7 +7678,10 @@ var Relai = class {
|
|
|
7681
7678
|
const dummyResponse = new Response(null);
|
|
7682
7679
|
const receiptResponse = mppResult.withReceipt(dummyResponse);
|
|
7683
7680
|
const receiptHeader = receiptResponse.headers.get("payment-receipt");
|
|
7684
|
-
if (receiptHeader)
|
|
7681
|
+
if (receiptHeader) {
|
|
7682
|
+
res.setHeader?.("Payment-Receipt", receiptHeader);
|
|
7683
|
+
res.setHeader?.("Cache-Control", "private");
|
|
7684
|
+
}
|
|
7685
7685
|
}
|
|
7686
7686
|
options.onPaymentSettled?.(req, {
|
|
7687
7687
|
success: true,
|
|
@@ -7898,12 +7898,13 @@ var Relai = class {
|
|
|
7898
7898
|
if (mppResult?.challenge instanceof Response) {
|
|
7899
7899
|
const wwwAuth = mppResult.challenge.headers.get("www-authenticate");
|
|
7900
7900
|
if (wwwAuth) {
|
|
7901
|
-
res.setHeader("WWW-Authenticate", wwwAuth);
|
|
7901
|
+
res.setHeader?.("WWW-Authenticate", wwwAuth);
|
|
7902
7902
|
}
|
|
7903
7903
|
}
|
|
7904
7904
|
} catch {
|
|
7905
7905
|
}
|
|
7906
7906
|
}
|
|
7907
|
+
res.setHeader?.("Cache-Control", "no-store");
|
|
7907
7908
|
return res.status(402).json(paymentRequiredResponse);
|
|
7908
7909
|
}
|
|
7909
7910
|
let paymentProof;
|
|
@@ -7920,39 +7921,6 @@ var Relai = class {
|
|
|
7920
7921
|
});
|
|
7921
7922
|
}
|
|
7922
7923
|
}
|
|
7923
|
-
if (paymentProof.bridged === true && paymentProof.targetTxId) {
|
|
7924
|
-
console.log(`[Relai] Bridged payment accepted: source=${paymentProof.sourceTxId}, target=${paymentProof.targetTxId}`);
|
|
7925
|
-
const paymentInfo2 = {
|
|
7926
|
-
verified: true,
|
|
7927
|
-
transactionId: paymentProof.targetTxId,
|
|
7928
|
-
payer: paymentProof.sourceTxId || "bridge",
|
|
7929
|
-
network,
|
|
7930
|
-
amount: resolvedPrice
|
|
7931
|
-
};
|
|
7932
|
-
req.payment = paymentInfo2;
|
|
7933
|
-
req.x402Payer = paymentProof.sourceTxId || "bridge";
|
|
7934
|
-
req.x402Paid = true;
|
|
7935
|
-
req.x402Transaction = paymentProof.targetTxId;
|
|
7936
|
-
req.x402Network = network;
|
|
7937
|
-
req.x402Bridged = true;
|
|
7938
|
-
req.x402SourceChain = paymentProof.sourceChain;
|
|
7939
|
-
const paymentResponse2 = {
|
|
7940
|
-
x402Version: 2,
|
|
7941
|
-
scheme: "exact",
|
|
7942
|
-
network: caip2,
|
|
7943
|
-
transaction: paymentProof.targetTxId,
|
|
7944
|
-
payer: paymentProof.sourceTxId,
|
|
7945
|
-
amount: amount2,
|
|
7946
|
-
asset,
|
|
7947
|
-
bridged: true
|
|
7948
|
-
};
|
|
7949
|
-
res.setHeader(
|
|
7950
|
-
"PAYMENT-RESPONSE",
|
|
7951
|
-
Buffer.from(JSON.stringify(paymentResponse2)).toString("base64")
|
|
7952
|
-
);
|
|
7953
|
-
options.onPaymentSettled?.(req, { success: true, transaction: paymentProof.targetTxId, payer: paymentProof.sourceTxId });
|
|
7954
|
-
return next();
|
|
7955
|
-
}
|
|
7956
7924
|
let settlePayTo;
|
|
7957
7925
|
if (stripeConfig) {
|
|
7958
7926
|
settlePayTo = paymentProof.payload?.authorization?.to || paymentProof.accepted?.payTo || "";
|
|
@@ -8099,6 +8067,73 @@ var server_default = Relai;
|
|
|
8099
8067
|
// src/client.ts
|
|
8100
8068
|
var import_web3 = require("@solana/web3.js");
|
|
8101
8069
|
var import_spl_token = require("@solana/spl-token");
|
|
8070
|
+
|
|
8071
|
+
// src/bridge.ts
|
|
8072
|
+
var RELAI_API_BASE = "https://api.relai.fi";
|
|
8073
|
+
var _cacheMap = /* @__PURE__ */ new Map();
|
|
8074
|
+
var CACHE_TTL = 5 * 60 * 1e3;
|
|
8075
|
+
async function getBridgeInfo(baseUrl = RELAI_API_BASE) {
|
|
8076
|
+
const key = baseUrl.replace(/\/$/, "");
|
|
8077
|
+
const now = Date.now();
|
|
8078
|
+
const cached2 = _cacheMap.get(key);
|
|
8079
|
+
if (cached2 && now - cached2.time < CACHE_TTL) return cached2.info;
|
|
8080
|
+
const url2 = `${key}/bridge/info`;
|
|
8081
|
+
const res = await fetch(url2);
|
|
8082
|
+
if (!res.ok) {
|
|
8083
|
+
if (cached2) return cached2.info;
|
|
8084
|
+
throw new Error(`[relai:bridge] Failed to fetch ${url2}: ${res.status}`);
|
|
8085
|
+
}
|
|
8086
|
+
const data = await res.json();
|
|
8087
|
+
const info = {
|
|
8088
|
+
settleEndpoint: data.settleEndpoint,
|
|
8089
|
+
supportedSourceChains: data.supportedSourceChains || [],
|
|
8090
|
+
supportedSourceAssets: data.supportedSourceAssets || [],
|
|
8091
|
+
payTo: data.payTo || {},
|
|
8092
|
+
feePayerSvm: data.feePayerSvm ?? null,
|
|
8093
|
+
feeBps: data.feeBps ?? 100,
|
|
8094
|
+
paymentFacilitator: data.paymentFacilitator || "https://facilitator.x402.fi"
|
|
8095
|
+
};
|
|
8096
|
+
_cacheMap.set(key, { info, time: now });
|
|
8097
|
+
return info;
|
|
8098
|
+
}
|
|
8099
|
+
async function settleBridge(settleEndpoint, body) {
|
|
8100
|
+
const res = await fetch(settleEndpoint, {
|
|
8101
|
+
method: "POST",
|
|
8102
|
+
headers: { "Content-Type": "application/json" },
|
|
8103
|
+
body: JSON.stringify(body)
|
|
8104
|
+
});
|
|
8105
|
+
if (!res.ok) {
|
|
8106
|
+
const err = await res.json().catch(() => ({}));
|
|
8107
|
+
throw new Error(`[relai:bridge] settle failed: ${err.error || res.status}${err.details ? " \u2014 " + err.details : ""}`);
|
|
8108
|
+
}
|
|
8109
|
+
return res.json();
|
|
8110
|
+
}
|
|
8111
|
+
function selectSourceChain(supportedChains, hasEvmWallet, hasSolanaWallet, preferredSourceChainId) {
|
|
8112
|
+
if (preferredSourceChainId && hasEvmWallet) {
|
|
8113
|
+
const preferred = `eip155:${preferredSourceChainId}`;
|
|
8114
|
+
if (supportedChains.includes(preferred)) {
|
|
8115
|
+
return { type: "evm", chain: preferred };
|
|
8116
|
+
}
|
|
8117
|
+
}
|
|
8118
|
+
if (hasSolanaWallet) {
|
|
8119
|
+
const sol = supportedChains.find((c) => c.startsWith("solana:"));
|
|
8120
|
+
if (sol) return { type: "solana", chain: sol };
|
|
8121
|
+
}
|
|
8122
|
+
if (hasEvmWallet) {
|
|
8123
|
+
for (const chain of supportedChains) {
|
|
8124
|
+
if (chain.startsWith("eip155:")) {
|
|
8125
|
+
return { type: "evm", chain };
|
|
8126
|
+
}
|
|
8127
|
+
}
|
|
8128
|
+
}
|
|
8129
|
+
return null;
|
|
8130
|
+
}
|
|
8131
|
+
function computeSourceAmount(targetAmount, feeBps) {
|
|
8132
|
+
const fee = targetAmount * BigInt(feeBps) / 10000n;
|
|
8133
|
+
return targetAmount + fee;
|
|
8134
|
+
}
|
|
8135
|
+
|
|
8136
|
+
// src/client.ts
|
|
8102
8137
|
var PERMIT_NETWORKS = /* @__PURE__ */ new Set([]);
|
|
8103
8138
|
var DEFAULT_EVM_RPC_URLS = {
|
|
8104
8139
|
"skale-base": "https://skale-base.skalenodes.com/v1/base",
|
|
@@ -8124,7 +8159,8 @@ function createX402Client(config2) {
|
|
|
8124
8159
|
integritas,
|
|
8125
8160
|
verbose = false,
|
|
8126
8161
|
defaultHeaders = {},
|
|
8127
|
-
mpp
|
|
8162
|
+
mpp,
|
|
8163
|
+
bridge: bridgeConfig
|
|
8128
8164
|
} = config2;
|
|
8129
8165
|
const relayWsEnabled = relayWs?.enabled === true;
|
|
8130
8166
|
const relayWsPreflightTimeoutMs = relayWs?.preflightTimeoutMs ?? 5e3;
|
|
@@ -8543,6 +8579,38 @@ function createX402Client(config2) {
|
|
|
8543
8579
|
}
|
|
8544
8580
|
return null;
|
|
8545
8581
|
}
|
|
8582
|
+
function extractMppChallengeFromWsError(error48) {
|
|
8583
|
+
if (isRecord(error48) && typeof error48.mppChallenge === "string") {
|
|
8584
|
+
return error48.mppChallenge;
|
|
8585
|
+
}
|
|
8586
|
+
if (isRecord(error48.data) && typeof error48.data.mppChallenge === "string") {
|
|
8587
|
+
return error48.data.mppChallenge;
|
|
8588
|
+
}
|
|
8589
|
+
const metadata = isRecord(error48) ? error48.responseHeaders : void 0;
|
|
8590
|
+
if (isRecord(metadata)) {
|
|
8591
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
8592
|
+
if (key.toLowerCase() === "www-authenticate" && typeof value === "string") {
|
|
8593
|
+
if (/^Payment\s+/i.test(value.trim())) {
|
|
8594
|
+
return value;
|
|
8595
|
+
}
|
|
8596
|
+
}
|
|
8597
|
+
}
|
|
8598
|
+
}
|
|
8599
|
+
return null;
|
|
8600
|
+
}
|
|
8601
|
+
function buildSyntheticMppResponse(mppChallenge, wsError) {
|
|
8602
|
+
const headers = new Headers();
|
|
8603
|
+
headers.set("WWW-Authenticate", mppChallenge);
|
|
8604
|
+
const responseHeaders = isRecord(wsError) ? wsError.responseHeaders : void 0;
|
|
8605
|
+
if (isRecord(responseHeaders)) {
|
|
8606
|
+
for (const [key, value] of Object.entries(responseHeaders)) {
|
|
8607
|
+
if (typeof value === "string" && key.toLowerCase() !== "www-authenticate") {
|
|
8608
|
+
headers.set(key, value);
|
|
8609
|
+
}
|
|
8610
|
+
}
|
|
8611
|
+
}
|
|
8612
|
+
return new Response(null, { status: 402, headers });
|
|
8613
|
+
}
|
|
8546
8614
|
function buildWsResponse(wsResponse) {
|
|
8547
8615
|
const statusFromMetadata = isRecord(wsResponse.metadata) && typeof wsResponse.metadata.status === "number" ? wsResponse.metadata.status : 200;
|
|
8548
8616
|
const status = Number.isInteger(statusFromMetadata) && statusFromMetadata >= 100 && statusFromMetadata <= 599 ? statusFromMetadata : 200;
|
|
@@ -9037,6 +9105,41 @@ function createX402Client(config2) {
|
|
|
9037
9105
|
if (Number(wsPreflightResponse.error.code) !== 402) {
|
|
9038
9106
|
throw new Error(wsPreflightResponse.error.message || "[relai-x402] WebSocket relay request failed");
|
|
9039
9107
|
}
|
|
9108
|
+
if (mpp) {
|
|
9109
|
+
const mppChallenge = extractMppChallengeFromWsError(wsPreflightResponse.error);
|
|
9110
|
+
if (mppChallenge) {
|
|
9111
|
+
log("MPP challenge detected in WS 402 response");
|
|
9112
|
+
try {
|
|
9113
|
+
const syntheticResponse = buildSyntheticMppResponse(mppChallenge, wsPreflightResponse.error);
|
|
9114
|
+
const credential = await mpp.createCredential(syntheticResponse);
|
|
9115
|
+
if (credential) {
|
|
9116
|
+
log("MPP credential created, retrying via WS with Authorization: Payment");
|
|
9117
|
+
wsPaymentPhaseStarted = true;
|
|
9118
|
+
const wsMppResponse = await relayCallOverWebSocket({
|
|
9119
|
+
relayUrl: url2,
|
|
9120
|
+
requestMethod,
|
|
9121
|
+
requestHeaders: {
|
|
9122
|
+
...requestHeaders,
|
|
9123
|
+
"Authorization": credential.startsWith("Payment ") ? credential : `Payment ${credential}`
|
|
9124
|
+
},
|
|
9125
|
+
requestBody,
|
|
9126
|
+
timeoutMs: relayWsPaymentTimeoutMs
|
|
9127
|
+
});
|
|
9128
|
+
if (!wsMppResponse.error) {
|
|
9129
|
+
return buildWsResponse(wsMppResponse);
|
|
9130
|
+
}
|
|
9131
|
+
if (Number(wsMppResponse.error.code) !== 402) {
|
|
9132
|
+
throw new Error(wsMppResponse.error.message || "[relai-x402] WebSocket MPP retry failed");
|
|
9133
|
+
}
|
|
9134
|
+
log("MPP retry via WS still returned 402, falling through to x402 WS flow");
|
|
9135
|
+
wsPaymentPhaseStarted = false;
|
|
9136
|
+
}
|
|
9137
|
+
} catch (mppWsErr) {
|
|
9138
|
+
if (wsPaymentPhaseStarted) throw mppWsErr;
|
|
9139
|
+
log(`MPP over WS failed (${mppWsErr instanceof Error ? mppWsErr.message : mppWsErr}), falling through to x402 WS flow`);
|
|
9140
|
+
}
|
|
9141
|
+
}
|
|
9142
|
+
}
|
|
9040
9143
|
const wsRequirements = extractPaymentRequirementsFromWsError(wsPreflightResponse.error);
|
|
9041
9144
|
if (!wsRequirements) {
|
|
9042
9145
|
throw new Error(
|
|
@@ -9054,6 +9157,17 @@ function createX402Client(config2) {
|
|
|
9054
9157
|
}
|
|
9055
9158
|
const wsSelected = selectAccept(wsAccepts);
|
|
9056
9159
|
if (!wsSelected) {
|
|
9160
|
+
const wsBridge = getBridgeExtension(wsRequirements);
|
|
9161
|
+
if (wsBridge && selectBridgeSource(wsBridge)) {
|
|
9162
|
+
log("No direct wallet match in WS flow \u2014 attempting bridge extension");
|
|
9163
|
+
wsPaymentPhaseStarted = true;
|
|
9164
|
+
const bridgePaymentHeader = await executeBridgePayment(wsBridge, wsAccepts, wsRequirements, url2);
|
|
9165
|
+
log("Retrying with X-PAYMENT header (bridge via WS fallback to HTTP)");
|
|
9166
|
+
return fetch(input, {
|
|
9167
|
+
...stripInternalInit(init) || {},
|
|
9168
|
+
headers: { ...requestHeaders, "X-PAYMENT": bridgePaymentHeader }
|
|
9169
|
+
});
|
|
9170
|
+
}
|
|
9057
9171
|
throw new Error(buildNoWalletError(wsAccepts, true));
|
|
9058
9172
|
}
|
|
9059
9173
|
const { accept: accept2, chain: chain2 } = wsSelected;
|
|
@@ -9161,6 +9275,70 @@ function createX402Client(config2) {
|
|
|
9161
9275
|
headers: { ...requestHeaders, "X-PAYMENT": paymentHeader }
|
|
9162
9276
|
});
|
|
9163
9277
|
}
|
|
9278
|
+
if (bridgeConfig?.enabled) {
|
|
9279
|
+
log("No direct wallet match \u2014 attempting auto-bridge via RelAI API");
|
|
9280
|
+
try {
|
|
9281
|
+
const info = await getBridgeInfo(bridgeConfig.baseUrl);
|
|
9282
|
+
const targetAccept = accepts[0];
|
|
9283
|
+
const hasEvm = !!effectiveWallets.evm;
|
|
9284
|
+
const hasSol = !!hasSolanaWallet;
|
|
9285
|
+
const source = selectSourceChain(info.supportedSourceChains, hasEvm, hasSol);
|
|
9286
|
+
if (source) {
|
|
9287
|
+
const bridgePayTo = info.payTo[source.chain];
|
|
9288
|
+
if (bridgePayTo) {
|
|
9289
|
+
const targetAmount = targetAccept.amount || targetAccept.maxAmountRequired;
|
|
9290
|
+
const sourceAmount = computeSourceAmount(BigInt(targetAmount), info.feeBps).toString();
|
|
9291
|
+
const sourceChainIdx = info.supportedSourceChains.indexOf(source.chain);
|
|
9292
|
+
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")) || "");
|
|
9293
|
+
let sourcePaymentHeader;
|
|
9294
|
+
const sourceAccept = {
|
|
9295
|
+
scheme: "exact",
|
|
9296
|
+
network: source.chain,
|
|
9297
|
+
asset: sourceAsset,
|
|
9298
|
+
payTo: bridgePayTo,
|
|
9299
|
+
amount: sourceAmount,
|
|
9300
|
+
extra: {
|
|
9301
|
+
...targetAccept.extra || {},
|
|
9302
|
+
...source.type === "solana" && info.feePayerSvm ? { feePayer: info.feePayerSvm } : {}
|
|
9303
|
+
}
|
|
9304
|
+
};
|
|
9305
|
+
if (source.type === "solana" && hasSolanaWallet) {
|
|
9306
|
+
sourcePaymentHeader = await buildSolanaPayment(sourceAccept, requirements, url2);
|
|
9307
|
+
} else if (source.type === "evm") {
|
|
9308
|
+
const evmNetwork = normalizeNetwork(source.chain);
|
|
9309
|
+
const usePermit = evmNetwork && PERMIT_NETWORKS.has(evmNetwork);
|
|
9310
|
+
sourcePaymentHeader = usePermit ? await buildEvmPermitPayment(sourceAccept, requirements, url2) : await buildEvmPayment(sourceAccept, requirements, url2);
|
|
9311
|
+
} else {
|
|
9312
|
+
throw new Error(`[relai-x402] No wallet for source chain type: ${source.type}`);
|
|
9313
|
+
}
|
|
9314
|
+
const settleData = await settleBridge(info.settleEndpoint, {
|
|
9315
|
+
sourcePayment: sourcePaymentHeader,
|
|
9316
|
+
sourceChain: source.chain,
|
|
9317
|
+
targetAccept: {
|
|
9318
|
+
scheme: "exact",
|
|
9319
|
+
network: targetAccept.network,
|
|
9320
|
+
asset: targetAccept.asset,
|
|
9321
|
+
payTo: targetAccept.payTo,
|
|
9322
|
+
amount: targetAmount
|
|
9323
|
+
},
|
|
9324
|
+
requirements,
|
|
9325
|
+
resource: url2,
|
|
9326
|
+
paymentFacilitator: info.paymentFacilitator
|
|
9327
|
+
});
|
|
9328
|
+
if (settleData.xPayment) {
|
|
9329
|
+
log(`Auto-bridge settled: target=${settleData.targetTxId}`);
|
|
9330
|
+
return fetch(input, {
|
|
9331
|
+
...requestInitWithHeaders,
|
|
9332
|
+
headers: { ...requestHeaders, "X-PAYMENT": settleData.xPayment }
|
|
9333
|
+
});
|
|
9334
|
+
}
|
|
9335
|
+
throw new Error("[relai-x402] Bridge settle did not return xPayment");
|
|
9336
|
+
}
|
|
9337
|
+
}
|
|
9338
|
+
} catch (bridgeErr) {
|
|
9339
|
+
log(`Auto-bridge failed: ${bridgeErr instanceof Error ? bridgeErr.message : bridgeErr}`);
|
|
9340
|
+
}
|
|
9341
|
+
}
|
|
9164
9342
|
throw new Error(buildNoWalletError(accepts, false));
|
|
9165
9343
|
}
|
|
9166
9344
|
const { accept, chain } = selected;
|
|
@@ -22856,8 +23034,50 @@ var charge = Method_exports.from({
|
|
|
22856
23034
|
}
|
|
22857
23035
|
});
|
|
22858
23036
|
|
|
22859
|
-
// src/mpp/
|
|
23037
|
+
// src/mpp/verify-erc20.ts
|
|
22860
23038
|
var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
23039
|
+
async function verifyErc20Transfer(opts) {
|
|
23040
|
+
const { txHash, rpcUrl, tokenAddress, recipient, expectedAmount } = opts;
|
|
23041
|
+
let receipt = null;
|
|
23042
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
23043
|
+
const receiptRes = await fetch(rpcUrl, {
|
|
23044
|
+
method: "POST",
|
|
23045
|
+
headers: { "Content-Type": "application/json" },
|
|
23046
|
+
body: JSON.stringify({
|
|
23047
|
+
jsonrpc: "2.0",
|
|
23048
|
+
id: 1,
|
|
23049
|
+
method: "eth_getTransactionReceipt",
|
|
23050
|
+
params: [txHash]
|
|
23051
|
+
})
|
|
23052
|
+
});
|
|
23053
|
+
const receiptData = await receiptRes.json();
|
|
23054
|
+
if (receiptData.error) {
|
|
23055
|
+
throw new Error(`RPC error: ${receiptData.error.message}`);
|
|
23056
|
+
}
|
|
23057
|
+
receipt = receiptData.result;
|
|
23058
|
+
if (receipt) break;
|
|
23059
|
+
await new Promise((r) => setTimeout(r, (attempt + 1) * 1e3));
|
|
23060
|
+
}
|
|
23061
|
+
if (!receipt) {
|
|
23062
|
+
throw new Error("Transaction not found or not yet confirmed");
|
|
23063
|
+
}
|
|
23064
|
+
if (receipt.status !== "0x1") {
|
|
23065
|
+
throw new Error("Transaction failed on-chain");
|
|
23066
|
+
}
|
|
23067
|
+
const recipientPadded = "0x" + recipient.slice(2).toLowerCase().padStart(64, "0");
|
|
23068
|
+
const tokenLower = tokenAddress.toLowerCase();
|
|
23069
|
+
const matchingLog = receipt.logs.find((log) => {
|
|
23070
|
+
if (log.address.toLowerCase() !== tokenLower) return false;
|
|
23071
|
+
if (log.topics[0] !== TRANSFER_EVENT_TOPIC) return false;
|
|
23072
|
+
if (log.topics[2]?.toLowerCase() !== recipientPadded) return false;
|
|
23073
|
+
return BigInt(log.data) >= expectedAmount;
|
|
23074
|
+
});
|
|
23075
|
+
if (!matchingLog) {
|
|
23076
|
+
throw new Error("No matching ERC-20 Transfer found for recipient and amount");
|
|
23077
|
+
}
|
|
23078
|
+
}
|
|
23079
|
+
|
|
23080
|
+
// src/mpp/evm-server.ts
|
|
22861
23081
|
function evmCharge(config2) {
|
|
22862
23082
|
const {
|
|
22863
23083
|
recipient,
|
|
@@ -22899,39 +23119,13 @@ function evmCharge(config2) {
|
|
|
22899
23119
|
if (!txHash || !txHash.startsWith("0x")) {
|
|
22900
23120
|
throw new Error("Missing or invalid transaction hash in credential payload");
|
|
22901
23121
|
}
|
|
22902
|
-
|
|
22903
|
-
|
|
22904
|
-
|
|
22905
|
-
|
|
22906
|
-
|
|
22907
|
-
|
|
22908
|
-
id: 1,
|
|
22909
|
-
method: "eth_getTransactionReceipt",
|
|
22910
|
-
params: [txHash]
|
|
22911
|
-
})
|
|
22912
|
-
});
|
|
22913
|
-
const receiptData = await receiptRes.json();
|
|
22914
|
-
if (receiptData.error) {
|
|
22915
|
-
throw new Error(`RPC error: ${receiptData.error.message}`);
|
|
22916
|
-
}
|
|
22917
|
-
const receipt = receiptData.result;
|
|
22918
|
-
if (!receipt) {
|
|
22919
|
-
throw new Error("Transaction not found or not yet confirmed");
|
|
22920
|
-
}
|
|
22921
|
-
if (receipt.status !== "0x1") {
|
|
22922
|
-
throw new Error("Transaction failed on-chain");
|
|
22923
|
-
}
|
|
22924
|
-
const recipientPadded = "0x" + recipient.slice(2).toLowerCase().padStart(64, "0");
|
|
22925
|
-
const tokenLower = tokenAddress.toLowerCase();
|
|
22926
|
-
const matchingLog = receipt.logs.find((log) => {
|
|
22927
|
-
if (log.address.toLowerCase() !== tokenLower) return false;
|
|
22928
|
-
if (log.topics[0] !== TRANSFER_EVENT_TOPIC) return false;
|
|
22929
|
-
if (log.topics[2]?.toLowerCase() !== recipientPadded) return false;
|
|
22930
|
-
return BigInt(log.data) >= expectedAmount;
|
|
23122
|
+
await verifyErc20Transfer({
|
|
23123
|
+
txHash,
|
|
23124
|
+
rpcUrl,
|
|
23125
|
+
tokenAddress,
|
|
23126
|
+
recipient,
|
|
23127
|
+
expectedAmount: BigInt(cred.challenge.request.amount)
|
|
22931
23128
|
});
|
|
22932
|
-
if (!matchingLog) {
|
|
22933
|
-
throw new Error("No matching ERC-20 Transfer found for recipient and amount");
|
|
22934
|
-
}
|
|
22935
23129
|
return Receipt_exports.from({
|
|
22936
23130
|
method: "evm",
|
|
22937
23131
|
reference: txHash,
|
|
@@ -26257,6 +26451,65 @@ async function waitForReceipt(rpcUrl, txHash, timeoutMs = 3e4) {
|
|
|
26257
26451
|
}
|
|
26258
26452
|
return false;
|
|
26259
26453
|
}
|
|
26454
|
+
|
|
26455
|
+
// src/mpp/bridge-method.ts
|
|
26456
|
+
var charge2 = Method_exports.from({
|
|
26457
|
+
intent: "charge",
|
|
26458
|
+
name: "bridge",
|
|
26459
|
+
schema: {
|
|
26460
|
+
credential: {
|
|
26461
|
+
payload: zod_exports.object({
|
|
26462
|
+
/** "settled" = client called bridge settle, targetTxHash is proof */
|
|
26463
|
+
type: zod_exports.string(),
|
|
26464
|
+
/** Target chain tx hash (0x-prefixed) — server verifies on-chain */
|
|
26465
|
+
targetTxHash: zod_exports.optional(zod_exports.string()),
|
|
26466
|
+
/** Source chain tx hash/signature (for auditing) */
|
|
26467
|
+
sourceTxHash: zod_exports.optional(zod_exports.string()),
|
|
26468
|
+
/** CAIP-2 source chain used */
|
|
26469
|
+
sourceChain: zod_exports.optional(zod_exports.string())
|
|
26470
|
+
})
|
|
26471
|
+
},
|
|
26472
|
+
request: zod_exports.object({
|
|
26473
|
+
/** Amount in target token base units */
|
|
26474
|
+
amount: zod_exports.string(),
|
|
26475
|
+
/** Target ERC-20 token contract address */
|
|
26476
|
+
currency: zod_exports.string(),
|
|
26477
|
+
/** Target chain recipient address (merchant) */
|
|
26478
|
+
recipient: zod_exports.string(),
|
|
26479
|
+
/** Human-readable description */
|
|
26480
|
+
description: zod_exports.optional(zod_exports.string()),
|
|
26481
|
+
methodDetails: zod_exports.object({
|
|
26482
|
+
/** Target chain ID (where the merchant gets paid) */
|
|
26483
|
+
targetChainId: zod_exports.number(),
|
|
26484
|
+
/** Human-readable target network name (e.g. "skale-base") */
|
|
26485
|
+
targetNetwork: zod_exports.optional(zod_exports.string()),
|
|
26486
|
+
/** Target chain RPC URL (for server-side verification) */
|
|
26487
|
+
targetRpcUrl: zod_exports.optional(zod_exports.string()),
|
|
26488
|
+
/** Target token decimals */
|
|
26489
|
+
targetDecimals: zod_exports.optional(zod_exports.number()),
|
|
26490
|
+
/** Bridge settle endpoint URL */
|
|
26491
|
+
settleEndpoint: zod_exports.string(),
|
|
26492
|
+
/** Supported source chains (CAIP-2 format, e.g. ["eip155:8453", "solana:5eykt4..."]) */
|
|
26493
|
+
supportedSourceChains: zod_exports.array(zod_exports.string()),
|
|
26494
|
+
/** Supported source token addresses */
|
|
26495
|
+
supportedSourceAssets: zod_exports.optional(zod_exports.array(zod_exports.string())),
|
|
26496
|
+
/** Bridge receiver addresses per source chain: { [caip2]: address } */
|
|
26497
|
+
payToMap: zod_exports.record(zod_exports.string(), zod_exports.string()),
|
|
26498
|
+
/** Solana fee payer address (bridge facilitator sponsors gas) */
|
|
26499
|
+
feePayerSvm: zod_exports.optional(zod_exports.string()),
|
|
26500
|
+
/** Bridge fee in basis points */
|
|
26501
|
+
feeBps: zod_exports.optional(zod_exports.number()),
|
|
26502
|
+
/** Payment facilitator URL */
|
|
26503
|
+
paymentFacilitator: zod_exports.optional(zod_exports.string()),
|
|
26504
|
+
/** Unique reference ID (for replay protection) */
|
|
26505
|
+
reference: zod_exports.string()
|
|
26506
|
+
})
|
|
26507
|
+
})
|
|
26508
|
+
}
|
|
26509
|
+
});
|
|
26510
|
+
|
|
26511
|
+
// src/mpp/bridge-server.ts
|
|
26512
|
+
var BRIDGE_INFO_TTL_MS = 5 * 60 * 1e3;
|
|
26260
26513
|
// Annotate the CommonJS export names for ESM import in node:
|
|
26261
26514
|
0 && (module.exports = {
|
|
26262
26515
|
BASE_MAINNET_NETWORK,
|