@relai-fi/x402 0.6.9 → 0.6.10
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/dist/index.cjs +760 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +146 -4
- package/dist/index.d.ts +146 -4
- package/dist/index.js +752 -40
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2118,10 +2118,14 @@ __export(index_exports, {
|
|
|
2118
2118
|
USDC_SOLANA: () => USDC_SOLANA,
|
|
2119
2119
|
cancelPaymentCode: () => cancelPaymentCode,
|
|
2120
2120
|
cancelSolanaPaymentCode: () => cancelSolanaPaymentCode,
|
|
2121
|
+
cancelStoredPaymentCode: () => cancelStoredPaymentCode,
|
|
2122
|
+
claimPaymentLink: () => claimPaymentLink,
|
|
2121
2123
|
convertPayloadToVersion: () => convertPayloadToVersion,
|
|
2122
2124
|
convertV1ToV2: () => convertV1ToV2,
|
|
2123
2125
|
convertV2ToV1: () => convertV2ToV1,
|
|
2124
2126
|
createPayRequest: () => createPayRequest,
|
|
2127
|
+
createPaymentCode: () => createPaymentCode,
|
|
2128
|
+
createPaymentCodesBatch: () => createPaymentCodesBatch,
|
|
2125
2129
|
createPrivateKeySigner: () => createPrivateKeySigner,
|
|
2126
2130
|
createX402Client: () => createX402Client,
|
|
2127
2131
|
default: () => Relai,
|
|
@@ -2136,11 +2140,13 @@ __export(index_exports, {
|
|
|
2136
2140
|
generateSolanaPaymentCode: () => generateSolanaPaymentCode,
|
|
2137
2141
|
getPayRequest: () => getPayRequest,
|
|
2138
2142
|
getPaymentCode: () => getPaymentCode,
|
|
2143
|
+
getPaymentCodeDetails: () => getPaymentCodeDetails,
|
|
2139
2144
|
getSolanaPaymentCode: () => getSolanaPaymentCode,
|
|
2140
2145
|
isEvm: () => isEvm,
|
|
2141
2146
|
isEvmNetwork: () => isEvmNetwork,
|
|
2142
2147
|
isSolana: () => isSolana,
|
|
2143
2148
|
isSolanaNetwork: () => isSolanaNetwork,
|
|
2149
|
+
listOwnerPaymentCodes: () => listOwnerPaymentCodes,
|
|
2144
2150
|
networkV1ToV2: () => networkV1ToV2,
|
|
2145
2151
|
networkV2ToV1: () => networkV2ToV1,
|
|
2146
2152
|
normalizeNetwork: () => normalizeNetwork,
|
|
@@ -2148,8 +2154,10 @@ __export(index_exports, {
|
|
|
2148
2154
|
payPayRequest: () => payPayRequest,
|
|
2149
2155
|
payPayRequestWithCode: () => payPayRequestWithCode,
|
|
2150
2156
|
payPayRequestWithSolana: () => payPayRequestWithSolana,
|
|
2157
|
+
payPayRequestWithStoredCode: () => payPayRequestWithStoredCode,
|
|
2151
2158
|
redeemPaymentCode: () => redeemPaymentCode,
|
|
2152
2159
|
redeemSolanaPaymentCode: () => redeemSolanaPaymentCode,
|
|
2160
|
+
redeemStoredPaymentCode: () => redeemStoredPaymentCode,
|
|
2153
2161
|
resolveToken: () => resolveToken,
|
|
2154
2162
|
stripePayTo: () => stripePayTo,
|
|
2155
2163
|
submitRelayFeedback: () => submitRelayFeedback,
|
|
@@ -4520,19 +4528,26 @@ function submitRelayFeedback(config2) {
|
|
|
4520
4528
|
|
|
4521
4529
|
// src/solana-payment-codes.ts
|
|
4522
4530
|
var DEFAULT_FACILITATOR = "https://relai.fi/facilitator";
|
|
4523
|
-
function generateCode() {
|
|
4531
|
+
function generateCode(network, claimLink = false) {
|
|
4524
4532
|
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
4525
|
-
const
|
|
4533
|
+
const randomLength = claimLink ? 6 : 7;
|
|
4534
|
+
const bytes = new Uint8Array(randomLength);
|
|
4526
4535
|
if (typeof globalThis !== "undefined" && globalThis.crypto?.getRandomValues) {
|
|
4527
4536
|
globalThis.crypto.getRandomValues(bytes);
|
|
4528
4537
|
} else {
|
|
4529
4538
|
const { randomBytes } = require("crypto");
|
|
4530
|
-
randomBytes(
|
|
4539
|
+
randomBytes(randomLength).copy(Buffer.from(bytes.buffer));
|
|
4531
4540
|
}
|
|
4532
|
-
let code = "";
|
|
4533
|
-
|
|
4541
|
+
let code = network === "solana-devnet" ? "D" : "S";
|
|
4542
|
+
if (claimLink) code += "Z";
|
|
4543
|
+
for (let i = 0; i < bytes.length; i++) code += chars[bytes[i] % chars.length];
|
|
4534
4544
|
return code;
|
|
4535
4545
|
}
|
|
4546
|
+
function buildClaimUrl(facilitatorUrl, claimToken) {
|
|
4547
|
+
if (!claimToken) return null;
|
|
4548
|
+
const origin = new URL(facilitatorUrl).origin;
|
|
4549
|
+
return new URL(`/claim/${claimToken}`, origin).toString();
|
|
4550
|
+
}
|
|
4536
4551
|
async function anchorDisc(name) {
|
|
4537
4552
|
const preimage = new TextEncoder().encode(`global:${name}`);
|
|
4538
4553
|
if (typeof globalThis !== "undefined" && globalThis.crypto?.subtle) {
|
|
@@ -4542,14 +4557,21 @@ async function anchorDisc(name) {
|
|
|
4542
4557
|
const { createHash } = require("crypto");
|
|
4543
4558
|
return new Uint8Array(createHash("sha256").update(preimage).digest()).slice(0, 8);
|
|
4544
4559
|
}
|
|
4560
|
+
function writeBigInt64LE(target, value, offset) {
|
|
4561
|
+
let remaining = BigInt.asUintN(64, value);
|
|
4562
|
+
for (let index = 0; index < 8; index += 1) {
|
|
4563
|
+
target[offset + index] = Number(remaining & 0xffn);
|
|
4564
|
+
remaining >>= 8n;
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4545
4567
|
async function buildInstructionData(name, codeBytes, extra) {
|
|
4546
4568
|
const disc = await anchorDisc(name);
|
|
4547
4569
|
if (name === "create_vault" && extra) {
|
|
4548
4570
|
const buf2 = Buffer.alloc(32);
|
|
4549
4571
|
Buffer.from(disc).copy(buf2, 0);
|
|
4550
4572
|
Buffer.from(codeBytes).copy(buf2, 8);
|
|
4551
|
-
buf2
|
|
4552
|
-
|
|
4573
|
+
writeBigInt64LE(buf2, extra.amount, 16);
|
|
4574
|
+
writeBigInt64LE(buf2, extra.validUntil, 24);
|
|
4553
4575
|
return buf2;
|
|
4554
4576
|
}
|
|
4555
4577
|
const buf = Buffer.alloc(16);
|
|
@@ -4559,7 +4581,7 @@ async function buildInstructionData(name, codeBytes, extra) {
|
|
|
4559
4581
|
}
|
|
4560
4582
|
async function generateSolanaPaymentCode(config2, wallet, params) {
|
|
4561
4583
|
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR;
|
|
4562
|
-
const { solanaRpcUrl, ttlSeconds = 86400 } = params;
|
|
4584
|
+
const { solanaRpcUrl, ttlSeconds = 86400, claimLink = false } = params;
|
|
4563
4585
|
const amount2 = BigInt(params.amount);
|
|
4564
4586
|
if (amount2 <= 0n) throw new Error("amount must be positive");
|
|
4565
4587
|
if (!wallet.publicKey) throw new Error("Solana wallet not connected");
|
|
@@ -4570,7 +4592,7 @@ async function generateSolanaPaymentCode(config2, wallet, params) {
|
|
|
4570
4592
|
const relayerAddr = relayerInfo.address;
|
|
4571
4593
|
const programId = relayerInfo.programId;
|
|
4572
4594
|
const usdcMint = relayerInfo.networks?.[network]?.usdc ?? (network === "solana-devnet" ? "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU" : "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
|
4573
|
-
const code = generateCode();
|
|
4595
|
+
const code = generateCode(network, claimLink);
|
|
4574
4596
|
const codeBytes = new TextEncoder().encode(code);
|
|
4575
4597
|
const {
|
|
4576
4598
|
Connection: Connection2,
|
|
@@ -4602,6 +4624,8 @@ async function generateSolanaPaymentCode(config2, wallet, params) {
|
|
|
4602
4624
|
const createVaultIx = new TransactionInstruction({
|
|
4603
4625
|
programId: programPK,
|
|
4604
4626
|
keys: [
|
|
4627
|
+
{ pubkey: relayerPK, isSigner: true, isWritable: true },
|
|
4628
|
+
// caller / fee payer
|
|
4605
4629
|
{ pubkey: buyerPK, isSigner: true, isWritable: true },
|
|
4606
4630
|
// buyer
|
|
4607
4631
|
{ pubkey: vaultPda, isSigner: false, isWritable: true },
|
|
@@ -4633,7 +4657,9 @@ async function generateSolanaPaymentCode(config2, wallet, params) {
|
|
|
4633
4657
|
code,
|
|
4634
4658
|
amount: amount2.toString(),
|
|
4635
4659
|
network,
|
|
4636
|
-
ttlSeconds
|
|
4660
|
+
ttlSeconds,
|
|
4661
|
+
claimLink,
|
|
4662
|
+
...params.description ? { description: params.description } : {}
|
|
4637
4663
|
};
|
|
4638
4664
|
const res = await fetch(`${facilitatorUrl}/solana-payment-codes`, {
|
|
4639
4665
|
method: "POST",
|
|
@@ -4644,7 +4670,129 @@ async function generateSolanaPaymentCode(config2, wallet, params) {
|
|
|
4644
4670
|
const err = await res.json().catch(() => ({}));
|
|
4645
4671
|
throw new Error(err.error ?? `generateSolanaPaymentCode failed: ${res.status}`);
|
|
4646
4672
|
}
|
|
4647
|
-
|
|
4673
|
+
const payload = await res.json();
|
|
4674
|
+
if (payload.claimLink === true) {
|
|
4675
|
+
const claimToken = typeof payload.claimToken === "string" ? payload.claimToken : null;
|
|
4676
|
+
const sanitized = { ...payload };
|
|
4677
|
+
delete sanitized.claimToken;
|
|
4678
|
+
delete sanitized.id;
|
|
4679
|
+
delete sanitized.code;
|
|
4680
|
+
return {
|
|
4681
|
+
...sanitized,
|
|
4682
|
+
claimLink: true,
|
|
4683
|
+
claimUrl: buildClaimUrl(facilitatorUrl, claimToken)
|
|
4684
|
+
};
|
|
4685
|
+
}
|
|
4686
|
+
return payload;
|
|
4687
|
+
}
|
|
4688
|
+
async function generateSolanaPaymentCodesBatch(config2, wallet, params) {
|
|
4689
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR;
|
|
4690
|
+
const items = Array.isArray(params.items) ? params.items : [];
|
|
4691
|
+
if (items.length === 0) throw new Error("items must not be empty");
|
|
4692
|
+
if (items.length > 10) throw new Error("Maximum 10 Solana batch items are supported per transaction");
|
|
4693
|
+
if (!wallet.publicKey) throw new Error("Solana wallet not connected");
|
|
4694
|
+
const infoRes = await fetch(`${facilitatorUrl}/solana-payment-codes/relayer-info`);
|
|
4695
|
+
if (!infoRes.ok) throw new Error("Failed to fetch Solana relayer info");
|
|
4696
|
+
const relayerInfo = await infoRes.json();
|
|
4697
|
+
const network = params.network ?? relayerInfo.defaultNetwork ?? "solana";
|
|
4698
|
+
const relayerAddr = relayerInfo.address;
|
|
4699
|
+
const programId = relayerInfo.programId;
|
|
4700
|
+
const usdcMint = relayerInfo.networks?.[network]?.usdc ?? (network === "solana-devnet" ? "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU" : "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
|
4701
|
+
const {
|
|
4702
|
+
Connection: Connection2,
|
|
4703
|
+
PublicKey: PublicKey2,
|
|
4704
|
+
TransactionMessage: TransactionMessage2,
|
|
4705
|
+
VersionedTransaction: VersionedTransaction2,
|
|
4706
|
+
TransactionInstruction
|
|
4707
|
+
} = await import("@solana/web3.js");
|
|
4708
|
+
const {
|
|
4709
|
+
getAssociatedTokenAddressSync,
|
|
4710
|
+
TOKEN_PROGRAM_ID: TOKEN_PROGRAM_ID2,
|
|
4711
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
4712
|
+
} = await import("@solana/spl-token");
|
|
4713
|
+
const rpcUrl = params.solanaRpcUrl ?? (network === "solana-devnet" ? "https://api.devnet.solana.com" : "https://api.mainnet-beta.solana.com");
|
|
4714
|
+
const connection = new Connection2(rpcUrl, "confirmed");
|
|
4715
|
+
const programPK = new PublicKey2(programId);
|
|
4716
|
+
const mintPK = new PublicKey2(usdcMint);
|
|
4717
|
+
const buyerPK = new PublicKey2(wallet.publicKey.toString());
|
|
4718
|
+
const relayerPK = new PublicKey2(relayerAddr);
|
|
4719
|
+
const buyerAta = getAssociatedTokenAddressSync(mintPK, buyerPK, false, TOKEN_PROGRAM_ID2);
|
|
4720
|
+
const systemProgram = new PublicKey2("11111111111111111111111111111111");
|
|
4721
|
+
const usedCodes = /* @__PURE__ */ new Set();
|
|
4722
|
+
const preparedItems = [];
|
|
4723
|
+
const instructions = [];
|
|
4724
|
+
let totalAmount = 0n;
|
|
4725
|
+
for (const item of items) {
|
|
4726
|
+
const amount2 = BigInt(item.amount);
|
|
4727
|
+
if (amount2 <= 0n) throw new Error("Each batch item amount must be positive");
|
|
4728
|
+
const ttlSeconds = Math.min(Math.max(Math.round(Number(item.ttlSeconds ?? 86400)), 60), 604800);
|
|
4729
|
+
const validUntil = BigInt(Math.floor(Date.now() / 1e3) + ttlSeconds);
|
|
4730
|
+
let code = generateCode(network, false);
|
|
4731
|
+
while (usedCodes.has(code)) code = generateCode(network, false);
|
|
4732
|
+
usedCodes.add(code);
|
|
4733
|
+
const codeBytes = new TextEncoder().encode(code);
|
|
4734
|
+
const [vaultPda] = PublicKey2.findProgramAddressSync(
|
|
4735
|
+
[Buffer.from("vault"), Buffer.from(codeBytes)],
|
|
4736
|
+
programPK
|
|
4737
|
+
);
|
|
4738
|
+
const vaultAta = getAssociatedTokenAddressSync(mintPK, vaultPda, true, TOKEN_PROGRAM_ID2);
|
|
4739
|
+
const ixData = await buildInstructionData("create_vault", codeBytes, { amount: amount2, validUntil });
|
|
4740
|
+
instructions.push(new TransactionInstruction({
|
|
4741
|
+
programId: programPK,
|
|
4742
|
+
keys: [
|
|
4743
|
+
{ pubkey: relayerPK, isSigner: true, isWritable: true },
|
|
4744
|
+
{ pubkey: buyerPK, isSigner: true, isWritable: true },
|
|
4745
|
+
{ pubkey: vaultPda, isSigner: false, isWritable: true },
|
|
4746
|
+
{ pubkey: vaultAta, isSigner: false, isWritable: true },
|
|
4747
|
+
{ pubkey: buyerAta, isSigner: false, isWritable: true },
|
|
4748
|
+
{ pubkey: mintPK, isSigner: false, isWritable: false },
|
|
4749
|
+
{ pubkey: TOKEN_PROGRAM_ID2, isSigner: false, isWritable: false },
|
|
4750
|
+
{ pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
4751
|
+
{ pubkey: systemProgram, isSigner: false, isWritable: false }
|
|
4752
|
+
],
|
|
4753
|
+
data: ixData
|
|
4754
|
+
}));
|
|
4755
|
+
preparedItems.push({
|
|
4756
|
+
code,
|
|
4757
|
+
amount: amount2,
|
|
4758
|
+
ttlSeconds,
|
|
4759
|
+
description: item.description,
|
|
4760
|
+
validUntil
|
|
4761
|
+
});
|
|
4762
|
+
totalAmount += amount2;
|
|
4763
|
+
}
|
|
4764
|
+
const { blockhash } = await connection.getLatestBlockhash("confirmed");
|
|
4765
|
+
const message = new TransactionMessage2({
|
|
4766
|
+
payerKey: relayerPK,
|
|
4767
|
+
recentBlockhash: blockhash,
|
|
4768
|
+
instructions
|
|
4769
|
+
}).compileToV0Message();
|
|
4770
|
+
const tx = new VersionedTransaction2(message);
|
|
4771
|
+
const signed = await wallet.signTransaction(tx);
|
|
4772
|
+
const serialized = Buffer.from(signed.serialize()).toString("base64");
|
|
4773
|
+
const res = await fetch(`${facilitatorUrl}/solana-payment-codes/batch`, {
|
|
4774
|
+
method: "POST",
|
|
4775
|
+
headers: { "Content-Type": "application/json" },
|
|
4776
|
+
body: JSON.stringify({
|
|
4777
|
+
transaction: serialized,
|
|
4778
|
+
network,
|
|
4779
|
+
items: preparedItems.map((item) => ({
|
|
4780
|
+
code: item.code,
|
|
4781
|
+
amount: item.amount.toString(),
|
|
4782
|
+
ttlSeconds: item.ttlSeconds,
|
|
4783
|
+
...item.description ? { description: item.description } : {}
|
|
4784
|
+
}))
|
|
4785
|
+
})
|
|
4786
|
+
});
|
|
4787
|
+
if (!res.ok) {
|
|
4788
|
+
const err = await res.json().catch(() => ({}));
|
|
4789
|
+
throw new Error(err.error ?? `generateSolanaPaymentCodesBatch failed: ${res.status}`);
|
|
4790
|
+
}
|
|
4791
|
+
const payload = await res.json();
|
|
4792
|
+
return {
|
|
4793
|
+
...payload,
|
|
4794
|
+
totalAmount: typeof payload.totalAmount === "string" ? payload.totalAmount : totalAmount.toString()
|
|
4795
|
+
};
|
|
4648
4796
|
}
|
|
4649
4797
|
async function getSolanaPaymentCode(config2, code) {
|
|
4650
4798
|
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR;
|
|
@@ -4714,6 +4862,8 @@ async function cancelSolanaPaymentCode(config2, code, wallet, options = {}) {
|
|
|
4714
4862
|
const cancelIx = new TransactionInstruction({
|
|
4715
4863
|
programId: programPK,
|
|
4716
4864
|
keys: [
|
|
4865
|
+
{ pubkey: relayerPK, isSigner: true, isWritable: true },
|
|
4866
|
+
// caller / fee payer
|
|
4717
4867
|
{ pubkey: buyerPK, isSigner: true, isWritable: true },
|
|
4718
4868
|
// buyer (must sign)
|
|
4719
4869
|
{ pubkey: vaultPda, isSigner: false, isWritable: true },
|
|
@@ -4803,6 +4953,7 @@ function createPrivateKeySigner(privateKey) {
|
|
|
4803
4953
|
};
|
|
4804
4954
|
}
|
|
4805
4955
|
var DEFAULT_FACILITATOR2 = "https://relai.fi/facilitator";
|
|
4956
|
+
var AUTHORIZATION_WINDOW_SECONDS = 3600;
|
|
4806
4957
|
function randomBytes32() {
|
|
4807
4958
|
const bytes = new Uint8Array(32);
|
|
4808
4959
|
if (typeof globalThis.crypto !== "undefined") {
|
|
@@ -4820,6 +4971,14 @@ async function fetchToAddress(facilitatorUrl, network) {
|
|
|
4820
4971
|
if (!data.toAddress) throw new Error("Facilitator returned no toAddress");
|
|
4821
4972
|
return data.toAddress;
|
|
4822
4973
|
}
|
|
4974
|
+
async function fetchEscrowAddress(facilitatorUrl, network) {
|
|
4975
|
+
const res = await fetch(`${facilitatorUrl}/payment-codes/relayer-address`);
|
|
4976
|
+
if (!res.ok) throw new Error("Failed to fetch escrow address from facilitator");
|
|
4977
|
+
const data = await res.json();
|
|
4978
|
+
const escrowAddress = data.escrowAddresses?.[network];
|
|
4979
|
+
if (!escrowAddress) throw new Error(`Facilitator returned no escrow address for ${network}`);
|
|
4980
|
+
return escrowAddress;
|
|
4981
|
+
}
|
|
4823
4982
|
async function signEip3009(signer, net, from4, toAddress, value, validAfter, validBefore, nonce, usdcOverride) {
|
|
4824
4983
|
const domain2 = {
|
|
4825
4984
|
name: net.domainName,
|
|
@@ -4894,8 +5053,7 @@ async function generatePaymentCodesBatch(config2, params) {
|
|
|
4894
5053
|
codes,
|
|
4895
5054
|
payee,
|
|
4896
5055
|
usdcContract,
|
|
4897
|
-
network = "base-sepolia"
|
|
4898
|
-
authToken
|
|
5056
|
+
network = "base-sepolia"
|
|
4899
5057
|
} = params;
|
|
4900
5058
|
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR2;
|
|
4901
5059
|
const net = NETWORK_CONFIGS[network];
|
|
@@ -4905,44 +5063,76 @@ async function generatePaymentCodesBatch(config2, params) {
|
|
|
4905
5063
|
const usdc = usdcContract ?? net.usdc;
|
|
4906
5064
|
const now = Math.floor(Date.now() / 1e3);
|
|
4907
5065
|
const toAddress = await fetchToAddress(facilitatorUrl, network);
|
|
4908
|
-
const
|
|
4909
|
-
|
|
5066
|
+
const escrowAddress = await fetchEscrowAddress(facilitatorUrl, network);
|
|
5067
|
+
const results = [];
|
|
5068
|
+
const failed = [];
|
|
5069
|
+
for (let index = 0; index < codes.length; index += 1) {
|
|
5070
|
+
const item = codes[index];
|
|
5071
|
+
try {
|
|
4910
5072
|
const validBefore = now + (item.ttl ?? 86400);
|
|
5073
|
+
const authorizationValidBefore = Math.min(validBefore, now + AUTHORIZATION_WINDOW_SECONDS);
|
|
4911
5074
|
const nonce = randomBytes32();
|
|
4912
5075
|
const value = BigInt(item.value).toString();
|
|
5076
|
+
const authorization = {
|
|
5077
|
+
from: from4,
|
|
5078
|
+
to: escrowAddress,
|
|
5079
|
+
value,
|
|
5080
|
+
validAfter: 0,
|
|
5081
|
+
validBefore: authorizationValidBefore,
|
|
5082
|
+
nonce
|
|
5083
|
+
};
|
|
4913
5084
|
const signature2 = await signEip3009(
|
|
4914
5085
|
signer,
|
|
4915
5086
|
net,
|
|
4916
5087
|
from4,
|
|
4917
|
-
|
|
5088
|
+
escrowAddress,
|
|
4918
5089
|
value,
|
|
4919
|
-
|
|
4920
|
-
validBefore,
|
|
4921
|
-
nonce,
|
|
5090
|
+
authorization.validAfter,
|
|
5091
|
+
authorization.validBefore,
|
|
5092
|
+
authorization.nonce,
|
|
4922
5093
|
usdc
|
|
4923
5094
|
);
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
5095
|
+
const res = await fetch(`${facilitatorUrl}/payment-codes`, {
|
|
5096
|
+
method: "POST",
|
|
5097
|
+
headers: { "Content-Type": "application/json" },
|
|
5098
|
+
body: JSON.stringify({
|
|
5099
|
+
from: from4,
|
|
5100
|
+
value,
|
|
5101
|
+
validBefore,
|
|
5102
|
+
usdcContract: usdc,
|
|
5103
|
+
settlementNetwork: net.settlementNetwork,
|
|
5104
|
+
escrowMode: true,
|
|
5105
|
+
...payee ? { payee } : {},
|
|
5106
|
+
signature: signature2,
|
|
5107
|
+
authorization
|
|
5108
|
+
})
|
|
5109
|
+
});
|
|
5110
|
+
if (!res.ok) {
|
|
5111
|
+
const err = await res.json().catch(() => ({}));
|
|
5112
|
+
failed.push({ index, error: String(err.error ?? err.detail ?? res.status) });
|
|
5113
|
+
continue;
|
|
5114
|
+
}
|
|
5115
|
+
const payload = await res.json();
|
|
5116
|
+
results.push({
|
|
5117
|
+
code: String(payload.code ?? ""),
|
|
5118
|
+
validBefore: Number(payload.validBefore ?? validBefore),
|
|
5119
|
+
expiresIn: Number(payload.expiresIn ?? validBefore - Math.floor(Date.now() / 1e3)),
|
|
5120
|
+
locked: Boolean(payload.locked ?? payee)
|
|
5121
|
+
});
|
|
5122
|
+
} catch (error48) {
|
|
5123
|
+
failed.push({
|
|
5124
|
+
index,
|
|
5125
|
+
error: error48 instanceof Error ? error48.message : String(error48)
|
|
5126
|
+
});
|
|
5127
|
+
}
|
|
4944
5128
|
}
|
|
4945
|
-
return
|
|
5129
|
+
return {
|
|
5130
|
+
registered: results.length,
|
|
5131
|
+
codes: results,
|
|
5132
|
+
failed,
|
|
5133
|
+
relayerAddress: toAddress,
|
|
5134
|
+
settlementNetwork: net.settlementNetwork
|
|
5135
|
+
};
|
|
4946
5136
|
}
|
|
4947
5137
|
async function redeemPaymentCode(config2, code, payee) {
|
|
4948
5138
|
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR2;
|
|
@@ -5211,6 +5401,528 @@ async function payPayRequestWithSolana(config2, requestCode, wallet, options = {
|
|
|
5211
5401
|
return res.json();
|
|
5212
5402
|
}
|
|
5213
5403
|
|
|
5404
|
+
// src/current-payment-codes.ts
|
|
5405
|
+
var import_ethers3 = require("ethers");
|
|
5406
|
+
var import_bs58 = __toESM(require("bs58"), 1);
|
|
5407
|
+
var DEFAULT_FACILITATOR4 = "https://relai.fi/facilitator";
|
|
5408
|
+
var TRANSFER_WITH_AUTHORIZATION_DOMAIN_VERSION = "2";
|
|
5409
|
+
var AUTHORIZATION_WINDOW_SECONDS2 = 3600;
|
|
5410
|
+
var EVM_ESCROW_ABI = ["function cancel(bytes8 code) external"];
|
|
5411
|
+
var EIP3009_TYPES3 = {
|
|
5412
|
+
TransferWithAuthorization: [
|
|
5413
|
+
{ name: "from", type: "address" },
|
|
5414
|
+
{ name: "to", type: "address" },
|
|
5415
|
+
{ name: "value", type: "uint256" },
|
|
5416
|
+
{ name: "validAfter", type: "uint256" },
|
|
5417
|
+
{ name: "validBefore", type: "uint256" },
|
|
5418
|
+
{ name: "nonce", type: "bytes32" }
|
|
5419
|
+
]
|
|
5420
|
+
};
|
|
5421
|
+
function isEvmAddress(value) {
|
|
5422
|
+
return /^0x[0-9a-fA-F]{40}$/i.test(value);
|
|
5423
|
+
}
|
|
5424
|
+
function codeToBytes8(code) {
|
|
5425
|
+
const normalized = code.trim().toUpperCase();
|
|
5426
|
+
const bytes = import_ethers3.ethers.toUtf8Bytes(normalized);
|
|
5427
|
+
if (bytes.length !== 8) throw new Error("Payment code must be exactly 8 characters long");
|
|
5428
|
+
return import_ethers3.ethers.hexlify(bytes);
|
|
5429
|
+
}
|
|
5430
|
+
function randomBytes323() {
|
|
5431
|
+
const bytes = new Uint8Array(32);
|
|
5432
|
+
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto?.getRandomValues) {
|
|
5433
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
5434
|
+
} else {
|
|
5435
|
+
const { randomBytes } = require("crypto");
|
|
5436
|
+
randomBytes(32).copy(Buffer.from(bytes.buffer));
|
|
5437
|
+
}
|
|
5438
|
+
return `0x${Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
5439
|
+
}
|
|
5440
|
+
function extractClaimToken(claimUrlOrToken) {
|
|
5441
|
+
const normalized = claimUrlOrToken.trim();
|
|
5442
|
+
if (!normalized) throw new Error("Claim URL or claim token is required");
|
|
5443
|
+
const directValue = normalized.split("?")[0]?.split("#")[0]?.trim() ?? "";
|
|
5444
|
+
if (!directValue.includes("/")) return directValue;
|
|
5445
|
+
try {
|
|
5446
|
+
const url2 = new URL(normalized);
|
|
5447
|
+
const segments = url2.pathname.split("/").filter(Boolean);
|
|
5448
|
+
return segments[segments.length - 1] ?? "";
|
|
5449
|
+
} catch {
|
|
5450
|
+
const segments = directValue.split("/").filter(Boolean);
|
|
5451
|
+
return segments[segments.length - 1] ?? "";
|
|
5452
|
+
}
|
|
5453
|
+
}
|
|
5454
|
+
function buildClaimUrl2(facilitatorUrl, claimToken) {
|
|
5455
|
+
if (!claimToken) return null;
|
|
5456
|
+
const origin = new URL(facilitatorUrl).origin;
|
|
5457
|
+
return new URL(`/claim/${claimToken}`, origin).toString();
|
|
5458
|
+
}
|
|
5459
|
+
function buildCancelAuthorizationMessage(code, owner, issuedAt) {
|
|
5460
|
+
return [
|
|
5461
|
+
"RelAI Codes",
|
|
5462
|
+
"Authorize payment code cancellation",
|
|
5463
|
+
`Code: ${code.trim().toUpperCase()}`,
|
|
5464
|
+
`Owner: ${owner.toLowerCase()}`,
|
|
5465
|
+
`Issued At: ${issuedAt}`
|
|
5466
|
+
].join("\n");
|
|
5467
|
+
}
|
|
5468
|
+
function buildClaimLinkCancelAuthorizationMessage(claimToken, owner, issuedAt) {
|
|
5469
|
+
return [
|
|
5470
|
+
"RelAI Codes",
|
|
5471
|
+
"Authorize claim link cancellation",
|
|
5472
|
+
`Claim Token: ${claimToken}`,
|
|
5473
|
+
`Owner: ${owner.toLowerCase()}`,
|
|
5474
|
+
`Issued At: ${issuedAt}`
|
|
5475
|
+
].join("\n");
|
|
5476
|
+
}
|
|
5477
|
+
function buildEvmClaimLinkAuthorizationMessage(params) {
|
|
5478
|
+
return [
|
|
5479
|
+
"RelAI EVM Claim Link",
|
|
5480
|
+
`Claim Token: ${params.claimToken}`,
|
|
5481
|
+
`Claimer: ${params.claimer.toLowerCase()}`,
|
|
5482
|
+
`Mode: ${params.mode}`,
|
|
5483
|
+
`Target Address: ${params.targetAddress ?? "-"}`,
|
|
5484
|
+
`Target Network: ${params.targetNetwork ?? "-"}`,
|
|
5485
|
+
`Issued At: ${params.issuedAt}`
|
|
5486
|
+
].join("\n");
|
|
5487
|
+
}
|
|
5488
|
+
function buildSolanaClaimLinkAuthorizationMessage(params) {
|
|
5489
|
+
return [
|
|
5490
|
+
"RelAI Solana Claim Link",
|
|
5491
|
+
`Claim Token: ${params.claimToken}`,
|
|
5492
|
+
`Claimer: ${params.claimer}`,
|
|
5493
|
+
`Mode: ${params.mode}`,
|
|
5494
|
+
`Target Address: ${params.targetAddress ?? "-"}`,
|
|
5495
|
+
`Target Network: ${params.targetNetwork ?? "-"}`,
|
|
5496
|
+
`Issued At: ${params.issuedAt}`
|
|
5497
|
+
].join("\n");
|
|
5498
|
+
}
|
|
5499
|
+
async function readJson(response) {
|
|
5500
|
+
return response.json().catch(() => ({}));
|
|
5501
|
+
}
|
|
5502
|
+
async function fetchWithPayload(url2, init) {
|
|
5503
|
+
const response = await fetch(url2, init);
|
|
5504
|
+
const payload = await readJson(response);
|
|
5505
|
+
return { response, payload };
|
|
5506
|
+
}
|
|
5507
|
+
function buildRequestError(response, payload) {
|
|
5508
|
+
return new Error(String(payload.error || payload.detail || payload.message || `Request failed (${response.status})`));
|
|
5509
|
+
}
|
|
5510
|
+
function isNotFoundResponse(response, payload) {
|
|
5511
|
+
return response.status === 404 || payload.errorCode === "not_found";
|
|
5512
|
+
}
|
|
5513
|
+
async function fetchStoredCodeWithFallback(facilitatorUrl, normalizedCode, options = {}) {
|
|
5514
|
+
const suffix = options.suffix ? `/${options.suffix.replace(/^\/+/, "")}` : "";
|
|
5515
|
+
const attempts = [
|
|
5516
|
+
{ kind: "evm", url: `${facilitatorUrl}/payment-codes/${normalizedCode}${suffix}` },
|
|
5517
|
+
{ kind: "solana", url: `${facilitatorUrl}/solana-payment-codes/${normalizedCode}${suffix}` }
|
|
5518
|
+
];
|
|
5519
|
+
let lastError = null;
|
|
5520
|
+
for (const attempt of attempts) {
|
|
5521
|
+
const { response, payload } = await fetchWithPayload(attempt.url, options.init);
|
|
5522
|
+
if (response.ok) {
|
|
5523
|
+
return { kind: attempt.kind, payload };
|
|
5524
|
+
}
|
|
5525
|
+
if (!isNotFoundResponse(response, payload)) {
|
|
5526
|
+
throw buildRequestError(response, payload);
|
|
5527
|
+
}
|
|
5528
|
+
lastError = buildRequestError(response, payload);
|
|
5529
|
+
}
|
|
5530
|
+
throw lastError ?? new Error("Payment code not found");
|
|
5531
|
+
}
|
|
5532
|
+
async function fetchOrThrow(url2, init) {
|
|
5533
|
+
const { response, payload } = await fetchWithPayload(url2, init);
|
|
5534
|
+
if (!response.ok) {
|
|
5535
|
+
throw buildRequestError(response, payload);
|
|
5536
|
+
}
|
|
5537
|
+
return payload;
|
|
5538
|
+
}
|
|
5539
|
+
function sanitizeClaimLinkResponse(payload, facilitatorUrl, fallbackClaimToken) {
|
|
5540
|
+
const claimToken = typeof payload.claimToken === "string" ? payload.claimToken : fallbackClaimToken ?? null;
|
|
5541
|
+
const sanitized = { ...payload };
|
|
5542
|
+
delete sanitized.claimToken;
|
|
5543
|
+
delete sanitized.id;
|
|
5544
|
+
return {
|
|
5545
|
+
...sanitized,
|
|
5546
|
+
claimUrl: buildClaimUrl2(facilitatorUrl, claimToken)
|
|
5547
|
+
};
|
|
5548
|
+
}
|
|
5549
|
+
async function createPaymentCode(config2, params) {
|
|
5550
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR4;
|
|
5551
|
+
const ttlSeconds = Math.max(60, Math.round(Number(params.ttlSeconds ?? 86400)));
|
|
5552
|
+
if ("wallet" in params) {
|
|
5553
|
+
const amount3 = BigInt(params.amount);
|
|
5554
|
+
if (amount3 <= 0n) throw new Error("amount must be positive");
|
|
5555
|
+
return generateSolanaPaymentCode(config2, params.wallet, {
|
|
5556
|
+
amount: amount3,
|
|
5557
|
+
network: params.network,
|
|
5558
|
+
claimLink: params.claimLink === true,
|
|
5559
|
+
description: params.description,
|
|
5560
|
+
ttlSeconds
|
|
5561
|
+
});
|
|
5562
|
+
}
|
|
5563
|
+
const network = params.network ?? "base-sepolia";
|
|
5564
|
+
const net = NETWORK_CONFIGS[network];
|
|
5565
|
+
if (!net) throw new Error(`Unsupported network: ${network}`);
|
|
5566
|
+
const amount2 = BigInt(params.amount);
|
|
5567
|
+
if (amount2 <= 0n) throw new Error("amount must be positive");
|
|
5568
|
+
const relayerConfig = await fetchOrThrow(`${facilitatorUrl}/payment-codes/relayer-address`);
|
|
5569
|
+
const escrowAddresses = relayerConfig.escrowAddresses ?? {};
|
|
5570
|
+
const escrowAddress = escrowAddresses[network];
|
|
5571
|
+
if (!escrowAddress) throw new Error(`EVM escrow is not configured for ${network}`);
|
|
5572
|
+
const from4 = await params.signer.getAddress();
|
|
5573
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
5574
|
+
const validBefore = now + ttlSeconds;
|
|
5575
|
+
const authorizationValidBefore = String(Math.min(validBefore, now + AUTHORIZATION_WINDOW_SECONDS2));
|
|
5576
|
+
const authorizationNonce = randomBytes323();
|
|
5577
|
+
const usdcContract = params.usdcContract ?? net.usdc;
|
|
5578
|
+
const domain2 = {
|
|
5579
|
+
name: net.domainName,
|
|
5580
|
+
version: TRANSFER_WITH_AUTHORIZATION_DOMAIN_VERSION,
|
|
5581
|
+
chainId: net.chainId,
|
|
5582
|
+
verifyingContract: usdcContract
|
|
5583
|
+
};
|
|
5584
|
+
const authorization = {
|
|
5585
|
+
from: from4,
|
|
5586
|
+
to: escrowAddress,
|
|
5587
|
+
value: amount2.toString(),
|
|
5588
|
+
validAfter: "0",
|
|
5589
|
+
validBefore: authorizationValidBefore,
|
|
5590
|
+
nonce: authorizationNonce
|
|
5591
|
+
};
|
|
5592
|
+
const signature2 = await params.signer.signTypedData(domain2, EIP3009_TYPES3, authorization);
|
|
5593
|
+
const payload = await fetchOrThrow(`${facilitatorUrl}/payment-codes`, {
|
|
5594
|
+
method: "POST",
|
|
5595
|
+
headers: { "Content-Type": "application/json" },
|
|
5596
|
+
body: JSON.stringify({
|
|
5597
|
+
from: from4,
|
|
5598
|
+
value: amount2.toString(),
|
|
5599
|
+
validBefore,
|
|
5600
|
+
usdcContract,
|
|
5601
|
+
settlementNetwork: network,
|
|
5602
|
+
escrowMode: true,
|
|
5603
|
+
claimLink: params.claimLink === true,
|
|
5604
|
+
...params.description ? { description: params.description } : {},
|
|
5605
|
+
...params.payee ? { payee: params.payee } : {},
|
|
5606
|
+
signature: signature2,
|
|
5607
|
+
authorization
|
|
5608
|
+
})
|
|
5609
|
+
});
|
|
5610
|
+
return params.claimLink === true ? sanitizeClaimLinkResponse(payload, facilitatorUrl) : payload;
|
|
5611
|
+
}
|
|
5612
|
+
async function createPaymentCodesBatch(config2, params) {
|
|
5613
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR4;
|
|
5614
|
+
if (!Array.isArray(params.codes) || params.codes.length === 0) {
|
|
5615
|
+
throw new Error("codes must not be empty");
|
|
5616
|
+
}
|
|
5617
|
+
if ("wallet" in params) {
|
|
5618
|
+
const items = params.codes.map((item) => ({
|
|
5619
|
+
amount: item.amount,
|
|
5620
|
+
ttlSeconds: item.ttlSeconds,
|
|
5621
|
+
description: item.description
|
|
5622
|
+
}));
|
|
5623
|
+
return generateSolanaPaymentCodesBatch(config2, params.wallet, {
|
|
5624
|
+
network: params.network,
|
|
5625
|
+
items
|
|
5626
|
+
});
|
|
5627
|
+
}
|
|
5628
|
+
const network = params.network ?? "base-sepolia";
|
|
5629
|
+
const net = NETWORK_CONFIGS[network];
|
|
5630
|
+
if (!net) throw new Error(`Unsupported network: ${network}`);
|
|
5631
|
+
const relayerConfig = await fetchOrThrow(`${facilitatorUrl}/payment-codes/relayer-address`);
|
|
5632
|
+
const escrowAddresses = relayerConfig.escrowAddresses ?? {};
|
|
5633
|
+
const escrowAddress = escrowAddresses[network];
|
|
5634
|
+
if (!escrowAddress) throw new Error(`EVM escrow is not configured for ${network}`);
|
|
5635
|
+
const from4 = await params.signer.getAddress();
|
|
5636
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
5637
|
+
const usdcContract = params.usdcContract ?? net.usdc;
|
|
5638
|
+
const normalizedCodes = params.codes.map((item) => {
|
|
5639
|
+
const amount2 = BigInt(item.amount);
|
|
5640
|
+
if (amount2 <= 0n) throw new Error("Each batch code amount must be positive");
|
|
5641
|
+
const ttlSeconds = Math.max(60, Math.round(Number(item.ttlSeconds ?? 86400)));
|
|
5642
|
+
return {
|
|
5643
|
+
value: amount2,
|
|
5644
|
+
validBefore: now + ttlSeconds,
|
|
5645
|
+
description: item.description?.trim() || void 0
|
|
5646
|
+
};
|
|
5647
|
+
});
|
|
5648
|
+
const totalAmount = normalizedCodes.reduce((sum, item) => sum + item.value, 0n);
|
|
5649
|
+
const authorizationValidBefore = String(now + AUTHORIZATION_WINDOW_SECONDS2);
|
|
5650
|
+
const authorizationNonce = randomBytes323();
|
|
5651
|
+
const domain2 = {
|
|
5652
|
+
name: net.domainName,
|
|
5653
|
+
version: TRANSFER_WITH_AUTHORIZATION_DOMAIN_VERSION,
|
|
5654
|
+
chainId: net.chainId,
|
|
5655
|
+
verifyingContract: usdcContract
|
|
5656
|
+
};
|
|
5657
|
+
const authorization = {
|
|
5658
|
+
from: from4,
|
|
5659
|
+
to: escrowAddress,
|
|
5660
|
+
value: totalAmount.toString(),
|
|
5661
|
+
validAfter: "0",
|
|
5662
|
+
validBefore: authorizationValidBefore,
|
|
5663
|
+
nonce: authorizationNonce
|
|
5664
|
+
};
|
|
5665
|
+
const signature2 = await params.signer.signTypedData(domain2, EIP3009_TYPES3, authorization);
|
|
5666
|
+
return fetchOrThrow(`${facilitatorUrl}/payment-codes/batch-funded`, {
|
|
5667
|
+
method: "POST",
|
|
5668
|
+
headers: { "Content-Type": "application/json" },
|
|
5669
|
+
body: JSON.stringify({
|
|
5670
|
+
from: from4,
|
|
5671
|
+
usdcContract,
|
|
5672
|
+
settlementNetwork: network,
|
|
5673
|
+
...params.payee ? { payee: params.payee } : {},
|
|
5674
|
+
signature: signature2,
|
|
5675
|
+
authorization,
|
|
5676
|
+
codes: normalizedCodes.map((item) => ({
|
|
5677
|
+
value: item.value.toString(),
|
|
5678
|
+
validBefore: item.validBefore,
|
|
5679
|
+
...item.description ? { description: item.description } : {}
|
|
5680
|
+
}))
|
|
5681
|
+
})
|
|
5682
|
+
});
|
|
5683
|
+
}
|
|
5684
|
+
async function listOwnerPaymentCodes(config2, params) {
|
|
5685
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR4;
|
|
5686
|
+
if (params.walletType === "solana") {
|
|
5687
|
+
if (!params.wallet.publicKey) throw new Error("Solana wallet not connected");
|
|
5688
|
+
const walletAddress2 = params.wallet.publicKey.toString();
|
|
5689
|
+
const challenge2 = await fetchOrThrow(`${facilitatorUrl}/payment-codes/owner/challenge`, {
|
|
5690
|
+
method: "POST",
|
|
5691
|
+
headers: { "Content-Type": "application/json" },
|
|
5692
|
+
body: JSON.stringify({ walletAddress: walletAddress2, walletType: "solana" })
|
|
5693
|
+
});
|
|
5694
|
+
const message2 = typeof challenge2.message === "string" ? challenge2.message : "";
|
|
5695
|
+
if (!message2) throw new Error("Failed to load owner challenge message");
|
|
5696
|
+
const signatureBytes = await params.wallet.signMessage(new TextEncoder().encode(message2));
|
|
5697
|
+
const session2 = await fetchOrThrow(`${facilitatorUrl}/payment-codes/owner/session`, {
|
|
5698
|
+
method: "POST",
|
|
5699
|
+
headers: { "Content-Type": "application/json" },
|
|
5700
|
+
body: JSON.stringify({ walletAddress: walletAddress2, walletType: "solana", signature: import_bs58.default.encode(signatureBytes) })
|
|
5701
|
+
});
|
|
5702
|
+
const accessToken2 = typeof session2.accessToken === "string" ? session2.accessToken : "";
|
|
5703
|
+
if (!accessToken2) throw new Error("Failed to create owner session");
|
|
5704
|
+
return fetchOrThrow(`${facilitatorUrl}/payment-codes/owner/codes`, {
|
|
5705
|
+
headers: { Authorization: `Bearer ${accessToken2}` }
|
|
5706
|
+
});
|
|
5707
|
+
}
|
|
5708
|
+
const walletAddress = await params.wallet.getAddress();
|
|
5709
|
+
const challenge = await fetchOrThrow(`${facilitatorUrl}/payment-codes/owner/challenge`, {
|
|
5710
|
+
method: "POST",
|
|
5711
|
+
headers: { "Content-Type": "application/json" },
|
|
5712
|
+
body: JSON.stringify({ walletAddress, walletType: "evm" })
|
|
5713
|
+
});
|
|
5714
|
+
const message = typeof challenge.message === "string" ? challenge.message : "";
|
|
5715
|
+
if (!message) throw new Error("Failed to load owner challenge message");
|
|
5716
|
+
const session = await fetchOrThrow(`${facilitatorUrl}/payment-codes/owner/session`, {
|
|
5717
|
+
method: "POST",
|
|
5718
|
+
headers: { "Content-Type": "application/json" },
|
|
5719
|
+
body: JSON.stringify({ walletAddress, walletType: "evm", signature: await params.wallet.signMessage(message) })
|
|
5720
|
+
});
|
|
5721
|
+
const accessToken = typeof session.accessToken === "string" ? session.accessToken : "";
|
|
5722
|
+
if (!accessToken) throw new Error("Failed to create owner session");
|
|
5723
|
+
return fetchOrThrow(`${facilitatorUrl}/payment-codes/owner/codes`, {
|
|
5724
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
5725
|
+
});
|
|
5726
|
+
}
|
|
5727
|
+
async function getPaymentCode2(config2, code) {
|
|
5728
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR4;
|
|
5729
|
+
const normalizedCode = code.trim().toUpperCase();
|
|
5730
|
+
const { payload } = await fetchStoredCodeWithFallback(facilitatorUrl, normalizedCode);
|
|
5731
|
+
return payload;
|
|
5732
|
+
}
|
|
5733
|
+
var getPaymentCodeDetails = getPaymentCode2;
|
|
5734
|
+
async function redeemStoredPaymentCode(config2, code, params) {
|
|
5735
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR4;
|
|
5736
|
+
const normalizedCode = code.trim().toUpperCase();
|
|
5737
|
+
const { payload } = await fetchStoredCodeWithFallback(facilitatorUrl, normalizedCode, {
|
|
5738
|
+
suffix: "redeem",
|
|
5739
|
+
init: {
|
|
5740
|
+
method: "POST",
|
|
5741
|
+
headers: { "Content-Type": "application/json" },
|
|
5742
|
+
body: JSON.stringify({
|
|
5743
|
+
payee: params.payee,
|
|
5744
|
+
...params.evmNetwork ? { evmNetwork: params.evmNetwork } : {},
|
|
5745
|
+
...params.solanaNetwork ? { solanaNetwork: params.solanaNetwork } : {}
|
|
5746
|
+
})
|
|
5747
|
+
}
|
|
5748
|
+
});
|
|
5749
|
+
return payload;
|
|
5750
|
+
}
|
|
5751
|
+
async function cancelStoredPaymentCode(config2, code, params = {}) {
|
|
5752
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR4;
|
|
5753
|
+
const normalizedCode = code.trim().toUpperCase();
|
|
5754
|
+
const statusInfo = await fetchStoredCodeWithFallback(facilitatorUrl, normalizedCode);
|
|
5755
|
+
if (statusInfo.kind === "solana") {
|
|
5756
|
+
if (!params.solanaWallet) {
|
|
5757
|
+
throw new Error("A Solana wallet is required to cancel this payment code");
|
|
5758
|
+
}
|
|
5759
|
+
return cancelSolanaPaymentCode(config2, normalizedCode, params.solanaWallet, {
|
|
5760
|
+
network: params.network === "solana" || params.network === "solana-devnet" ? params.network : void 0
|
|
5761
|
+
});
|
|
5762
|
+
}
|
|
5763
|
+
if (!params.wallet) {
|
|
5764
|
+
throw new Error("An EVM wallet is required to cancel this payment code");
|
|
5765
|
+
}
|
|
5766
|
+
const status = statusInfo.payload;
|
|
5767
|
+
const owner = await params.wallet.getAddress();
|
|
5768
|
+
const network = params.network ?? String(status.settlementNetwork || "base-sepolia");
|
|
5769
|
+
if (status.claimLink === true && typeof status.claimToken === "string" && status.escrowMode === true) {
|
|
5770
|
+
const issuedAt2 = Date.now();
|
|
5771
|
+
return fetchOrThrow(`${facilitatorUrl}/payment-codes/claim-links/evm/${status.claimToken}/cancel`, {
|
|
5772
|
+
method: "POST",
|
|
5773
|
+
headers: { "Content-Type": "application/json" },
|
|
5774
|
+
body: JSON.stringify({
|
|
5775
|
+
owner,
|
|
5776
|
+
issuedAt: issuedAt2,
|
|
5777
|
+
signature: await params.wallet.signMessage(buildClaimLinkCancelAuthorizationMessage(status.claimToken, owner, issuedAt2))
|
|
5778
|
+
})
|
|
5779
|
+
});
|
|
5780
|
+
}
|
|
5781
|
+
if (status.escrowMode === true) {
|
|
5782
|
+
if (!params.wallet.sendTransaction) {
|
|
5783
|
+
throw new Error("This EVM wallet does not support sendTransaction, which is required for escrow cancellation");
|
|
5784
|
+
}
|
|
5785
|
+
const relayerConfig = await fetchOrThrow(`${facilitatorUrl}/payment-codes/relayer-address`);
|
|
5786
|
+
const escrowAddresses = relayerConfig.escrowAddresses ?? {};
|
|
5787
|
+
const escrowAddress = escrowAddresses[network];
|
|
5788
|
+
if (!escrowAddress) throw new Error(`EVM escrow is not configured for ${network}`);
|
|
5789
|
+
const cancelInterface = new import_ethers3.ethers.Interface(EVM_ESCROW_ABI);
|
|
5790
|
+
const tx = await params.wallet.sendTransaction({
|
|
5791
|
+
to: escrowAddress,
|
|
5792
|
+
data: cancelInterface.encodeFunctionData("cancel", [codeToBytes8(normalizedCode)])
|
|
5793
|
+
});
|
|
5794
|
+
await tx.wait();
|
|
5795
|
+
return {
|
|
5796
|
+
success: true,
|
|
5797
|
+
code: normalizedCode,
|
|
5798
|
+
cancelTxHash: tx.hash,
|
|
5799
|
+
network
|
|
5800
|
+
};
|
|
5801
|
+
}
|
|
5802
|
+
const issuedAt = Date.now();
|
|
5803
|
+
return fetchOrThrow(`${facilitatorUrl}/payment-codes/${normalizedCode}/cancel`, {
|
|
5804
|
+
method: "POST",
|
|
5805
|
+
headers: { "Content-Type": "application/json" },
|
|
5806
|
+
body: JSON.stringify({
|
|
5807
|
+
owner,
|
|
5808
|
+
issuedAt,
|
|
5809
|
+
signature: await params.wallet.signMessage(buildCancelAuthorizationMessage(normalizedCode, owner, issuedAt))
|
|
5810
|
+
})
|
|
5811
|
+
});
|
|
5812
|
+
}
|
|
5813
|
+
async function claimPaymentLink(config2, claimUrlOrToken, params) {
|
|
5814
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR4;
|
|
5815
|
+
const claimToken = extractClaimToken(claimUrlOrToken);
|
|
5816
|
+
if (!claimToken) throw new Error("Claim URL or claim token is required");
|
|
5817
|
+
const issuedAt = Date.now();
|
|
5818
|
+
const mode = params.mode ?? "claim-usdc";
|
|
5819
|
+
if ("solanaWallet" in params && params.solanaWallet) {
|
|
5820
|
+
if (!params.solanaWallet.publicKey) throw new Error("Solana wallet not connected");
|
|
5821
|
+
const claimer2 = params.solanaWallet.publicKey.toString();
|
|
5822
|
+
const requestedPayee2 = params.payee?.trim() || claimer2;
|
|
5823
|
+
const targetAddress2 = mode === "claim-usdc" ? isEvmAddress(requestedPayee2) ? requestedPayee2.toLowerCase() : requestedPayee2 : null;
|
|
5824
|
+
const targetNetwork2 = mode === "claim-usdc" ? targetAddress2 == null ? null : isEvmAddress(targetAddress2) ? params.evmNetwork ?? null : params.solanaNetwork ?? null : null;
|
|
5825
|
+
const message2 = buildSolanaClaimLinkAuthorizationMessage({
|
|
5826
|
+
claimToken,
|
|
5827
|
+
claimer: claimer2,
|
|
5828
|
+
issuedAt,
|
|
5829
|
+
mode,
|
|
5830
|
+
targetAddress: targetAddress2,
|
|
5831
|
+
targetNetwork: targetNetwork2
|
|
5832
|
+
});
|
|
5833
|
+
const signatureBytes = await params.solanaWallet.signMessage(new TextEncoder().encode(message2));
|
|
5834
|
+
const payload2 = await fetchOrThrow(`${facilitatorUrl}/solana-payment-codes/claim-links/${claimToken}/claim`, {
|
|
5835
|
+
method: "POST",
|
|
5836
|
+
headers: { "Content-Type": "application/json" },
|
|
5837
|
+
body: JSON.stringify({
|
|
5838
|
+
claimer: claimer2,
|
|
5839
|
+
issuedAt,
|
|
5840
|
+
signature: import_bs58.default.encode(signatureBytes),
|
|
5841
|
+
mode,
|
|
5842
|
+
...targetAddress2 ? { targetAddress: targetAddress2 } : {},
|
|
5843
|
+
...targetNetwork2 ? { targetNetwork: targetNetwork2 } : {}
|
|
5844
|
+
})
|
|
5845
|
+
});
|
|
5846
|
+
return sanitizeClaimLinkResponse(payload2, facilitatorUrl, claimToken);
|
|
5847
|
+
}
|
|
5848
|
+
if (!("wallet" in params) || !params.wallet) {
|
|
5849
|
+
throw new Error("An EVM wallet or Solana wallet is required to claim this payment link");
|
|
5850
|
+
}
|
|
5851
|
+
const claimer = (await params.wallet.getAddress()).toLowerCase();
|
|
5852
|
+
const requestedPayee = params.payee?.trim() || claimer;
|
|
5853
|
+
const targetAddress = mode === "claim-usdc" ? isEvmAddress(requestedPayee) ? requestedPayee.toLowerCase() : requestedPayee : null;
|
|
5854
|
+
const targetNetwork = mode === "claim-usdc" ? targetAddress == null ? null : isEvmAddress(targetAddress) ? params.evmNetwork ?? null : params.solanaNetwork ?? null : null;
|
|
5855
|
+
const message = buildEvmClaimLinkAuthorizationMessage({
|
|
5856
|
+
claimToken,
|
|
5857
|
+
claimer,
|
|
5858
|
+
issuedAt,
|
|
5859
|
+
mode,
|
|
5860
|
+
targetAddress,
|
|
5861
|
+
targetNetwork
|
|
5862
|
+
});
|
|
5863
|
+
const payload = await fetchOrThrow(`${facilitatorUrl}/payment-codes/claim-links/evm/${claimToken}/claim`, {
|
|
5864
|
+
method: "POST",
|
|
5865
|
+
headers: { "Content-Type": "application/json" },
|
|
5866
|
+
body: JSON.stringify({
|
|
5867
|
+
claimer,
|
|
5868
|
+
issuedAt,
|
|
5869
|
+
signature: await params.wallet.signMessage(message),
|
|
5870
|
+
mode,
|
|
5871
|
+
...targetAddress ? { targetAddress, payee: targetAddress } : {},
|
|
5872
|
+
...targetNetwork ? { targetNetwork } : {},
|
|
5873
|
+
...targetAddress && isEvmAddress(targetAddress) && targetNetwork ? { evmNetwork: targetNetwork } : {},
|
|
5874
|
+
...targetAddress && !isEvmAddress(targetAddress) && targetNetwork ? { solanaNetwork: targetNetwork } : {}
|
|
5875
|
+
})
|
|
5876
|
+
});
|
|
5877
|
+
return sanitizeClaimLinkResponse(payload, facilitatorUrl, claimToken);
|
|
5878
|
+
}
|
|
5879
|
+
async function payPayRequestWithStoredCode(config2, requestCode, paymentCode, options = {}) {
|
|
5880
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR4;
|
|
5881
|
+
const normalizedRequestCode = requestCode.trim().toUpperCase();
|
|
5882
|
+
const normalizedPaymentCode = paymentCode.trim().toUpperCase();
|
|
5883
|
+
const codeStatusInfo = await fetchStoredCodeWithFallback(facilitatorUrl, normalizedPaymentCode);
|
|
5884
|
+
if (codeStatusInfo.kind === "solana") {
|
|
5885
|
+
return fetchOrThrow(`${facilitatorUrl}/solana-payment-codes/${normalizedPaymentCode}/pay-request`, {
|
|
5886
|
+
method: "POST",
|
|
5887
|
+
headers: { "Content-Type": "application/json" },
|
|
5888
|
+
body: JSON.stringify({
|
|
5889
|
+
requestCode: normalizedRequestCode,
|
|
5890
|
+
...options.changeAddress ? { changeAddress: options.changeAddress } : {}
|
|
5891
|
+
})
|
|
5892
|
+
});
|
|
5893
|
+
}
|
|
5894
|
+
const requestInfo = await getPayRequest(config2, normalizedRequestCode);
|
|
5895
|
+
const codeStatus = codeStatusInfo.payload;
|
|
5896
|
+
const allowOverpayment = options.allowOverpayment ?? true;
|
|
5897
|
+
const returnChange = options.returnChange ?? "code";
|
|
5898
|
+
const codeValue = BigInt(String(codeStatus.value ?? 0));
|
|
5899
|
+
const requestAmount = BigInt(requestInfo.amount);
|
|
5900
|
+
if (codeValue < requestAmount) {
|
|
5901
|
+
throw new Error(
|
|
5902
|
+
`Payment code value (${Number(codeValue) / 1e6} USDC) is less than the request amount (${Number(requestAmount) / 1e6} USDC)`
|
|
5903
|
+
);
|
|
5904
|
+
}
|
|
5905
|
+
if (!allowOverpayment && codeValue > requestAmount) {
|
|
5906
|
+
throw new Error(
|
|
5907
|
+
`Payment code value (${Number(codeValue) / 1e6} USDC) exceeds the request amount (${Number(requestAmount) / 1e6} USDC)`
|
|
5908
|
+
);
|
|
5909
|
+
}
|
|
5910
|
+
const merchantAddress = String(requestInfo.to);
|
|
5911
|
+
const requestNetwork = String(requestInfo.network);
|
|
5912
|
+
const usePartial = codeValue > requestAmount;
|
|
5913
|
+
return fetchOrThrow(`${facilitatorUrl}/payment-codes/${normalizedPaymentCode}/redeem`, {
|
|
5914
|
+
method: "POST",
|
|
5915
|
+
headers: { "Content-Type": "application/json" },
|
|
5916
|
+
body: JSON.stringify({
|
|
5917
|
+
payee: merchantAddress,
|
|
5918
|
+
...usePartial ? { invoiceAmount: requestInfo.amount.toString() } : {},
|
|
5919
|
+
...usePartial ? { returnChangeAsCode: returnChange === "code" } : {},
|
|
5920
|
+
...usePartial && options.changeAddress ? { changeAddress: options.changeAddress } : {},
|
|
5921
|
+
...isEvmAddress(merchantAddress) ? { evmNetwork: requestNetwork } : { solanaNetwork: requestNetwork }
|
|
5922
|
+
})
|
|
5923
|
+
});
|
|
5924
|
+
}
|
|
5925
|
+
|
|
5214
5926
|
// src/utils/payload-converter.ts
|
|
5215
5927
|
var NETWORK_V1_TO_V2 = {
|
|
5216
5928
|
"solana": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
@@ -18991,10 +19703,14 @@ var BRIDGE_INFO_TTL_MS = 5 * 60 * 1e3;
|
|
|
18991
19703
|
USDC_SOLANA,
|
|
18992
19704
|
cancelPaymentCode,
|
|
18993
19705
|
cancelSolanaPaymentCode,
|
|
19706
|
+
cancelStoredPaymentCode,
|
|
19707
|
+
claimPaymentLink,
|
|
18994
19708
|
convertPayloadToVersion,
|
|
18995
19709
|
convertV1ToV2,
|
|
18996
19710
|
convertV2ToV1,
|
|
18997
19711
|
createPayRequest,
|
|
19712
|
+
createPaymentCode,
|
|
19713
|
+
createPaymentCodesBatch,
|
|
18998
19714
|
createPrivateKeySigner,
|
|
18999
19715
|
createX402Client,
|
|
19000
19716
|
detectPayloadVersion,
|
|
@@ -19008,11 +19724,13 @@ var BRIDGE_INFO_TTL_MS = 5 * 60 * 1e3;
|
|
|
19008
19724
|
generateSolanaPaymentCode,
|
|
19009
19725
|
getPayRequest,
|
|
19010
19726
|
getPaymentCode,
|
|
19727
|
+
getPaymentCodeDetails,
|
|
19011
19728
|
getSolanaPaymentCode,
|
|
19012
19729
|
isEvm,
|
|
19013
19730
|
isEvmNetwork,
|
|
19014
19731
|
isSolana,
|
|
19015
19732
|
isSolanaNetwork,
|
|
19733
|
+
listOwnerPaymentCodes,
|
|
19016
19734
|
networkV1ToV2,
|
|
19017
19735
|
networkV2ToV1,
|
|
19018
19736
|
normalizeNetwork,
|
|
@@ -19020,8 +19738,10 @@ var BRIDGE_INFO_TTL_MS = 5 * 60 * 1e3;
|
|
|
19020
19738
|
payPayRequest,
|
|
19021
19739
|
payPayRequestWithCode,
|
|
19022
19740
|
payPayRequestWithSolana,
|
|
19741
|
+
payPayRequestWithStoredCode,
|
|
19023
19742
|
redeemPaymentCode,
|
|
19024
19743
|
redeemSolanaPaymentCode,
|
|
19744
|
+
redeemStoredPaymentCode,
|
|
19025
19745
|
resolveToken,
|
|
19026
19746
|
stripePayTo,
|
|
19027
19747
|
submitRelayFeedback,
|