@relai-fi/x402 0.6.6 → 0.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -0
- package/dist/client.d.cts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/index.cjs +339 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +291 -30
- package/dist/index.d.ts +291 -30
- package/dist/index.js +331 -32
- package/dist/index.js.map +1 -1
- package/dist/plugins.d.cts +2 -2
- package/dist/plugins.d.ts +2 -2
- package/dist/react/index.d.cts +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/{server-DaySqG5H.d.ts → server-D9ZfrFFx.d.ts} +1 -1
- package/dist/{server-CBZ2RjEP.d.cts → server-DgMG2zhy.d.cts} +1 -1
- package/dist/server.d.cts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/{types-Y9ni5XwY.d.cts → types-DjEveKgt.d.cts} +1 -1
- package/dist/{types-Y9ni5XwY.d.ts → types-DjEveKgt.d.ts} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1599,6 +1599,141 @@ See [`examples/bridge/`](./examples/bridge/) for runnable examples of each appro
|
|
|
1599
1599
|
|
|
1600
1600
|
---
|
|
1601
1601
|
|
|
1602
|
+
## Payment Codes
|
|
1603
|
+
|
|
1604
|
+
BLIK-style one-time payment codes — pre-signed EIP-3009 tokens that can be generated in advance and redeemed later, without a wallet at redemption time. Ideal for AI agents and walletless buyers.
|
|
1605
|
+
|
|
1606
|
+
```typescript
|
|
1607
|
+
import {
|
|
1608
|
+
createPrivateKeySigner,
|
|
1609
|
+
generatePaymentCode,
|
|
1610
|
+
generatePaymentCodesBatch,
|
|
1611
|
+
redeemPaymentCode,
|
|
1612
|
+
getPaymentCode,
|
|
1613
|
+
cancelPaymentCode,
|
|
1614
|
+
} from '@relai-fi/x402';
|
|
1615
|
+
import { ethers } from 'ethers';
|
|
1616
|
+
|
|
1617
|
+
const config = { facilitatorUrl: 'https://relai.fi/facilitator' };
|
|
1618
|
+
|
|
1619
|
+
// ── Create a signer from a private key (agent / server-side) ──────────────
|
|
1620
|
+
const signer = createPrivateKeySigner(process.env.AGENT_PRIVATE_KEY!);
|
|
1621
|
+
|
|
1622
|
+
// ── Generate a single payment code ($10 USDC, expires in 24 h) ───────────
|
|
1623
|
+
const code = await generatePaymentCode(config, signer, {
|
|
1624
|
+
amount: 10_000_000, // $10.00 in µUSDC
|
|
1625
|
+
network: 'base-sepolia',
|
|
1626
|
+
});
|
|
1627
|
+
console.log('Code:', code.code); // "ABCD1234"
|
|
1628
|
+
|
|
1629
|
+
// ── Batch generate 5 codes (requires API key) ─────────────────────────────
|
|
1630
|
+
const batch = await generatePaymentCodesBatch(config, signer, {
|
|
1631
|
+
amount: 5_000_000,
|
|
1632
|
+
network: 'base-sepolia',
|
|
1633
|
+
count: 5,
|
|
1634
|
+
apiKey: process.env.RELAI_API_KEY!,
|
|
1635
|
+
});
|
|
1636
|
+
|
|
1637
|
+
// ── Check status ──────────────────────────────────────────────────────────
|
|
1638
|
+
const status = await getPaymentCode(config, 'ABCD1234');
|
|
1639
|
+
console.log(status.redeemed, status.expired, status.value);
|
|
1640
|
+
|
|
1641
|
+
// ── Redeem (settle USDC to a payee) ──────────────────────────────────────
|
|
1642
|
+
const result = await redeemPaymentCode(config, 'ABCD1234', {
|
|
1643
|
+
payee: '0xMerchantWallet',
|
|
1644
|
+
});
|
|
1645
|
+
console.log('Explorer:', result.explorerUrl);
|
|
1646
|
+
|
|
1647
|
+
// ── Cancel before use ─────────────────────────────────────────────────────
|
|
1648
|
+
await cancelPaymentCode(config, 'ABCD1234');
|
|
1649
|
+
```
|
|
1650
|
+
|
|
1651
|
+
### `generatePaymentCode` options
|
|
1652
|
+
|
|
1653
|
+
| Option | Type | Default | Description |
|
|
1654
|
+
|--------|------|---------|-------------|
|
|
1655
|
+
| `amount` | `number` | — | Amount in µUSDC (1 USDC = 1 000 000) |
|
|
1656
|
+
| `network` | `string` | `'base-sepolia'` | Settlement network |
|
|
1657
|
+
| `description` | `string` | — | Optional note stored with the code |
|
|
1658
|
+
| `payee` | `string` | — | Lock the code to a specific payee address |
|
|
1659
|
+
| `ttl` | `number` | `86400` | Expiry in seconds (default: 24 h) |
|
|
1660
|
+
|
|
1661
|
+
### `RedeemResult` fields
|
|
1662
|
+
|
|
1663
|
+
| Field | Type | Description |
|
|
1664
|
+
|-------|------|-------------|
|
|
1665
|
+
| `success` | `boolean` | Settlement succeeded |
|
|
1666
|
+
| `code` | `string` | The redeemed code |
|
|
1667
|
+
| `l2TxHash` | `string` | On-chain tx hash |
|
|
1668
|
+
| `explorerUrl` | `string` | Block explorer link |
|
|
1669
|
+
| `amount` | `string` | Amount settled (µUSDC) |
|
|
1670
|
+
| `change` | `string?` | Remainder returned to buyer (µUSDC), if partial |
|
|
1671
|
+
| `changeMode` | `'code' \| 'wallet'?` | How change was returned |
|
|
1672
|
+
| `changeCode` | `string?` | New payment code for change (when `changeMode === 'code'`) |
|
|
1673
|
+
|
|
1674
|
+
---
|
|
1675
|
+
|
|
1676
|
+
## Payment Requests
|
|
1677
|
+
|
|
1678
|
+
Merchant-initiated invoices — the merchant creates a payment request, shares the code or link, and any buyer (or agent) can pay it.
|
|
1679
|
+
|
|
1680
|
+
```typescript
|
|
1681
|
+
import {
|
|
1682
|
+
createPayRequest,
|
|
1683
|
+
getPayRequest,
|
|
1684
|
+
payPayRequest,
|
|
1685
|
+
payPayRequestWithCode,
|
|
1686
|
+
} from '@relai-fi/x402';
|
|
1687
|
+
|
|
1688
|
+
const config = { facilitatorUrl: 'https://relai.fi/facilitator' };
|
|
1689
|
+
|
|
1690
|
+
// ── Merchant: create an invoice ───────────────────────────────────────────
|
|
1691
|
+
const req = await createPayRequest(config, {
|
|
1692
|
+
to: '0xMerchantWallet',
|
|
1693
|
+
amount: 5_000_000, // $5.00 USDC
|
|
1694
|
+
network: 'base-sepolia',
|
|
1695
|
+
description: 'Order #42',
|
|
1696
|
+
ttl: 3600, // expires in 1 h
|
|
1697
|
+
});
|
|
1698
|
+
console.log('Invoice code:', req.code); // "MW78SGTW"
|
|
1699
|
+
console.log('Pay URL:', req.payUrl); // "https://relai.fi/pay#MW78SGTW"
|
|
1700
|
+
|
|
1701
|
+
// ── Buyer: read the request ────────────────────────────────────────────────
|
|
1702
|
+
const info = await getPayRequest(config, 'MW78SGTW');
|
|
1703
|
+
console.log(`$${Number(info.amount) / 1e6} USDC → ${info.to}`);
|
|
1704
|
+
|
|
1705
|
+
// ── Buyer: pay with EIP-3009 signer (has wallet) ─────────────────────────
|
|
1706
|
+
const signer = createPrivateKeySigner(process.env.BUYER_PRIVATE_KEY!);
|
|
1707
|
+
const paid = await payPayRequest(config, 'MW78SGTW', signer);
|
|
1708
|
+
console.log('Explorer:', paid.explorerUrl);
|
|
1709
|
+
|
|
1710
|
+
// ── Buyer: pay using a pre-generated payment code (no wallet at payment time)
|
|
1711
|
+
const result = await payPayRequestWithCode(config, 'MW78SGTW', 'MYBLIK78');
|
|
1712
|
+
console.log('Success:', result.success);
|
|
1713
|
+
console.log('Explorer:', result.explorerUrl);
|
|
1714
|
+
|
|
1715
|
+
// If the code covers more than the invoice → change is returned as a new code
|
|
1716
|
+
if (result.changeCode) {
|
|
1717
|
+
console.log(`Change $${Number(result.change) / 1e6} USDC → code: ${result.changeCode}`);
|
|
1718
|
+
}
|
|
1719
|
+
```
|
|
1720
|
+
|
|
1721
|
+
### `payPayRequestWithCode` options
|
|
1722
|
+
|
|
1723
|
+
| Option | Type | Default | Description |
|
|
1724
|
+
|--------|------|---------|-------------|
|
|
1725
|
+
| `returnChange` | `'code' \| 'wallet'` | `'code'` | How to return surplus when code value > invoice |
|
|
1726
|
+
| `allowOverpayment` | `boolean` | `true` | If `false`, throws when code value ≠ invoice amount |
|
|
1727
|
+
|
|
1728
|
+
**`returnChange` behaviour:**
|
|
1729
|
+
|
|
1730
|
+
| Value | Mechanism | Requires buyer wallet? |
|
|
1731
|
+
|-------|-----------|----------------------|
|
|
1732
|
+
| `'code'` (default) | Relayer pays merchant, generates a new code for the remainder | No |
|
|
1733
|
+
| `'wallet'` | `PaymentSettler.settleExact()` — merchant + change in one atomic tx | Yes (`from` address) |
|
|
1734
|
+
|
|
1735
|
+
---
|
|
1736
|
+
|
|
1602
1737
|
## Development
|
|
1603
1738
|
|
|
1604
1739
|
```bash
|
package/dist/client.d.cts
CHANGED
package/dist/client.d.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -7021,6 +7021,7 @@ __export(index_exports, {
|
|
|
7021
7021
|
CHAIN_IDS: () => CHAIN_IDS,
|
|
7022
7022
|
EXPLORER_TX_URL: () => EXPLORER_TX_URL,
|
|
7023
7023
|
NETWORK_CAIP2: () => NETWORK_CAIP2,
|
|
7024
|
+
NETWORK_CONFIGS: () => NETWORK_CONFIGS,
|
|
7024
7025
|
NETWORK_LABELS: () => NETWORK_LABELS,
|
|
7025
7026
|
NETWORK_TOKENS: () => NETWORK_TOKENS,
|
|
7026
7027
|
NETWORK_V1_TO_V2: () => NETWORK_V1_TO_V2,
|
|
@@ -7032,9 +7033,12 @@ __export(index_exports, {
|
|
|
7032
7033
|
USDC_ADDRESSES: () => USDC_ADDRESSES,
|
|
7033
7034
|
USDC_BASE: () => USDC_BASE,
|
|
7034
7035
|
USDC_SOLANA: () => USDC_SOLANA,
|
|
7036
|
+
cancelPaymentCode: () => cancelPaymentCode,
|
|
7035
7037
|
convertPayloadToVersion: () => convertPayloadToVersion,
|
|
7036
7038
|
convertV1ToV2: () => convertV1ToV2,
|
|
7037
7039
|
convertV2ToV1: () => convertV2ToV1,
|
|
7040
|
+
createPayRequest: () => createPayRequest,
|
|
7041
|
+
createPrivateKeySigner: () => createPrivateKeySigner,
|
|
7038
7042
|
createX402Client: () => createX402Client,
|
|
7039
7043
|
default: () => Relai,
|
|
7040
7044
|
detectPayloadVersion: () => detectPayloadVersion,
|
|
@@ -7044,6 +7048,8 @@ __export(index_exports, {
|
|
|
7044
7048
|
formatUsd: () => formatUsd,
|
|
7045
7049
|
fromAtomicUnits: () => fromAtomicUnits,
|
|
7046
7050
|
generatePaymentCode: () => generatePaymentCode,
|
|
7051
|
+
generatePaymentCodesBatch: () => generatePaymentCodesBatch,
|
|
7052
|
+
getPayRequest: () => getPayRequest,
|
|
7047
7053
|
getPaymentCode: () => getPaymentCode,
|
|
7048
7054
|
isEvm: () => isEvm,
|
|
7049
7055
|
isEvmNetwork: () => isEvmNetwork,
|
|
@@ -7053,6 +7059,8 @@ __export(index_exports, {
|
|
|
7053
7059
|
networkV2ToV1: () => networkV2ToV1,
|
|
7054
7060
|
normalizeNetwork: () => normalizeNetwork,
|
|
7055
7061
|
normalizePaymentHeader: () => normalizePaymentHeader,
|
|
7062
|
+
payPayRequest: () => payPayRequest,
|
|
7063
|
+
payPayRequestWithCode: () => payPayRequestWithCode,
|
|
7056
7064
|
redeemPaymentCode: () => redeemPaymentCode,
|
|
7057
7065
|
resolveToken: () => resolveToken,
|
|
7058
7066
|
stripePayTo: () => stripePayTo,
|
|
@@ -9435,8 +9443,37 @@ function submitRelayFeedback(config2) {
|
|
|
9435
9443
|
}
|
|
9436
9444
|
|
|
9437
9445
|
// src/payment-codes.ts
|
|
9438
|
-
var
|
|
9439
|
-
var
|
|
9446
|
+
var import_ethers2 = require("ethers");
|
|
9447
|
+
var NETWORK_CONFIGS = {
|
|
9448
|
+
"base": {
|
|
9449
|
+
chainId: 8453,
|
|
9450
|
+
usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
9451
|
+
domainName: "USD Coin",
|
|
9452
|
+
rpc: "https://mainnet.base.org",
|
|
9453
|
+
settlementNetwork: "base"
|
|
9454
|
+
},
|
|
9455
|
+
"base-sepolia": {
|
|
9456
|
+
chainId: 84532,
|
|
9457
|
+
usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
9458
|
+
domainName: "USDC",
|
|
9459
|
+
rpc: "https://sepolia.base.org",
|
|
9460
|
+
settlementNetwork: "base-sepolia"
|
|
9461
|
+
},
|
|
9462
|
+
"skale-base-sepolia": {
|
|
9463
|
+
chainId: 324705682,
|
|
9464
|
+
usdc: "0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",
|
|
9465
|
+
domainName: "Bridged USDC (SKALE Bridge)",
|
|
9466
|
+
rpc: "https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha",
|
|
9467
|
+
settlementNetwork: "skale-base-sepolia"
|
|
9468
|
+
},
|
|
9469
|
+
"skale-base": {
|
|
9470
|
+
chainId: 1482601649,
|
|
9471
|
+
usdc: "0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20",
|
|
9472
|
+
domainName: "Bridged USDC",
|
|
9473
|
+
rpc: "https://skale-base.skalenodes.com/v1/base",
|
|
9474
|
+
settlementNetwork: "skale-base"
|
|
9475
|
+
}
|
|
9476
|
+
};
|
|
9440
9477
|
var EIP3009_TYPES = {
|
|
9441
9478
|
TransferWithAuthorization: [
|
|
9442
9479
|
{ name: "from", type: "address" },
|
|
@@ -9447,6 +9484,14 @@ var EIP3009_TYPES = {
|
|
|
9447
9484
|
{ name: "nonce", type: "bytes32" }
|
|
9448
9485
|
]
|
|
9449
9486
|
};
|
|
9487
|
+
function createPrivateKeySigner(privateKey) {
|
|
9488
|
+
const wallet = new import_ethers2.ethers.Wallet(privateKey);
|
|
9489
|
+
return {
|
|
9490
|
+
getAddress: () => Promise.resolve(wallet.address),
|
|
9491
|
+
signTypedData: (domain2, types, value) => wallet.signTypedData(domain2, types, value)
|
|
9492
|
+
};
|
|
9493
|
+
}
|
|
9494
|
+
var DEFAULT_FACILITATOR = "https://relai.fi/facilitator";
|
|
9450
9495
|
function randomBytes32() {
|
|
9451
9496
|
const bytes = new Uint8Array(32);
|
|
9452
9497
|
if (typeof globalThis.crypto !== "undefined") {
|
|
@@ -9457,69 +9502,323 @@ function randomBytes32() {
|
|
|
9457
9502
|
}
|
|
9458
9503
|
return "0x" + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
9459
9504
|
}
|
|
9460
|
-
async function
|
|
9461
|
-
const
|
|
9462
|
-
|
|
9463
|
-
const
|
|
9464
|
-
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
|
|
9468
|
-
const nonce = randomBytes32();
|
|
9505
|
+
async function fetchToAddress(facilitatorUrl, network) {
|
|
9506
|
+
const res = await fetch(`${facilitatorUrl}/payment-codes/relayer?network=${network}`);
|
|
9507
|
+
if (!res.ok) throw new Error("Failed to fetch relayer address from facilitator");
|
|
9508
|
+
const data = await res.json();
|
|
9509
|
+
if (!data.toAddress) throw new Error("Facilitator returned no toAddress");
|
|
9510
|
+
return data.toAddress;
|
|
9511
|
+
}
|
|
9512
|
+
async function signEip3009(signer, net, from4, toAddress, value, validAfter, validBefore, nonce, usdcOverride) {
|
|
9469
9513
|
const domain2 = {
|
|
9470
|
-
name:
|
|
9514
|
+
name: net.domainName,
|
|
9471
9515
|
version: "2",
|
|
9472
|
-
chainId:
|
|
9473
|
-
|
|
9474
|
-
verifyingContract: usdc
|
|
9516
|
+
chainId: net.chainId,
|
|
9517
|
+
verifyingContract: usdcOverride ?? net.usdc
|
|
9475
9518
|
};
|
|
9476
|
-
|
|
9519
|
+
return signer.signTypedData(domain2, EIP3009_TYPES, {
|
|
9477
9520
|
from: from4,
|
|
9478
|
-
to,
|
|
9479
|
-
value
|
|
9521
|
+
to: toAddress,
|
|
9522
|
+
value,
|
|
9480
9523
|
validAfter,
|
|
9481
9524
|
validBefore,
|
|
9482
9525
|
nonce
|
|
9483
|
-
};
|
|
9484
|
-
|
|
9526
|
+
});
|
|
9527
|
+
}
|
|
9528
|
+
async function generatePaymentCode(config2, params) {
|
|
9529
|
+
const {
|
|
9530
|
+
signer,
|
|
9531
|
+
value,
|
|
9532
|
+
ttl = 86400,
|
|
9533
|
+
description,
|
|
9534
|
+
payee,
|
|
9535
|
+
usdcContract,
|
|
9536
|
+
network = "base-sepolia"
|
|
9537
|
+
} = params;
|
|
9538
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR;
|
|
9539
|
+
const net = NETWORK_CONFIGS[network];
|
|
9540
|
+
if (!net) throw new Error(`Unsupported network: ${network}`);
|
|
9541
|
+
const from4 = await signer.getAddress();
|
|
9542
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
9543
|
+
const validBefore = now + ttl;
|
|
9544
|
+
const nonce = randomBytes32();
|
|
9545
|
+
const usdc = usdcContract ?? net.usdc;
|
|
9546
|
+
const toAddress = await fetchToAddress(facilitatorUrl, network);
|
|
9547
|
+
const signature2 = await signEip3009(
|
|
9548
|
+
signer,
|
|
9549
|
+
net,
|
|
9550
|
+
from4,
|
|
9551
|
+
toAddress,
|
|
9552
|
+
BigInt(value).toString(),
|
|
9553
|
+
0,
|
|
9554
|
+
validBefore,
|
|
9555
|
+
nonce,
|
|
9556
|
+
usdc
|
|
9557
|
+
);
|
|
9485
9558
|
const res = await fetch(`${facilitatorUrl}/payment-codes`, {
|
|
9486
9559
|
method: "POST",
|
|
9487
9560
|
headers: { "Content-Type": "application/json" },
|
|
9488
9561
|
body: JSON.stringify({
|
|
9489
9562
|
from: from4,
|
|
9490
|
-
to,
|
|
9491
9563
|
value: BigInt(value).toString(),
|
|
9492
|
-
validAfter,
|
|
9564
|
+
validAfter: 0,
|
|
9493
9565
|
validBefore,
|
|
9494
9566
|
nonce,
|
|
9495
9567
|
signature: signature2,
|
|
9496
|
-
usdcContract: usdc
|
|
9568
|
+
usdcContract: usdc,
|
|
9569
|
+
settlementNetwork: net.settlementNetwork,
|
|
9570
|
+
...description ? { description } : {},
|
|
9571
|
+
...payee ? { payee } : {}
|
|
9497
9572
|
})
|
|
9498
9573
|
});
|
|
9499
9574
|
if (!res.ok) {
|
|
9500
9575
|
const err = await res.json().catch(() => ({}));
|
|
9501
|
-
throw new Error(`Failed to register payment code: ${err.error
|
|
9576
|
+
throw new Error(`Failed to register payment code: ${err.error ?? res.status}`);
|
|
9502
9577
|
}
|
|
9503
9578
|
return res.json();
|
|
9504
9579
|
}
|
|
9505
|
-
async function
|
|
9506
|
-
const
|
|
9507
|
-
|
|
9580
|
+
async function generatePaymentCodesBatch(config2, params) {
|
|
9581
|
+
const {
|
|
9582
|
+
signer,
|
|
9583
|
+
codes,
|
|
9584
|
+
payee,
|
|
9585
|
+
usdcContract,
|
|
9586
|
+
network = "base-sepolia",
|
|
9587
|
+
authToken
|
|
9588
|
+
} = params;
|
|
9589
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR;
|
|
9590
|
+
const net = NETWORK_CONFIGS[network];
|
|
9591
|
+
if (!net) throw new Error(`Unsupported network: ${network}`);
|
|
9592
|
+
if (codes.length > 20) throw new Error("Maximum 20 codes per batch");
|
|
9593
|
+
const from4 = await signer.getAddress();
|
|
9594
|
+
const usdc = usdcContract ?? net.usdc;
|
|
9595
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
9596
|
+
const toAddress = await fetchToAddress(facilitatorUrl, network);
|
|
9597
|
+
const signedCodes = await Promise.all(
|
|
9598
|
+
codes.map(async (item) => {
|
|
9599
|
+
const validBefore = now + (item.ttl ?? 86400);
|
|
9600
|
+
const nonce = randomBytes32();
|
|
9601
|
+
const value = BigInt(item.value).toString();
|
|
9602
|
+
const signature2 = await signEip3009(
|
|
9603
|
+
signer,
|
|
9604
|
+
net,
|
|
9605
|
+
from4,
|
|
9606
|
+
toAddress,
|
|
9607
|
+
value,
|
|
9608
|
+
0,
|
|
9609
|
+
validBefore,
|
|
9610
|
+
nonce,
|
|
9611
|
+
usdc
|
|
9612
|
+
);
|
|
9613
|
+
return { value, validAfter: 0, validBefore, nonce, signature: signature2 };
|
|
9614
|
+
})
|
|
9615
|
+
);
|
|
9616
|
+
const res = await fetch(`${facilitatorUrl}/payment-codes/batch`, {
|
|
9508
9617
|
method: "POST",
|
|
9509
|
-
headers: {
|
|
9618
|
+
headers: {
|
|
9619
|
+
"Content-Type": "application/json",
|
|
9620
|
+
"Authorization": `Bearer ${authToken}`
|
|
9621
|
+
},
|
|
9622
|
+
body: JSON.stringify({
|
|
9623
|
+
from: from4,
|
|
9624
|
+
settlementNetwork: net.settlementNetwork,
|
|
9625
|
+
usdcContract: usdc,
|
|
9626
|
+
...payee ? { payee } : {},
|
|
9627
|
+
codes: signedCodes
|
|
9628
|
+
})
|
|
9510
9629
|
});
|
|
9511
9630
|
if (!res.ok) {
|
|
9512
9631
|
const err = await res.json().catch(() => ({}));
|
|
9513
|
-
throw new Error(`
|
|
9632
|
+
throw new Error(`Batch registration failed: ${err.error ?? res.status}`);
|
|
9633
|
+
}
|
|
9634
|
+
return res.json();
|
|
9635
|
+
}
|
|
9636
|
+
async function redeemPaymentCode(config2, code, payee) {
|
|
9637
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR;
|
|
9638
|
+
const res = await fetch(
|
|
9639
|
+
`${facilitatorUrl}/payment-codes/${code.trim().toUpperCase()}/redeem`,
|
|
9640
|
+
{
|
|
9641
|
+
method: "POST",
|
|
9642
|
+
headers: { "Content-Type": "application/json" },
|
|
9643
|
+
body: JSON.stringify(payee ? { payee } : {})
|
|
9644
|
+
}
|
|
9645
|
+
);
|
|
9646
|
+
if (!res.ok) {
|
|
9647
|
+
const err = await res.json().catch(() => ({}));
|
|
9648
|
+
throw new Error(err.error ?? `Redeem failed: ${res.status}`);
|
|
9514
9649
|
}
|
|
9515
9650
|
return res.json();
|
|
9516
9651
|
}
|
|
9517
9652
|
async function getPaymentCode(config2, code) {
|
|
9518
|
-
const facilitatorUrl = config2.facilitatorUrl
|
|
9519
|
-
const res = await fetch(
|
|
9653
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR;
|
|
9654
|
+
const res = await fetch(
|
|
9655
|
+
`${facilitatorUrl}/payment-codes/${code.trim().toUpperCase()}`
|
|
9656
|
+
);
|
|
9657
|
+
if (!res.ok) {
|
|
9658
|
+
const err = await res.json().catch(() => ({}));
|
|
9659
|
+
throw new Error(`Payment code not found: ${err.error ?? res.status}`);
|
|
9660
|
+
}
|
|
9661
|
+
return res.json();
|
|
9662
|
+
}
|
|
9663
|
+
async function cancelPaymentCode(config2, code) {
|
|
9664
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR;
|
|
9665
|
+
const res = await fetch(
|
|
9666
|
+
`${facilitatorUrl}/payment-codes/${code.trim().toUpperCase()}`,
|
|
9667
|
+
{ method: "DELETE" }
|
|
9668
|
+
);
|
|
9669
|
+
if (!res.ok) {
|
|
9670
|
+
const err = await res.json().catch(() => ({}));
|
|
9671
|
+
throw new Error(`Cancel failed: ${err.error ?? res.status}`);
|
|
9672
|
+
}
|
|
9673
|
+
return res.json();
|
|
9674
|
+
}
|
|
9675
|
+
|
|
9676
|
+
// src/payment-requests.ts
|
|
9677
|
+
var DEFAULT_FACILITATOR2 = "https://relai.fi/facilitator";
|
|
9678
|
+
var EIP3009_TYPES2 = {
|
|
9679
|
+
TransferWithAuthorization: [
|
|
9680
|
+
{ name: "from", type: "address" },
|
|
9681
|
+
{ name: "to", type: "address" },
|
|
9682
|
+
{ name: "value", type: "uint256" },
|
|
9683
|
+
{ name: "validAfter", type: "uint256" },
|
|
9684
|
+
{ name: "validBefore", type: "uint256" },
|
|
9685
|
+
{ name: "nonce", type: "bytes32" }
|
|
9686
|
+
]
|
|
9687
|
+
};
|
|
9688
|
+
function randomBytes322() {
|
|
9689
|
+
const bytes = new Uint8Array(32);
|
|
9690
|
+
if (typeof globalThis.crypto !== "undefined") {
|
|
9691
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
9692
|
+
} else {
|
|
9693
|
+
const { randomBytes: randomBytes2 } = require("crypto");
|
|
9694
|
+
randomBytes2(32).copy(Buffer.from(bytes.buffer));
|
|
9695
|
+
}
|
|
9696
|
+
return "0x" + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
9697
|
+
}
|
|
9698
|
+
async function createPayRequest(config2, params) {
|
|
9699
|
+
const {
|
|
9700
|
+
to,
|
|
9701
|
+
amount: amount2,
|
|
9702
|
+
network = "base-sepolia",
|
|
9703
|
+
description,
|
|
9704
|
+
ttlSeconds
|
|
9705
|
+
} = params;
|
|
9706
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR2;
|
|
9707
|
+
const res = await fetch(`${facilitatorUrl}/payment-requests`, {
|
|
9708
|
+
method: "POST",
|
|
9709
|
+
headers: { "Content-Type": "application/json" },
|
|
9710
|
+
body: JSON.stringify({
|
|
9711
|
+
to,
|
|
9712
|
+
amount: Number(amount2),
|
|
9713
|
+
network,
|
|
9714
|
+
...description ? { description } : {},
|
|
9715
|
+
...ttlSeconds ? { ttlSeconds } : {}
|
|
9716
|
+
})
|
|
9717
|
+
});
|
|
9718
|
+
if (!res.ok) {
|
|
9719
|
+
const err = await res.json().catch(() => ({}));
|
|
9720
|
+
throw new Error(`Failed to create payment request: ${err.error ?? res.status}`);
|
|
9721
|
+
}
|
|
9722
|
+
return res.json();
|
|
9723
|
+
}
|
|
9724
|
+
async function getPayRequest(config2, code) {
|
|
9725
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR2;
|
|
9726
|
+
const res = await fetch(
|
|
9727
|
+
`${facilitatorUrl}/payment-requests/${code.trim().toUpperCase()}`
|
|
9728
|
+
);
|
|
9729
|
+
if (!res.ok) {
|
|
9730
|
+
const err = await res.json().catch(() => ({}));
|
|
9731
|
+
throw new Error(`Payment request not found: ${err.error ?? res.status}`);
|
|
9732
|
+
}
|
|
9733
|
+
return res.json();
|
|
9734
|
+
}
|
|
9735
|
+
async function payPayRequest(config2, code, signer) {
|
|
9736
|
+
const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR2;
|
|
9737
|
+
const info = await getPayRequest(config2, code);
|
|
9738
|
+
if (!info.payable) {
|
|
9739
|
+
throw new Error(
|
|
9740
|
+
info.status === "paid" ? "Payment request already paid" : "Payment request expired or not payable"
|
|
9741
|
+
);
|
|
9742
|
+
}
|
|
9743
|
+
const netKey = info.network;
|
|
9744
|
+
const net = NETWORK_CONFIGS[netKey];
|
|
9745
|
+
if (!net) throw new Error(`Unknown network from payment request: ${info.network}`);
|
|
9746
|
+
const from4 = await signer.getAddress();
|
|
9747
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
9748
|
+
const validBefore = Math.min(now + 300, info.validUntil);
|
|
9749
|
+
const nonce = randomBytes322();
|
|
9750
|
+
const domain2 = {
|
|
9751
|
+
name: net.domainName,
|
|
9752
|
+
version: "2",
|
|
9753
|
+
chainId: net.chainId,
|
|
9754
|
+
verifyingContract: info.usdcContract
|
|
9755
|
+
};
|
|
9756
|
+
const signature2 = await signer.signTypedData(domain2, EIP3009_TYPES2, {
|
|
9757
|
+
from: from4,
|
|
9758
|
+
to: info.toAddress,
|
|
9759
|
+
// settler/relayer — NOT the merchant directly
|
|
9760
|
+
value: String(info.amount),
|
|
9761
|
+
validAfter: 0,
|
|
9762
|
+
validBefore,
|
|
9763
|
+
nonce
|
|
9764
|
+
});
|
|
9765
|
+
const res = await fetch(
|
|
9766
|
+
`${facilitatorUrl}/payment-requests/${code.trim().toUpperCase()}/pay`,
|
|
9767
|
+
{
|
|
9768
|
+
method: "POST",
|
|
9769
|
+
headers: { "Content-Type": "application/json" },
|
|
9770
|
+
body: JSON.stringify({ from: from4, validAfter: 0, validBefore, nonce, signature: signature2 })
|
|
9771
|
+
}
|
|
9772
|
+
);
|
|
9773
|
+
if (!res.ok) {
|
|
9774
|
+
const err = await res.json().catch(() => ({}));
|
|
9775
|
+
throw new Error(err.error ?? `Payment failed: ${res.status}`);
|
|
9776
|
+
}
|
|
9777
|
+
return res.json();
|
|
9778
|
+
}
|
|
9779
|
+
async function payPayRequestWithCode(config2, requestCode, paymentCode, options = {}) {
|
|
9780
|
+
const { allowOverpayment = true, returnChange = "code" } = options;
|
|
9781
|
+
const info = await getPayRequest(config2, requestCode);
|
|
9782
|
+
if (!info.payable) {
|
|
9783
|
+
throw new Error(
|
|
9784
|
+
info.status === "paid" ? "Payment request already paid" : "Payment request expired or not payable"
|
|
9785
|
+
);
|
|
9786
|
+
}
|
|
9787
|
+
const codeStatus = await getPaymentCode(config2, paymentCode);
|
|
9788
|
+
if (!codeStatus.redeemable) {
|
|
9789
|
+
throw new Error(
|
|
9790
|
+
codeStatus.redeemed ? "Payment code already redeemed" : "Payment code expired or not redeemable"
|
|
9791
|
+
);
|
|
9792
|
+
}
|
|
9793
|
+
const codeValue = BigInt(codeStatus.value);
|
|
9794
|
+
const reqAmount = BigInt(info.amount);
|
|
9795
|
+
if (codeValue < reqAmount) {
|
|
9796
|
+
throw new Error(
|
|
9797
|
+
`Payment code value (${Number(codeValue) / 1e6} USDC) is less than the request amount (${Number(reqAmount) / 1e6} USDC)`
|
|
9798
|
+
);
|
|
9799
|
+
}
|
|
9800
|
+
if (!allowOverpayment && codeValue > reqAmount) {
|
|
9801
|
+
throw new Error(
|
|
9802
|
+
`Payment code value (${Number(codeValue) / 1e6} USDC) exceeds the request amount (${Number(reqAmount) / 1e6} USDC). Generate a code for the exact amount, or pass { allowOverpayment: true }.`
|
|
9803
|
+
);
|
|
9804
|
+
}
|
|
9805
|
+
const facilitatorUrl = config2.facilitatorUrl ?? "https://relai.fi/facilitator";
|
|
9806
|
+
const usePartial = codeValue > reqAmount;
|
|
9807
|
+
const res = await fetch(
|
|
9808
|
+
`${facilitatorUrl}/payment-codes/${paymentCode.trim().toUpperCase()}/redeem`,
|
|
9809
|
+
{
|
|
9810
|
+
method: "POST",
|
|
9811
|
+
headers: { "Content-Type": "application/json" },
|
|
9812
|
+
body: JSON.stringify({
|
|
9813
|
+
payee: info.to,
|
|
9814
|
+
...usePartial ? { invoiceAmount: info.amount.toString() } : {},
|
|
9815
|
+
...usePartial ? { returnChangeAsCode: returnChange === "code" } : {}
|
|
9816
|
+
})
|
|
9817
|
+
}
|
|
9818
|
+
);
|
|
9520
9819
|
if (!res.ok) {
|
|
9521
9820
|
const err = await res.json().catch(() => ({}));
|
|
9522
|
-
throw new Error(
|
|
9821
|
+
throw new Error(err.error ?? `Redeem failed: ${res.status}`);
|
|
9523
9822
|
}
|
|
9524
9823
|
return res.json();
|
|
9525
9824
|
}
|
|
@@ -26517,6 +26816,7 @@ var BRIDGE_INFO_TTL_MS = 5 * 60 * 1e3;
|
|
|
26517
26816
|
CHAIN_IDS,
|
|
26518
26817
|
EXPLORER_TX_URL,
|
|
26519
26818
|
NETWORK_CAIP2,
|
|
26819
|
+
NETWORK_CONFIGS,
|
|
26520
26820
|
NETWORK_LABELS,
|
|
26521
26821
|
NETWORK_TOKENS,
|
|
26522
26822
|
NETWORK_V1_TO_V2,
|
|
@@ -26528,9 +26828,12 @@ var BRIDGE_INFO_TTL_MS = 5 * 60 * 1e3;
|
|
|
26528
26828
|
USDC_ADDRESSES,
|
|
26529
26829
|
USDC_BASE,
|
|
26530
26830
|
USDC_SOLANA,
|
|
26831
|
+
cancelPaymentCode,
|
|
26531
26832
|
convertPayloadToVersion,
|
|
26532
26833
|
convertV1ToV2,
|
|
26533
26834
|
convertV2ToV1,
|
|
26835
|
+
createPayRequest,
|
|
26836
|
+
createPrivateKeySigner,
|
|
26534
26837
|
createX402Client,
|
|
26535
26838
|
detectPayloadVersion,
|
|
26536
26839
|
evmChargeClient,
|
|
@@ -26539,6 +26842,8 @@ var BRIDGE_INFO_TTL_MS = 5 * 60 * 1e3;
|
|
|
26539
26842
|
formatUsd,
|
|
26540
26843
|
fromAtomicUnits,
|
|
26541
26844
|
generatePaymentCode,
|
|
26845
|
+
generatePaymentCodesBatch,
|
|
26846
|
+
getPayRequest,
|
|
26542
26847
|
getPaymentCode,
|
|
26543
26848
|
isEvm,
|
|
26544
26849
|
isEvmNetwork,
|
|
@@ -26548,6 +26853,8 @@ var BRIDGE_INFO_TTL_MS = 5 * 60 * 1e3;
|
|
|
26548
26853
|
networkV2ToV1,
|
|
26549
26854
|
normalizeNetwork,
|
|
26550
26855
|
normalizePaymentHeader,
|
|
26856
|
+
payPayRequest,
|
|
26857
|
+
payPayRequestWithCode,
|
|
26551
26858
|
redeemPaymentCode,
|
|
26552
26859
|
resolveToken,
|
|
26553
26860
|
stripePayTo,
|