@relai-fi/x402 0.6.8 → 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.js
CHANGED
|
@@ -4454,19 +4454,26 @@ function submitRelayFeedback(config2) {
|
|
|
4454
4454
|
|
|
4455
4455
|
// src/solana-payment-codes.ts
|
|
4456
4456
|
var DEFAULT_FACILITATOR = "https://relai.fi/facilitator";
|
|
4457
|
-
function generateCode() {
|
|
4457
|
+
function generateCode(network, claimLink = false) {
|
|
4458
4458
|
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
4459
|
-
const
|
|
4459
|
+
const randomLength = claimLink ? 6 : 7;
|
|
4460
|
+
const bytes = new Uint8Array(randomLength);
|
|
4460
4461
|
if (typeof globalThis !== "undefined" && globalThis.crypto?.getRandomValues) {
|
|
4461
4462
|
globalThis.crypto.getRandomValues(bytes);
|
|
4462
4463
|
} else {
|
|
4463
4464
|
const { randomBytes } = __require("crypto");
|
|
4464
|
-
randomBytes(
|
|
4465
|
+
randomBytes(randomLength).copy(Buffer.from(bytes.buffer));
|
|
4465
4466
|
}
|
|
4466
|
-
let code = "";
|
|
4467
|
-
|
|
4467
|
+
let code = network === "solana-devnet" ? "D" : "S";
|
|
4468
|
+
if (claimLink) code += "Z";
|
|
4469
|
+
for (let i = 0; i < bytes.length; i++) code += chars[bytes[i] % chars.length];
|
|
4468
4470
|
return code;
|
|
4469
4471
|
}
|
|
4472
|
+
function buildClaimUrl(facilitatorUrl, claimToken) {
|
|
4473
|
+
if (!claimToken) return null;
|
|
4474
|
+
const origin = new URL(facilitatorUrl).origin;
|
|
4475
|
+
return new URL(`/claim/${claimToken}`, origin).toString();
|
|
4476
|
+
}
|
|
4470
4477
|
async function anchorDisc(name) {
|
|
4471
4478
|
const preimage = new TextEncoder().encode(`global:${name}`);
|
|
4472
4479
|
if (typeof globalThis !== "undefined" && globalThis.crypto?.subtle) {
|
|
@@ -4476,14 +4483,21 @@ async function anchorDisc(name) {
|
|
|
4476
4483
|
const { createHash } = __require("crypto");
|
|
4477
4484
|
return new Uint8Array(createHash("sha256").update(preimage).digest()).slice(0, 8);
|
|
4478
4485
|
}
|
|
4486
|
+
function writeBigInt64LE(target, value, offset) {
|
|
4487
|
+
let remaining = BigInt.asUintN(64, value);
|
|
4488
|
+
for (let index = 0; index < 8; index += 1) {
|
|
4489
|
+
target[offset + index] = Number(remaining & 0xffn);
|
|
4490
|
+
remaining >>= 8n;
|
|
4491
|
+
}
|
|
4492
|
+
}
|
|
4479
4493
|
async function buildInstructionData(name, codeBytes, extra) {
|
|
4480
4494
|
const disc = await anchorDisc(name);
|
|
4481
4495
|
if (name === "create_vault" && extra) {
|
|
4482
4496
|
const buf2 = Buffer.alloc(32);
|
|
4483
4497
|
Buffer.from(disc).copy(buf2, 0);
|
|
4484
4498
|
Buffer.from(codeBytes).copy(buf2, 8);
|
|
4485
|
-
buf2
|
|
4486
|
-
|
|
4499
|
+
writeBigInt64LE(buf2, extra.amount, 16);
|
|
4500
|
+
writeBigInt64LE(buf2, extra.validUntil, 24);
|
|
4487
4501
|
return buf2;
|
|
4488
4502
|
}
|
|
4489
4503
|
const buf = Buffer.alloc(16);
|
|
@@ -4493,7 +4507,7 @@ async function buildInstructionData(name, codeBytes, extra) {
|
|
|
4493
4507
|
}
|
|
4494
4508
|
async function generateSolanaPaymentCode(config2, wallet, params) {
|
|
4495
4509
|
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR;
|
|
4496
|
-
const { solanaRpcUrl, ttlSeconds = 86400 } = params;
|
|
4510
|
+
const { solanaRpcUrl, ttlSeconds = 86400, claimLink = false } = params;
|
|
4497
4511
|
const amount2 = BigInt(params.amount);
|
|
4498
4512
|
if (amount2 <= 0n) throw new Error("amount must be positive");
|
|
4499
4513
|
if (!wallet.publicKey) throw new Error("Solana wallet not connected");
|
|
@@ -4504,7 +4518,7 @@ async function generateSolanaPaymentCode(config2, wallet, params) {
|
|
|
4504
4518
|
const relayerAddr = relayerInfo.address;
|
|
4505
4519
|
const programId = relayerInfo.programId;
|
|
4506
4520
|
const usdcMint = relayerInfo.networks?.[network]?.usdc ?? (network === "solana-devnet" ? "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU" : "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
|
4507
|
-
const code = generateCode();
|
|
4521
|
+
const code = generateCode(network, claimLink);
|
|
4508
4522
|
const codeBytes = new TextEncoder().encode(code);
|
|
4509
4523
|
const {
|
|
4510
4524
|
Connection: Connection2,
|
|
@@ -4536,6 +4550,8 @@ async function generateSolanaPaymentCode(config2, wallet, params) {
|
|
|
4536
4550
|
const createVaultIx = new TransactionInstruction({
|
|
4537
4551
|
programId: programPK,
|
|
4538
4552
|
keys: [
|
|
4553
|
+
{ pubkey: relayerPK, isSigner: true, isWritable: true },
|
|
4554
|
+
// caller / fee payer
|
|
4539
4555
|
{ pubkey: buyerPK, isSigner: true, isWritable: true },
|
|
4540
4556
|
// buyer
|
|
4541
4557
|
{ pubkey: vaultPda, isSigner: false, isWritable: true },
|
|
@@ -4567,7 +4583,9 @@ async function generateSolanaPaymentCode(config2, wallet, params) {
|
|
|
4567
4583
|
code,
|
|
4568
4584
|
amount: amount2.toString(),
|
|
4569
4585
|
network,
|
|
4570
|
-
ttlSeconds
|
|
4586
|
+
ttlSeconds,
|
|
4587
|
+
claimLink,
|
|
4588
|
+
...params.description ? { description: params.description } : {}
|
|
4571
4589
|
};
|
|
4572
4590
|
const res = await fetch(`${facilitatorUrl}/solana-payment-codes`, {
|
|
4573
4591
|
method: "POST",
|
|
@@ -4578,7 +4596,129 @@ async function generateSolanaPaymentCode(config2, wallet, params) {
|
|
|
4578
4596
|
const err = await res.json().catch(() => ({}));
|
|
4579
4597
|
throw new Error(err.error ?? `generateSolanaPaymentCode failed: ${res.status}`);
|
|
4580
4598
|
}
|
|
4581
|
-
|
|
4599
|
+
const payload = await res.json();
|
|
4600
|
+
if (payload.claimLink === true) {
|
|
4601
|
+
const claimToken = typeof payload.claimToken === "string" ? payload.claimToken : null;
|
|
4602
|
+
const sanitized = { ...payload };
|
|
4603
|
+
delete sanitized.claimToken;
|
|
4604
|
+
delete sanitized.id;
|
|
4605
|
+
delete sanitized.code;
|
|
4606
|
+
return {
|
|
4607
|
+
...sanitized,
|
|
4608
|
+
claimLink: true,
|
|
4609
|
+
claimUrl: buildClaimUrl(facilitatorUrl, claimToken)
|
|
4610
|
+
};
|
|
4611
|
+
}
|
|
4612
|
+
return payload;
|
|
4613
|
+
}
|
|
4614
|
+
async function generateSolanaPaymentCodesBatch(config2, wallet, params) {
|
|
4615
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR;
|
|
4616
|
+
const items = Array.isArray(params.items) ? params.items : [];
|
|
4617
|
+
if (items.length === 0) throw new Error("items must not be empty");
|
|
4618
|
+
if (items.length > 10) throw new Error("Maximum 10 Solana batch items are supported per transaction");
|
|
4619
|
+
if (!wallet.publicKey) throw new Error("Solana wallet not connected");
|
|
4620
|
+
const infoRes = await fetch(`${facilitatorUrl}/solana-payment-codes/relayer-info`);
|
|
4621
|
+
if (!infoRes.ok) throw new Error("Failed to fetch Solana relayer info");
|
|
4622
|
+
const relayerInfo = await infoRes.json();
|
|
4623
|
+
const network = params.network ?? relayerInfo.defaultNetwork ?? "solana";
|
|
4624
|
+
const relayerAddr = relayerInfo.address;
|
|
4625
|
+
const programId = relayerInfo.programId;
|
|
4626
|
+
const usdcMint = relayerInfo.networks?.[network]?.usdc ?? (network === "solana-devnet" ? "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU" : "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
|
4627
|
+
const {
|
|
4628
|
+
Connection: Connection2,
|
|
4629
|
+
PublicKey: PublicKey2,
|
|
4630
|
+
TransactionMessage: TransactionMessage2,
|
|
4631
|
+
VersionedTransaction: VersionedTransaction2,
|
|
4632
|
+
TransactionInstruction
|
|
4633
|
+
} = await import("@solana/web3.js");
|
|
4634
|
+
const {
|
|
4635
|
+
getAssociatedTokenAddressSync,
|
|
4636
|
+
TOKEN_PROGRAM_ID: TOKEN_PROGRAM_ID2,
|
|
4637
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
4638
|
+
} = await import("@solana/spl-token");
|
|
4639
|
+
const rpcUrl = params.solanaRpcUrl ?? (network === "solana-devnet" ? "https://api.devnet.solana.com" : "https://api.mainnet-beta.solana.com");
|
|
4640
|
+
const connection = new Connection2(rpcUrl, "confirmed");
|
|
4641
|
+
const programPK = new PublicKey2(programId);
|
|
4642
|
+
const mintPK = new PublicKey2(usdcMint);
|
|
4643
|
+
const buyerPK = new PublicKey2(wallet.publicKey.toString());
|
|
4644
|
+
const relayerPK = new PublicKey2(relayerAddr);
|
|
4645
|
+
const buyerAta = getAssociatedTokenAddressSync(mintPK, buyerPK, false, TOKEN_PROGRAM_ID2);
|
|
4646
|
+
const systemProgram = new PublicKey2("11111111111111111111111111111111");
|
|
4647
|
+
const usedCodes = /* @__PURE__ */ new Set();
|
|
4648
|
+
const preparedItems = [];
|
|
4649
|
+
const instructions = [];
|
|
4650
|
+
let totalAmount = 0n;
|
|
4651
|
+
for (const item of items) {
|
|
4652
|
+
const amount2 = BigInt(item.amount);
|
|
4653
|
+
if (amount2 <= 0n) throw new Error("Each batch item amount must be positive");
|
|
4654
|
+
const ttlSeconds = Math.min(Math.max(Math.round(Number(item.ttlSeconds ?? 86400)), 60), 604800);
|
|
4655
|
+
const validUntil = BigInt(Math.floor(Date.now() / 1e3) + ttlSeconds);
|
|
4656
|
+
let code = generateCode(network, false);
|
|
4657
|
+
while (usedCodes.has(code)) code = generateCode(network, false);
|
|
4658
|
+
usedCodes.add(code);
|
|
4659
|
+
const codeBytes = new TextEncoder().encode(code);
|
|
4660
|
+
const [vaultPda] = PublicKey2.findProgramAddressSync(
|
|
4661
|
+
[Buffer.from("vault"), Buffer.from(codeBytes)],
|
|
4662
|
+
programPK
|
|
4663
|
+
);
|
|
4664
|
+
const vaultAta = getAssociatedTokenAddressSync(mintPK, vaultPda, true, TOKEN_PROGRAM_ID2);
|
|
4665
|
+
const ixData = await buildInstructionData("create_vault", codeBytes, { amount: amount2, validUntil });
|
|
4666
|
+
instructions.push(new TransactionInstruction({
|
|
4667
|
+
programId: programPK,
|
|
4668
|
+
keys: [
|
|
4669
|
+
{ pubkey: relayerPK, isSigner: true, isWritable: true },
|
|
4670
|
+
{ pubkey: buyerPK, isSigner: true, isWritable: true },
|
|
4671
|
+
{ pubkey: vaultPda, isSigner: false, isWritable: true },
|
|
4672
|
+
{ pubkey: vaultAta, isSigner: false, isWritable: true },
|
|
4673
|
+
{ pubkey: buyerAta, isSigner: false, isWritable: true },
|
|
4674
|
+
{ pubkey: mintPK, isSigner: false, isWritable: false },
|
|
4675
|
+
{ pubkey: TOKEN_PROGRAM_ID2, isSigner: false, isWritable: false },
|
|
4676
|
+
{ pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
4677
|
+
{ pubkey: systemProgram, isSigner: false, isWritable: false }
|
|
4678
|
+
],
|
|
4679
|
+
data: ixData
|
|
4680
|
+
}));
|
|
4681
|
+
preparedItems.push({
|
|
4682
|
+
code,
|
|
4683
|
+
amount: amount2,
|
|
4684
|
+
ttlSeconds,
|
|
4685
|
+
description: item.description,
|
|
4686
|
+
validUntil
|
|
4687
|
+
});
|
|
4688
|
+
totalAmount += amount2;
|
|
4689
|
+
}
|
|
4690
|
+
const { blockhash } = await connection.getLatestBlockhash("confirmed");
|
|
4691
|
+
const message = new TransactionMessage2({
|
|
4692
|
+
payerKey: relayerPK,
|
|
4693
|
+
recentBlockhash: blockhash,
|
|
4694
|
+
instructions
|
|
4695
|
+
}).compileToV0Message();
|
|
4696
|
+
const tx = new VersionedTransaction2(message);
|
|
4697
|
+
const signed = await wallet.signTransaction(tx);
|
|
4698
|
+
const serialized = Buffer.from(signed.serialize()).toString("base64");
|
|
4699
|
+
const res = await fetch(`${facilitatorUrl}/solana-payment-codes/batch`, {
|
|
4700
|
+
method: "POST",
|
|
4701
|
+
headers: { "Content-Type": "application/json" },
|
|
4702
|
+
body: JSON.stringify({
|
|
4703
|
+
transaction: serialized,
|
|
4704
|
+
network,
|
|
4705
|
+
items: preparedItems.map((item) => ({
|
|
4706
|
+
code: item.code,
|
|
4707
|
+
amount: item.amount.toString(),
|
|
4708
|
+
ttlSeconds: item.ttlSeconds,
|
|
4709
|
+
...item.description ? { description: item.description } : {}
|
|
4710
|
+
}))
|
|
4711
|
+
})
|
|
4712
|
+
});
|
|
4713
|
+
if (!res.ok) {
|
|
4714
|
+
const err = await res.json().catch(() => ({}));
|
|
4715
|
+
throw new Error(err.error ?? `generateSolanaPaymentCodesBatch failed: ${res.status}`);
|
|
4716
|
+
}
|
|
4717
|
+
const payload = await res.json();
|
|
4718
|
+
return {
|
|
4719
|
+
...payload,
|
|
4720
|
+
totalAmount: typeof payload.totalAmount === "string" ? payload.totalAmount : totalAmount.toString()
|
|
4721
|
+
};
|
|
4582
4722
|
}
|
|
4583
4723
|
async function getSolanaPaymentCode(config2, code) {
|
|
4584
4724
|
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR;
|
|
@@ -4648,6 +4788,8 @@ async function cancelSolanaPaymentCode(config2, code, wallet, options = {}) {
|
|
|
4648
4788
|
const cancelIx = new TransactionInstruction({
|
|
4649
4789
|
programId: programPK,
|
|
4650
4790
|
keys: [
|
|
4791
|
+
{ pubkey: relayerPK, isSigner: true, isWritable: true },
|
|
4792
|
+
// caller / fee payer
|
|
4651
4793
|
{ pubkey: buyerPK, isSigner: true, isWritable: true },
|
|
4652
4794
|
// buyer (must sign)
|
|
4653
4795
|
{ pubkey: vaultPda, isSigner: false, isWritable: true },
|
|
@@ -4737,6 +4879,7 @@ function createPrivateKeySigner(privateKey) {
|
|
|
4737
4879
|
};
|
|
4738
4880
|
}
|
|
4739
4881
|
var DEFAULT_FACILITATOR2 = "https://relai.fi/facilitator";
|
|
4882
|
+
var AUTHORIZATION_WINDOW_SECONDS = 3600;
|
|
4740
4883
|
function randomBytes32() {
|
|
4741
4884
|
const bytes = new Uint8Array(32);
|
|
4742
4885
|
if (typeof globalThis.crypto !== "undefined") {
|
|
@@ -4754,6 +4897,14 @@ async function fetchToAddress(facilitatorUrl, network) {
|
|
|
4754
4897
|
if (!data.toAddress) throw new Error("Facilitator returned no toAddress");
|
|
4755
4898
|
return data.toAddress;
|
|
4756
4899
|
}
|
|
4900
|
+
async function fetchEscrowAddress(facilitatorUrl, network) {
|
|
4901
|
+
const res = await fetch(`${facilitatorUrl}/payment-codes/relayer-address`);
|
|
4902
|
+
if (!res.ok) throw new Error("Failed to fetch escrow address from facilitator");
|
|
4903
|
+
const data = await res.json();
|
|
4904
|
+
const escrowAddress = data.escrowAddresses?.[network];
|
|
4905
|
+
if (!escrowAddress) throw new Error(`Facilitator returned no escrow address for ${network}`);
|
|
4906
|
+
return escrowAddress;
|
|
4907
|
+
}
|
|
4757
4908
|
async function signEip3009(signer, net, from4, toAddress, value, validAfter, validBefore, nonce, usdcOverride) {
|
|
4758
4909
|
const domain2 = {
|
|
4759
4910
|
name: net.domainName,
|
|
@@ -4828,8 +4979,7 @@ async function generatePaymentCodesBatch(config2, params) {
|
|
|
4828
4979
|
codes,
|
|
4829
4980
|
payee,
|
|
4830
4981
|
usdcContract,
|
|
4831
|
-
network = "base-sepolia"
|
|
4832
|
-
authToken
|
|
4982
|
+
network = "base-sepolia"
|
|
4833
4983
|
} = params;
|
|
4834
4984
|
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR2;
|
|
4835
4985
|
const net = NETWORK_CONFIGS[network];
|
|
@@ -4839,44 +4989,76 @@ async function generatePaymentCodesBatch(config2, params) {
|
|
|
4839
4989
|
const usdc = usdcContract ?? net.usdc;
|
|
4840
4990
|
const now = Math.floor(Date.now() / 1e3);
|
|
4841
4991
|
const toAddress = await fetchToAddress(facilitatorUrl, network);
|
|
4842
|
-
const
|
|
4843
|
-
|
|
4992
|
+
const escrowAddress = await fetchEscrowAddress(facilitatorUrl, network);
|
|
4993
|
+
const results = [];
|
|
4994
|
+
const failed = [];
|
|
4995
|
+
for (let index = 0; index < codes.length; index += 1) {
|
|
4996
|
+
const item = codes[index];
|
|
4997
|
+
try {
|
|
4844
4998
|
const validBefore = now + (item.ttl ?? 86400);
|
|
4999
|
+
const authorizationValidBefore = Math.min(validBefore, now + AUTHORIZATION_WINDOW_SECONDS);
|
|
4845
5000
|
const nonce = randomBytes32();
|
|
4846
5001
|
const value = BigInt(item.value).toString();
|
|
5002
|
+
const authorization = {
|
|
5003
|
+
from: from4,
|
|
5004
|
+
to: escrowAddress,
|
|
5005
|
+
value,
|
|
5006
|
+
validAfter: 0,
|
|
5007
|
+
validBefore: authorizationValidBefore,
|
|
5008
|
+
nonce
|
|
5009
|
+
};
|
|
4847
5010
|
const signature2 = await signEip3009(
|
|
4848
5011
|
signer,
|
|
4849
5012
|
net,
|
|
4850
5013
|
from4,
|
|
4851
|
-
|
|
5014
|
+
escrowAddress,
|
|
4852
5015
|
value,
|
|
4853
|
-
|
|
4854
|
-
validBefore,
|
|
4855
|
-
nonce,
|
|
5016
|
+
authorization.validAfter,
|
|
5017
|
+
authorization.validBefore,
|
|
5018
|
+
authorization.nonce,
|
|
4856
5019
|
usdc
|
|
4857
5020
|
);
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
5021
|
+
const res = await fetch(`${facilitatorUrl}/payment-codes`, {
|
|
5022
|
+
method: "POST",
|
|
5023
|
+
headers: { "Content-Type": "application/json" },
|
|
5024
|
+
body: JSON.stringify({
|
|
5025
|
+
from: from4,
|
|
5026
|
+
value,
|
|
5027
|
+
validBefore,
|
|
5028
|
+
usdcContract: usdc,
|
|
5029
|
+
settlementNetwork: net.settlementNetwork,
|
|
5030
|
+
escrowMode: true,
|
|
5031
|
+
...payee ? { payee } : {},
|
|
5032
|
+
signature: signature2,
|
|
5033
|
+
authorization
|
|
5034
|
+
})
|
|
5035
|
+
});
|
|
5036
|
+
if (!res.ok) {
|
|
5037
|
+
const err = await res.json().catch(() => ({}));
|
|
5038
|
+
failed.push({ index, error: String(err.error ?? err.detail ?? res.status) });
|
|
5039
|
+
continue;
|
|
5040
|
+
}
|
|
5041
|
+
const payload = await res.json();
|
|
5042
|
+
results.push({
|
|
5043
|
+
code: String(payload.code ?? ""),
|
|
5044
|
+
validBefore: Number(payload.validBefore ?? validBefore),
|
|
5045
|
+
expiresIn: Number(payload.expiresIn ?? validBefore - Math.floor(Date.now() / 1e3)),
|
|
5046
|
+
locked: Boolean(payload.locked ?? payee)
|
|
5047
|
+
});
|
|
5048
|
+
} catch (error48) {
|
|
5049
|
+
failed.push({
|
|
5050
|
+
index,
|
|
5051
|
+
error: error48 instanceof Error ? error48.message : String(error48)
|
|
5052
|
+
});
|
|
5053
|
+
}
|
|
4878
5054
|
}
|
|
4879
|
-
return
|
|
5055
|
+
return {
|
|
5056
|
+
registered: results.length,
|
|
5057
|
+
codes: results,
|
|
5058
|
+
failed,
|
|
5059
|
+
relayerAddress: toAddress,
|
|
5060
|
+
settlementNetwork: net.settlementNetwork
|
|
5061
|
+
};
|
|
4880
5062
|
}
|
|
4881
5063
|
async function redeemPaymentCode(config2, code, payee) {
|
|
4882
5064
|
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR2;
|
|
@@ -5145,6 +5327,528 @@ async function payPayRequestWithSolana(config2, requestCode, wallet, options = {
|
|
|
5145
5327
|
return res.json();
|
|
5146
5328
|
}
|
|
5147
5329
|
|
|
5330
|
+
// src/current-payment-codes.ts
|
|
5331
|
+
import { ethers as ethers3 } from "ethers";
|
|
5332
|
+
import bs58 from "bs58";
|
|
5333
|
+
var DEFAULT_FACILITATOR4 = "https://relai.fi/facilitator";
|
|
5334
|
+
var TRANSFER_WITH_AUTHORIZATION_DOMAIN_VERSION = "2";
|
|
5335
|
+
var AUTHORIZATION_WINDOW_SECONDS2 = 3600;
|
|
5336
|
+
var EVM_ESCROW_ABI = ["function cancel(bytes8 code) external"];
|
|
5337
|
+
var EIP3009_TYPES3 = {
|
|
5338
|
+
TransferWithAuthorization: [
|
|
5339
|
+
{ name: "from", type: "address" },
|
|
5340
|
+
{ name: "to", type: "address" },
|
|
5341
|
+
{ name: "value", type: "uint256" },
|
|
5342
|
+
{ name: "validAfter", type: "uint256" },
|
|
5343
|
+
{ name: "validBefore", type: "uint256" },
|
|
5344
|
+
{ name: "nonce", type: "bytes32" }
|
|
5345
|
+
]
|
|
5346
|
+
};
|
|
5347
|
+
function isEvmAddress(value) {
|
|
5348
|
+
return /^0x[0-9a-fA-F]{40}$/i.test(value);
|
|
5349
|
+
}
|
|
5350
|
+
function codeToBytes8(code) {
|
|
5351
|
+
const normalized = code.trim().toUpperCase();
|
|
5352
|
+
const bytes = ethers3.toUtf8Bytes(normalized);
|
|
5353
|
+
if (bytes.length !== 8) throw new Error("Payment code must be exactly 8 characters long");
|
|
5354
|
+
return ethers3.hexlify(bytes);
|
|
5355
|
+
}
|
|
5356
|
+
function randomBytes323() {
|
|
5357
|
+
const bytes = new Uint8Array(32);
|
|
5358
|
+
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto?.getRandomValues) {
|
|
5359
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
5360
|
+
} else {
|
|
5361
|
+
const { randomBytes } = __require("crypto");
|
|
5362
|
+
randomBytes(32).copy(Buffer.from(bytes.buffer));
|
|
5363
|
+
}
|
|
5364
|
+
return `0x${Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
5365
|
+
}
|
|
5366
|
+
function extractClaimToken(claimUrlOrToken) {
|
|
5367
|
+
const normalized = claimUrlOrToken.trim();
|
|
5368
|
+
if (!normalized) throw new Error("Claim URL or claim token is required");
|
|
5369
|
+
const directValue = normalized.split("?")[0]?.split("#")[0]?.trim() ?? "";
|
|
5370
|
+
if (!directValue.includes("/")) return directValue;
|
|
5371
|
+
try {
|
|
5372
|
+
const url2 = new URL(normalized);
|
|
5373
|
+
const segments = url2.pathname.split("/").filter(Boolean);
|
|
5374
|
+
return segments[segments.length - 1] ?? "";
|
|
5375
|
+
} catch {
|
|
5376
|
+
const segments = directValue.split("/").filter(Boolean);
|
|
5377
|
+
return segments[segments.length - 1] ?? "";
|
|
5378
|
+
}
|
|
5379
|
+
}
|
|
5380
|
+
function buildClaimUrl2(facilitatorUrl, claimToken) {
|
|
5381
|
+
if (!claimToken) return null;
|
|
5382
|
+
const origin = new URL(facilitatorUrl).origin;
|
|
5383
|
+
return new URL(`/claim/${claimToken}`, origin).toString();
|
|
5384
|
+
}
|
|
5385
|
+
function buildCancelAuthorizationMessage(code, owner, issuedAt) {
|
|
5386
|
+
return [
|
|
5387
|
+
"RelAI Codes",
|
|
5388
|
+
"Authorize payment code cancellation",
|
|
5389
|
+
`Code: ${code.trim().toUpperCase()}`,
|
|
5390
|
+
`Owner: ${owner.toLowerCase()}`,
|
|
5391
|
+
`Issued At: ${issuedAt}`
|
|
5392
|
+
].join("\n");
|
|
5393
|
+
}
|
|
5394
|
+
function buildClaimLinkCancelAuthorizationMessage(claimToken, owner, issuedAt) {
|
|
5395
|
+
return [
|
|
5396
|
+
"RelAI Codes",
|
|
5397
|
+
"Authorize claim link cancellation",
|
|
5398
|
+
`Claim Token: ${claimToken}`,
|
|
5399
|
+
`Owner: ${owner.toLowerCase()}`,
|
|
5400
|
+
`Issued At: ${issuedAt}`
|
|
5401
|
+
].join("\n");
|
|
5402
|
+
}
|
|
5403
|
+
function buildEvmClaimLinkAuthorizationMessage(params) {
|
|
5404
|
+
return [
|
|
5405
|
+
"RelAI EVM Claim Link",
|
|
5406
|
+
`Claim Token: ${params.claimToken}`,
|
|
5407
|
+
`Claimer: ${params.claimer.toLowerCase()}`,
|
|
5408
|
+
`Mode: ${params.mode}`,
|
|
5409
|
+
`Target Address: ${params.targetAddress ?? "-"}`,
|
|
5410
|
+
`Target Network: ${params.targetNetwork ?? "-"}`,
|
|
5411
|
+
`Issued At: ${params.issuedAt}`
|
|
5412
|
+
].join("\n");
|
|
5413
|
+
}
|
|
5414
|
+
function buildSolanaClaimLinkAuthorizationMessage(params) {
|
|
5415
|
+
return [
|
|
5416
|
+
"RelAI Solana Claim Link",
|
|
5417
|
+
`Claim Token: ${params.claimToken}`,
|
|
5418
|
+
`Claimer: ${params.claimer}`,
|
|
5419
|
+
`Mode: ${params.mode}`,
|
|
5420
|
+
`Target Address: ${params.targetAddress ?? "-"}`,
|
|
5421
|
+
`Target Network: ${params.targetNetwork ?? "-"}`,
|
|
5422
|
+
`Issued At: ${params.issuedAt}`
|
|
5423
|
+
].join("\n");
|
|
5424
|
+
}
|
|
5425
|
+
async function readJson(response) {
|
|
5426
|
+
return response.json().catch(() => ({}));
|
|
5427
|
+
}
|
|
5428
|
+
async function fetchWithPayload(url2, init) {
|
|
5429
|
+
const response = await fetch(url2, init);
|
|
5430
|
+
const payload = await readJson(response);
|
|
5431
|
+
return { response, payload };
|
|
5432
|
+
}
|
|
5433
|
+
function buildRequestError(response, payload) {
|
|
5434
|
+
return new Error(String(payload.error || payload.detail || payload.message || `Request failed (${response.status})`));
|
|
5435
|
+
}
|
|
5436
|
+
function isNotFoundResponse(response, payload) {
|
|
5437
|
+
return response.status === 404 || payload.errorCode === "not_found";
|
|
5438
|
+
}
|
|
5439
|
+
async function fetchStoredCodeWithFallback(facilitatorUrl, normalizedCode, options = {}) {
|
|
5440
|
+
const suffix = options.suffix ? `/${options.suffix.replace(/^\/+/, "")}` : "";
|
|
5441
|
+
const attempts = [
|
|
5442
|
+
{ kind: "evm", url: `${facilitatorUrl}/payment-codes/${normalizedCode}${suffix}` },
|
|
5443
|
+
{ kind: "solana", url: `${facilitatorUrl}/solana-payment-codes/${normalizedCode}${suffix}` }
|
|
5444
|
+
];
|
|
5445
|
+
let lastError = null;
|
|
5446
|
+
for (const attempt of attempts) {
|
|
5447
|
+
const { response, payload } = await fetchWithPayload(attempt.url, options.init);
|
|
5448
|
+
if (response.ok) {
|
|
5449
|
+
return { kind: attempt.kind, payload };
|
|
5450
|
+
}
|
|
5451
|
+
if (!isNotFoundResponse(response, payload)) {
|
|
5452
|
+
throw buildRequestError(response, payload);
|
|
5453
|
+
}
|
|
5454
|
+
lastError = buildRequestError(response, payload);
|
|
5455
|
+
}
|
|
5456
|
+
throw lastError ?? new Error("Payment code not found");
|
|
5457
|
+
}
|
|
5458
|
+
async function fetchOrThrow(url2, init) {
|
|
5459
|
+
const { response, payload } = await fetchWithPayload(url2, init);
|
|
5460
|
+
if (!response.ok) {
|
|
5461
|
+
throw buildRequestError(response, payload);
|
|
5462
|
+
}
|
|
5463
|
+
return payload;
|
|
5464
|
+
}
|
|
5465
|
+
function sanitizeClaimLinkResponse(payload, facilitatorUrl, fallbackClaimToken) {
|
|
5466
|
+
const claimToken = typeof payload.claimToken === "string" ? payload.claimToken : fallbackClaimToken ?? null;
|
|
5467
|
+
const sanitized = { ...payload };
|
|
5468
|
+
delete sanitized.claimToken;
|
|
5469
|
+
delete sanitized.id;
|
|
5470
|
+
return {
|
|
5471
|
+
...sanitized,
|
|
5472
|
+
claimUrl: buildClaimUrl2(facilitatorUrl, claimToken)
|
|
5473
|
+
};
|
|
5474
|
+
}
|
|
5475
|
+
async function createPaymentCode(config2, params) {
|
|
5476
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR4;
|
|
5477
|
+
const ttlSeconds = Math.max(60, Math.round(Number(params.ttlSeconds ?? 86400)));
|
|
5478
|
+
if ("wallet" in params) {
|
|
5479
|
+
const amount3 = BigInt(params.amount);
|
|
5480
|
+
if (amount3 <= 0n) throw new Error("amount must be positive");
|
|
5481
|
+
return generateSolanaPaymentCode(config2, params.wallet, {
|
|
5482
|
+
amount: amount3,
|
|
5483
|
+
network: params.network,
|
|
5484
|
+
claimLink: params.claimLink === true,
|
|
5485
|
+
description: params.description,
|
|
5486
|
+
ttlSeconds
|
|
5487
|
+
});
|
|
5488
|
+
}
|
|
5489
|
+
const network = params.network ?? "base-sepolia";
|
|
5490
|
+
const net = NETWORK_CONFIGS[network];
|
|
5491
|
+
if (!net) throw new Error(`Unsupported network: ${network}`);
|
|
5492
|
+
const amount2 = BigInt(params.amount);
|
|
5493
|
+
if (amount2 <= 0n) throw new Error("amount must be positive");
|
|
5494
|
+
const relayerConfig = await fetchOrThrow(`${facilitatorUrl}/payment-codes/relayer-address`);
|
|
5495
|
+
const escrowAddresses = relayerConfig.escrowAddresses ?? {};
|
|
5496
|
+
const escrowAddress = escrowAddresses[network];
|
|
5497
|
+
if (!escrowAddress) throw new Error(`EVM escrow is not configured for ${network}`);
|
|
5498
|
+
const from4 = await params.signer.getAddress();
|
|
5499
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
5500
|
+
const validBefore = now + ttlSeconds;
|
|
5501
|
+
const authorizationValidBefore = String(Math.min(validBefore, now + AUTHORIZATION_WINDOW_SECONDS2));
|
|
5502
|
+
const authorizationNonce = randomBytes323();
|
|
5503
|
+
const usdcContract = params.usdcContract ?? net.usdc;
|
|
5504
|
+
const domain2 = {
|
|
5505
|
+
name: net.domainName,
|
|
5506
|
+
version: TRANSFER_WITH_AUTHORIZATION_DOMAIN_VERSION,
|
|
5507
|
+
chainId: net.chainId,
|
|
5508
|
+
verifyingContract: usdcContract
|
|
5509
|
+
};
|
|
5510
|
+
const authorization = {
|
|
5511
|
+
from: from4,
|
|
5512
|
+
to: escrowAddress,
|
|
5513
|
+
value: amount2.toString(),
|
|
5514
|
+
validAfter: "0",
|
|
5515
|
+
validBefore: authorizationValidBefore,
|
|
5516
|
+
nonce: authorizationNonce
|
|
5517
|
+
};
|
|
5518
|
+
const signature2 = await params.signer.signTypedData(domain2, EIP3009_TYPES3, authorization);
|
|
5519
|
+
const payload = await fetchOrThrow(`${facilitatorUrl}/payment-codes`, {
|
|
5520
|
+
method: "POST",
|
|
5521
|
+
headers: { "Content-Type": "application/json" },
|
|
5522
|
+
body: JSON.stringify({
|
|
5523
|
+
from: from4,
|
|
5524
|
+
value: amount2.toString(),
|
|
5525
|
+
validBefore,
|
|
5526
|
+
usdcContract,
|
|
5527
|
+
settlementNetwork: network,
|
|
5528
|
+
escrowMode: true,
|
|
5529
|
+
claimLink: params.claimLink === true,
|
|
5530
|
+
...params.description ? { description: params.description } : {},
|
|
5531
|
+
...params.payee ? { payee: params.payee } : {},
|
|
5532
|
+
signature: signature2,
|
|
5533
|
+
authorization
|
|
5534
|
+
})
|
|
5535
|
+
});
|
|
5536
|
+
return params.claimLink === true ? sanitizeClaimLinkResponse(payload, facilitatorUrl) : payload;
|
|
5537
|
+
}
|
|
5538
|
+
async function createPaymentCodesBatch(config2, params) {
|
|
5539
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR4;
|
|
5540
|
+
if (!Array.isArray(params.codes) || params.codes.length === 0) {
|
|
5541
|
+
throw new Error("codes must not be empty");
|
|
5542
|
+
}
|
|
5543
|
+
if ("wallet" in params) {
|
|
5544
|
+
const items = params.codes.map((item) => ({
|
|
5545
|
+
amount: item.amount,
|
|
5546
|
+
ttlSeconds: item.ttlSeconds,
|
|
5547
|
+
description: item.description
|
|
5548
|
+
}));
|
|
5549
|
+
return generateSolanaPaymentCodesBatch(config2, params.wallet, {
|
|
5550
|
+
network: params.network,
|
|
5551
|
+
items
|
|
5552
|
+
});
|
|
5553
|
+
}
|
|
5554
|
+
const network = params.network ?? "base-sepolia";
|
|
5555
|
+
const net = NETWORK_CONFIGS[network];
|
|
5556
|
+
if (!net) throw new Error(`Unsupported network: ${network}`);
|
|
5557
|
+
const relayerConfig = await fetchOrThrow(`${facilitatorUrl}/payment-codes/relayer-address`);
|
|
5558
|
+
const escrowAddresses = relayerConfig.escrowAddresses ?? {};
|
|
5559
|
+
const escrowAddress = escrowAddresses[network];
|
|
5560
|
+
if (!escrowAddress) throw new Error(`EVM escrow is not configured for ${network}`);
|
|
5561
|
+
const from4 = await params.signer.getAddress();
|
|
5562
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
5563
|
+
const usdcContract = params.usdcContract ?? net.usdc;
|
|
5564
|
+
const normalizedCodes = params.codes.map((item) => {
|
|
5565
|
+
const amount2 = BigInt(item.amount);
|
|
5566
|
+
if (amount2 <= 0n) throw new Error("Each batch code amount must be positive");
|
|
5567
|
+
const ttlSeconds = Math.max(60, Math.round(Number(item.ttlSeconds ?? 86400)));
|
|
5568
|
+
return {
|
|
5569
|
+
value: amount2,
|
|
5570
|
+
validBefore: now + ttlSeconds,
|
|
5571
|
+
description: item.description?.trim() || void 0
|
|
5572
|
+
};
|
|
5573
|
+
});
|
|
5574
|
+
const totalAmount = normalizedCodes.reduce((sum, item) => sum + item.value, 0n);
|
|
5575
|
+
const authorizationValidBefore = String(now + AUTHORIZATION_WINDOW_SECONDS2);
|
|
5576
|
+
const authorizationNonce = randomBytes323();
|
|
5577
|
+
const domain2 = {
|
|
5578
|
+
name: net.domainName,
|
|
5579
|
+
version: TRANSFER_WITH_AUTHORIZATION_DOMAIN_VERSION,
|
|
5580
|
+
chainId: net.chainId,
|
|
5581
|
+
verifyingContract: usdcContract
|
|
5582
|
+
};
|
|
5583
|
+
const authorization = {
|
|
5584
|
+
from: from4,
|
|
5585
|
+
to: escrowAddress,
|
|
5586
|
+
value: totalAmount.toString(),
|
|
5587
|
+
validAfter: "0",
|
|
5588
|
+
validBefore: authorizationValidBefore,
|
|
5589
|
+
nonce: authorizationNonce
|
|
5590
|
+
};
|
|
5591
|
+
const signature2 = await params.signer.signTypedData(domain2, EIP3009_TYPES3, authorization);
|
|
5592
|
+
return fetchOrThrow(`${facilitatorUrl}/payment-codes/batch-funded`, {
|
|
5593
|
+
method: "POST",
|
|
5594
|
+
headers: { "Content-Type": "application/json" },
|
|
5595
|
+
body: JSON.stringify({
|
|
5596
|
+
from: from4,
|
|
5597
|
+
usdcContract,
|
|
5598
|
+
settlementNetwork: network,
|
|
5599
|
+
...params.payee ? { payee: params.payee } : {},
|
|
5600
|
+
signature: signature2,
|
|
5601
|
+
authorization,
|
|
5602
|
+
codes: normalizedCodes.map((item) => ({
|
|
5603
|
+
value: item.value.toString(),
|
|
5604
|
+
validBefore: item.validBefore,
|
|
5605
|
+
...item.description ? { description: item.description } : {}
|
|
5606
|
+
}))
|
|
5607
|
+
})
|
|
5608
|
+
});
|
|
5609
|
+
}
|
|
5610
|
+
async function listOwnerPaymentCodes(config2, params) {
|
|
5611
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR4;
|
|
5612
|
+
if (params.walletType === "solana") {
|
|
5613
|
+
if (!params.wallet.publicKey) throw new Error("Solana wallet not connected");
|
|
5614
|
+
const walletAddress2 = params.wallet.publicKey.toString();
|
|
5615
|
+
const challenge2 = await fetchOrThrow(`${facilitatorUrl}/payment-codes/owner/challenge`, {
|
|
5616
|
+
method: "POST",
|
|
5617
|
+
headers: { "Content-Type": "application/json" },
|
|
5618
|
+
body: JSON.stringify({ walletAddress: walletAddress2, walletType: "solana" })
|
|
5619
|
+
});
|
|
5620
|
+
const message2 = typeof challenge2.message === "string" ? challenge2.message : "";
|
|
5621
|
+
if (!message2) throw new Error("Failed to load owner challenge message");
|
|
5622
|
+
const signatureBytes = await params.wallet.signMessage(new TextEncoder().encode(message2));
|
|
5623
|
+
const session2 = await fetchOrThrow(`${facilitatorUrl}/payment-codes/owner/session`, {
|
|
5624
|
+
method: "POST",
|
|
5625
|
+
headers: { "Content-Type": "application/json" },
|
|
5626
|
+
body: JSON.stringify({ walletAddress: walletAddress2, walletType: "solana", signature: bs58.encode(signatureBytes) })
|
|
5627
|
+
});
|
|
5628
|
+
const accessToken2 = typeof session2.accessToken === "string" ? session2.accessToken : "";
|
|
5629
|
+
if (!accessToken2) throw new Error("Failed to create owner session");
|
|
5630
|
+
return fetchOrThrow(`${facilitatorUrl}/payment-codes/owner/codes`, {
|
|
5631
|
+
headers: { Authorization: `Bearer ${accessToken2}` }
|
|
5632
|
+
});
|
|
5633
|
+
}
|
|
5634
|
+
const walletAddress = await params.wallet.getAddress();
|
|
5635
|
+
const challenge = await fetchOrThrow(`${facilitatorUrl}/payment-codes/owner/challenge`, {
|
|
5636
|
+
method: "POST",
|
|
5637
|
+
headers: { "Content-Type": "application/json" },
|
|
5638
|
+
body: JSON.stringify({ walletAddress, walletType: "evm" })
|
|
5639
|
+
});
|
|
5640
|
+
const message = typeof challenge.message === "string" ? challenge.message : "";
|
|
5641
|
+
if (!message) throw new Error("Failed to load owner challenge message");
|
|
5642
|
+
const session = await fetchOrThrow(`${facilitatorUrl}/payment-codes/owner/session`, {
|
|
5643
|
+
method: "POST",
|
|
5644
|
+
headers: { "Content-Type": "application/json" },
|
|
5645
|
+
body: JSON.stringify({ walletAddress, walletType: "evm", signature: await params.wallet.signMessage(message) })
|
|
5646
|
+
});
|
|
5647
|
+
const accessToken = typeof session.accessToken === "string" ? session.accessToken : "";
|
|
5648
|
+
if (!accessToken) throw new Error("Failed to create owner session");
|
|
5649
|
+
return fetchOrThrow(`${facilitatorUrl}/payment-codes/owner/codes`, {
|
|
5650
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
5651
|
+
});
|
|
5652
|
+
}
|
|
5653
|
+
async function getPaymentCode2(config2, code) {
|
|
5654
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR4;
|
|
5655
|
+
const normalizedCode = code.trim().toUpperCase();
|
|
5656
|
+
const { payload } = await fetchStoredCodeWithFallback(facilitatorUrl, normalizedCode);
|
|
5657
|
+
return payload;
|
|
5658
|
+
}
|
|
5659
|
+
var getPaymentCodeDetails = getPaymentCode2;
|
|
5660
|
+
async function redeemStoredPaymentCode(config2, code, params) {
|
|
5661
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR4;
|
|
5662
|
+
const normalizedCode = code.trim().toUpperCase();
|
|
5663
|
+
const { payload } = await fetchStoredCodeWithFallback(facilitatorUrl, normalizedCode, {
|
|
5664
|
+
suffix: "redeem",
|
|
5665
|
+
init: {
|
|
5666
|
+
method: "POST",
|
|
5667
|
+
headers: { "Content-Type": "application/json" },
|
|
5668
|
+
body: JSON.stringify({
|
|
5669
|
+
payee: params.payee,
|
|
5670
|
+
...params.evmNetwork ? { evmNetwork: params.evmNetwork } : {},
|
|
5671
|
+
...params.solanaNetwork ? { solanaNetwork: params.solanaNetwork } : {}
|
|
5672
|
+
})
|
|
5673
|
+
}
|
|
5674
|
+
});
|
|
5675
|
+
return payload;
|
|
5676
|
+
}
|
|
5677
|
+
async function cancelStoredPaymentCode(config2, code, params = {}) {
|
|
5678
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR4;
|
|
5679
|
+
const normalizedCode = code.trim().toUpperCase();
|
|
5680
|
+
const statusInfo = await fetchStoredCodeWithFallback(facilitatorUrl, normalizedCode);
|
|
5681
|
+
if (statusInfo.kind === "solana") {
|
|
5682
|
+
if (!params.solanaWallet) {
|
|
5683
|
+
throw new Error("A Solana wallet is required to cancel this payment code");
|
|
5684
|
+
}
|
|
5685
|
+
return cancelSolanaPaymentCode(config2, normalizedCode, params.solanaWallet, {
|
|
5686
|
+
network: params.network === "solana" || params.network === "solana-devnet" ? params.network : void 0
|
|
5687
|
+
});
|
|
5688
|
+
}
|
|
5689
|
+
if (!params.wallet) {
|
|
5690
|
+
throw new Error("An EVM wallet is required to cancel this payment code");
|
|
5691
|
+
}
|
|
5692
|
+
const status = statusInfo.payload;
|
|
5693
|
+
const owner = await params.wallet.getAddress();
|
|
5694
|
+
const network = params.network ?? String(status.settlementNetwork || "base-sepolia");
|
|
5695
|
+
if (status.claimLink === true && typeof status.claimToken === "string" && status.escrowMode === true) {
|
|
5696
|
+
const issuedAt2 = Date.now();
|
|
5697
|
+
return fetchOrThrow(`${facilitatorUrl}/payment-codes/claim-links/evm/${status.claimToken}/cancel`, {
|
|
5698
|
+
method: "POST",
|
|
5699
|
+
headers: { "Content-Type": "application/json" },
|
|
5700
|
+
body: JSON.stringify({
|
|
5701
|
+
owner,
|
|
5702
|
+
issuedAt: issuedAt2,
|
|
5703
|
+
signature: await params.wallet.signMessage(buildClaimLinkCancelAuthorizationMessage(status.claimToken, owner, issuedAt2))
|
|
5704
|
+
})
|
|
5705
|
+
});
|
|
5706
|
+
}
|
|
5707
|
+
if (status.escrowMode === true) {
|
|
5708
|
+
if (!params.wallet.sendTransaction) {
|
|
5709
|
+
throw new Error("This EVM wallet does not support sendTransaction, which is required for escrow cancellation");
|
|
5710
|
+
}
|
|
5711
|
+
const relayerConfig = await fetchOrThrow(`${facilitatorUrl}/payment-codes/relayer-address`);
|
|
5712
|
+
const escrowAddresses = relayerConfig.escrowAddresses ?? {};
|
|
5713
|
+
const escrowAddress = escrowAddresses[network];
|
|
5714
|
+
if (!escrowAddress) throw new Error(`EVM escrow is not configured for ${network}`);
|
|
5715
|
+
const cancelInterface = new ethers3.Interface(EVM_ESCROW_ABI);
|
|
5716
|
+
const tx = await params.wallet.sendTransaction({
|
|
5717
|
+
to: escrowAddress,
|
|
5718
|
+
data: cancelInterface.encodeFunctionData("cancel", [codeToBytes8(normalizedCode)])
|
|
5719
|
+
});
|
|
5720
|
+
await tx.wait();
|
|
5721
|
+
return {
|
|
5722
|
+
success: true,
|
|
5723
|
+
code: normalizedCode,
|
|
5724
|
+
cancelTxHash: tx.hash,
|
|
5725
|
+
network
|
|
5726
|
+
};
|
|
5727
|
+
}
|
|
5728
|
+
const issuedAt = Date.now();
|
|
5729
|
+
return fetchOrThrow(`${facilitatorUrl}/payment-codes/${normalizedCode}/cancel`, {
|
|
5730
|
+
method: "POST",
|
|
5731
|
+
headers: { "Content-Type": "application/json" },
|
|
5732
|
+
body: JSON.stringify({
|
|
5733
|
+
owner,
|
|
5734
|
+
issuedAt,
|
|
5735
|
+
signature: await params.wallet.signMessage(buildCancelAuthorizationMessage(normalizedCode, owner, issuedAt))
|
|
5736
|
+
})
|
|
5737
|
+
});
|
|
5738
|
+
}
|
|
5739
|
+
async function claimPaymentLink(config2, claimUrlOrToken, params) {
|
|
5740
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR4;
|
|
5741
|
+
const claimToken = extractClaimToken(claimUrlOrToken);
|
|
5742
|
+
if (!claimToken) throw new Error("Claim URL or claim token is required");
|
|
5743
|
+
const issuedAt = Date.now();
|
|
5744
|
+
const mode = params.mode ?? "claim-usdc";
|
|
5745
|
+
if ("solanaWallet" in params && params.solanaWallet) {
|
|
5746
|
+
if (!params.solanaWallet.publicKey) throw new Error("Solana wallet not connected");
|
|
5747
|
+
const claimer2 = params.solanaWallet.publicKey.toString();
|
|
5748
|
+
const requestedPayee2 = params.payee?.trim() || claimer2;
|
|
5749
|
+
const targetAddress2 = mode === "claim-usdc" ? isEvmAddress(requestedPayee2) ? requestedPayee2.toLowerCase() : requestedPayee2 : null;
|
|
5750
|
+
const targetNetwork2 = mode === "claim-usdc" ? targetAddress2 == null ? null : isEvmAddress(targetAddress2) ? params.evmNetwork ?? null : params.solanaNetwork ?? null : null;
|
|
5751
|
+
const message2 = buildSolanaClaimLinkAuthorizationMessage({
|
|
5752
|
+
claimToken,
|
|
5753
|
+
claimer: claimer2,
|
|
5754
|
+
issuedAt,
|
|
5755
|
+
mode,
|
|
5756
|
+
targetAddress: targetAddress2,
|
|
5757
|
+
targetNetwork: targetNetwork2
|
|
5758
|
+
});
|
|
5759
|
+
const signatureBytes = await params.solanaWallet.signMessage(new TextEncoder().encode(message2));
|
|
5760
|
+
const payload2 = await fetchOrThrow(`${facilitatorUrl}/solana-payment-codes/claim-links/${claimToken}/claim`, {
|
|
5761
|
+
method: "POST",
|
|
5762
|
+
headers: { "Content-Type": "application/json" },
|
|
5763
|
+
body: JSON.stringify({
|
|
5764
|
+
claimer: claimer2,
|
|
5765
|
+
issuedAt,
|
|
5766
|
+
signature: bs58.encode(signatureBytes),
|
|
5767
|
+
mode,
|
|
5768
|
+
...targetAddress2 ? { targetAddress: targetAddress2 } : {},
|
|
5769
|
+
...targetNetwork2 ? { targetNetwork: targetNetwork2 } : {}
|
|
5770
|
+
})
|
|
5771
|
+
});
|
|
5772
|
+
return sanitizeClaimLinkResponse(payload2, facilitatorUrl, claimToken);
|
|
5773
|
+
}
|
|
5774
|
+
if (!("wallet" in params) || !params.wallet) {
|
|
5775
|
+
throw new Error("An EVM wallet or Solana wallet is required to claim this payment link");
|
|
5776
|
+
}
|
|
5777
|
+
const claimer = (await params.wallet.getAddress()).toLowerCase();
|
|
5778
|
+
const requestedPayee = params.payee?.trim() || claimer;
|
|
5779
|
+
const targetAddress = mode === "claim-usdc" ? isEvmAddress(requestedPayee) ? requestedPayee.toLowerCase() : requestedPayee : null;
|
|
5780
|
+
const targetNetwork = mode === "claim-usdc" ? targetAddress == null ? null : isEvmAddress(targetAddress) ? params.evmNetwork ?? null : params.solanaNetwork ?? null : null;
|
|
5781
|
+
const message = buildEvmClaimLinkAuthorizationMessage({
|
|
5782
|
+
claimToken,
|
|
5783
|
+
claimer,
|
|
5784
|
+
issuedAt,
|
|
5785
|
+
mode,
|
|
5786
|
+
targetAddress,
|
|
5787
|
+
targetNetwork
|
|
5788
|
+
});
|
|
5789
|
+
const payload = await fetchOrThrow(`${facilitatorUrl}/payment-codes/claim-links/evm/${claimToken}/claim`, {
|
|
5790
|
+
method: "POST",
|
|
5791
|
+
headers: { "Content-Type": "application/json" },
|
|
5792
|
+
body: JSON.stringify({
|
|
5793
|
+
claimer,
|
|
5794
|
+
issuedAt,
|
|
5795
|
+
signature: await params.wallet.signMessage(message),
|
|
5796
|
+
mode,
|
|
5797
|
+
...targetAddress ? { targetAddress, payee: targetAddress } : {},
|
|
5798
|
+
...targetNetwork ? { targetNetwork } : {},
|
|
5799
|
+
...targetAddress && isEvmAddress(targetAddress) && targetNetwork ? { evmNetwork: targetNetwork } : {},
|
|
5800
|
+
...targetAddress && !isEvmAddress(targetAddress) && targetNetwork ? { solanaNetwork: targetNetwork } : {}
|
|
5801
|
+
})
|
|
5802
|
+
});
|
|
5803
|
+
return sanitizeClaimLinkResponse(payload, facilitatorUrl, claimToken);
|
|
5804
|
+
}
|
|
5805
|
+
async function payPayRequestWithStoredCode(config2, requestCode, paymentCode, options = {}) {
|
|
5806
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR4;
|
|
5807
|
+
const normalizedRequestCode = requestCode.trim().toUpperCase();
|
|
5808
|
+
const normalizedPaymentCode = paymentCode.trim().toUpperCase();
|
|
5809
|
+
const codeStatusInfo = await fetchStoredCodeWithFallback(facilitatorUrl, normalizedPaymentCode);
|
|
5810
|
+
if (codeStatusInfo.kind === "solana") {
|
|
5811
|
+
return fetchOrThrow(`${facilitatorUrl}/solana-payment-codes/${normalizedPaymentCode}/pay-request`, {
|
|
5812
|
+
method: "POST",
|
|
5813
|
+
headers: { "Content-Type": "application/json" },
|
|
5814
|
+
body: JSON.stringify({
|
|
5815
|
+
requestCode: normalizedRequestCode,
|
|
5816
|
+
...options.changeAddress ? { changeAddress: options.changeAddress } : {}
|
|
5817
|
+
})
|
|
5818
|
+
});
|
|
5819
|
+
}
|
|
5820
|
+
const requestInfo = await getPayRequest(config2, normalizedRequestCode);
|
|
5821
|
+
const codeStatus = codeStatusInfo.payload;
|
|
5822
|
+
const allowOverpayment = options.allowOverpayment ?? true;
|
|
5823
|
+
const returnChange = options.returnChange ?? "code";
|
|
5824
|
+
const codeValue = BigInt(String(codeStatus.value ?? 0));
|
|
5825
|
+
const requestAmount = BigInt(requestInfo.amount);
|
|
5826
|
+
if (codeValue < requestAmount) {
|
|
5827
|
+
throw new Error(
|
|
5828
|
+
`Payment code value (${Number(codeValue) / 1e6} USDC) is less than the request amount (${Number(requestAmount) / 1e6} USDC)`
|
|
5829
|
+
);
|
|
5830
|
+
}
|
|
5831
|
+
if (!allowOverpayment && codeValue > requestAmount) {
|
|
5832
|
+
throw new Error(
|
|
5833
|
+
`Payment code value (${Number(codeValue) / 1e6} USDC) exceeds the request amount (${Number(requestAmount) / 1e6} USDC)`
|
|
5834
|
+
);
|
|
5835
|
+
}
|
|
5836
|
+
const merchantAddress = String(requestInfo.to);
|
|
5837
|
+
const requestNetwork = String(requestInfo.network);
|
|
5838
|
+
const usePartial = codeValue > requestAmount;
|
|
5839
|
+
return fetchOrThrow(`${facilitatorUrl}/payment-codes/${normalizedPaymentCode}/redeem`, {
|
|
5840
|
+
method: "POST",
|
|
5841
|
+
headers: { "Content-Type": "application/json" },
|
|
5842
|
+
body: JSON.stringify({
|
|
5843
|
+
payee: merchantAddress,
|
|
5844
|
+
...usePartial ? { invoiceAmount: requestInfo.amount.toString() } : {},
|
|
5845
|
+
...usePartial ? { returnChangeAsCode: returnChange === "code" } : {},
|
|
5846
|
+
...usePartial && options.changeAddress ? { changeAddress: options.changeAddress } : {},
|
|
5847
|
+
...isEvmAddress(merchantAddress) ? { evmNetwork: requestNetwork } : { solanaNetwork: requestNetwork }
|
|
5848
|
+
})
|
|
5849
|
+
});
|
|
5850
|
+
}
|
|
5851
|
+
|
|
5148
5852
|
// src/utils/payload-converter.ts
|
|
5149
5853
|
var NETWORK_V1_TO_V2 = {
|
|
5150
5854
|
"solana": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
@@ -18924,10 +19628,14 @@ export {
|
|
|
18924
19628
|
USDC_SOLANA,
|
|
18925
19629
|
cancelPaymentCode,
|
|
18926
19630
|
cancelSolanaPaymentCode,
|
|
19631
|
+
cancelStoredPaymentCode,
|
|
19632
|
+
claimPaymentLink,
|
|
18927
19633
|
convertPayloadToVersion,
|
|
18928
19634
|
convertV1ToV2,
|
|
18929
19635
|
convertV2ToV1,
|
|
18930
19636
|
createPayRequest,
|
|
19637
|
+
createPaymentCode,
|
|
19638
|
+
createPaymentCodesBatch,
|
|
18931
19639
|
createPrivateKeySigner,
|
|
18932
19640
|
createX402Client,
|
|
18933
19641
|
Relai as default,
|
|
@@ -18942,11 +19650,13 @@ export {
|
|
|
18942
19650
|
generateSolanaPaymentCode,
|
|
18943
19651
|
getPayRequest,
|
|
18944
19652
|
getPaymentCode,
|
|
19653
|
+
getPaymentCodeDetails,
|
|
18945
19654
|
getSolanaPaymentCode,
|
|
18946
19655
|
isEvm,
|
|
18947
19656
|
isEvmNetwork,
|
|
18948
19657
|
isSolana,
|
|
18949
19658
|
isSolanaNetwork,
|
|
19659
|
+
listOwnerPaymentCodes,
|
|
18950
19660
|
networkV1ToV2,
|
|
18951
19661
|
networkV2ToV1,
|
|
18952
19662
|
normalizeNetwork,
|
|
@@ -18954,8 +19664,10 @@ export {
|
|
|
18954
19664
|
payPayRequest,
|
|
18955
19665
|
payPayRequestWithCode,
|
|
18956
19666
|
payPayRequestWithSolana,
|
|
19667
|
+
payPayRequestWithStoredCode,
|
|
18957
19668
|
redeemPaymentCode,
|
|
18958
19669
|
redeemSolanaPaymentCode,
|
|
19670
|
+
redeemStoredPaymentCode,
|
|
18959
19671
|
resolveToken,
|
|
18960
19672
|
stripePayTo,
|
|
18961
19673
|
submitRelayFeedback,
|