@relai-fi/x402 0.6.5 → 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 +251 -0
- package/dist/bridge.cjs +109 -0
- package/dist/bridge.cjs.map +1 -0
- package/dist/bridge.d.cts +78 -0
- package/dist/bridge.d.ts +78 -0
- package/dist/bridge.js +80 -0
- package/dist/bridge.js.map +1 -0
- package/dist/client.cjs +131 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +13 -1
- package/dist/client.d.ts +13 -1
- package/dist/client.js +131 -1
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +586 -105
- 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 +578 -105
- package/dist/index.js.map +1 -1
- package/dist/mpp/bridge-client.cjs +23922 -0
- package/dist/mpp/bridge-client.cjs.map +1 -0
- package/dist/mpp/bridge-client.d.cts +58 -0
- package/dist/mpp/bridge-client.d.ts +58 -0
- package/dist/mpp/bridge-client.js +23892 -0
- package/dist/mpp/bridge-client.js.map +1 -0
- package/dist/mpp/bridge-method.cjs +13202 -0
- package/dist/mpp/bridge-method.cjs.map +1 -0
- package/dist/mpp/bridge-method.d.cts +69 -0
- package/dist/mpp/bridge-method.d.ts +69 -0
- package/dist/mpp/bridge-method.js +13181 -0
- package/dist/mpp/bridge-method.js.map +1 -0
- package/dist/mpp/bridge-server.cjs +13887 -0
- package/dist/mpp/bridge-server.cjs.map +1 -0
- package/dist/mpp/bridge-server.d.cts +62 -0
- package/dist/mpp/bridge-server.d.ts +62 -0
- package/dist/mpp/bridge-server.js +13866 -0
- package/dist/mpp/bridge-server.js.map +1 -0
- package/dist/mpp/evm-server.cjs +49 -33
- package/dist/mpp/evm-server.cjs.map +1 -1
- package/dist/mpp/evm-server.js +49 -33
- package/dist/mpp/evm-server.js.map +1 -1
- package/dist/mpp/verify-erc20.cjs +71 -0
- package/dist/mpp/verify-erc20.cjs.map +1 -0
- package/dist/mpp/verify-erc20.d.cts +27 -0
- package/dist/mpp/verify-erc20.d.ts +27 -0
- package/dist/mpp/verify-erc20.js +46 -0
- package/dist/mpp/verify-erc20.js.map +1 -0
- package/dist/mpp/verify-spl.cjs +96 -0
- package/dist/mpp/verify-spl.cjs.map +1 -0
- package/dist/mpp/verify-spl.d.cts +30 -0
- package/dist/mpp/verify-spl.d.ts +30 -0
- package/dist/mpp/verify-spl.js +71 -0
- package/dist/mpp/verify-spl.js.map +1 -0
- package/dist/mpp/with-bridge.cjs +23956 -0
- package/dist/mpp/with-bridge.cjs.map +1 -0
- package/dist/mpp/with-bridge.d.cts +53 -0
- package/dist/mpp/with-bridge.d.ts +53 -0
- package/dist/mpp/with-bridge.js +23926 -0
- package/dist/mpp/with-bridge.js.map +1 -0
- package/dist/plugins.d.cts +2 -2
- package/dist/plugins.d.ts +2 -2
- package/dist/react/index.cjs +131 -1
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +131 -1
- package/dist/react/index.js.map +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.cjs +6 -39
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +6 -39
- package/dist/server.js.map +1 -1
- 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 +31 -1
package/README.md
CHANGED
|
@@ -1483,6 +1483,257 @@ Both interfaces are exported from `@relai-fi/x402`.
|
|
|
1483
1483
|
|
|
1484
1484
|
---
|
|
1485
1485
|
|
|
1486
|
+
## Cross-Chain Bridge Flows
|
|
1487
|
+
|
|
1488
|
+
The SDK supports transparent cross-chain payments via the RelAI bridge. There are two approaches depending on who manages the bridge logic — the **client** or the **server**.
|
|
1489
|
+
|
|
1490
|
+
### x402 Auto-Bridge (client-side)
|
|
1491
|
+
|
|
1492
|
+
The server is a standard x402 server. The client has a wallet on a different chain and uses `bridge: { enabled: true }` to auto-bridge.
|
|
1493
|
+
|
|
1494
|
+
```mermaid
|
|
1495
|
+
sequenceDiagram
|
|
1496
|
+
participant Client as Client (Solana)
|
|
1497
|
+
participant Server as Server (Base)
|
|
1498
|
+
participant Bridge as RelAI Bridge API
|
|
1499
|
+
participant Facilitator as Facilitator
|
|
1500
|
+
|
|
1501
|
+
Client->>Server: GET /api/data
|
|
1502
|
+
Server-->>Client: 402 (accepts: Base USDC)
|
|
1503
|
+
|
|
1504
|
+
Note over Client: No Base wallet → auto-bridge
|
|
1505
|
+
|
|
1506
|
+
Client->>Bridge: GET /bridge/info
|
|
1507
|
+
Bridge-->>Client: source chains, payTo, fees
|
|
1508
|
+
|
|
1509
|
+
Client->>Client: Build Solana SPL transfer to bridge payTo
|
|
1510
|
+
Client->>Bridge: POST /bridge/settle (sourcePayment, targetAccept)
|
|
1511
|
+
Bridge->>Bridge: Co-sign & broadcast Solana tx
|
|
1512
|
+
Bridge->>Bridge: Sign EIP-3009 authorization (Base)
|
|
1513
|
+
Bridge-->>Client: xPayment (base64)
|
|
1514
|
+
|
|
1515
|
+
Client->>Server: GET /api/data + X-PAYMENT header
|
|
1516
|
+
Server->>Facilitator: POST /settle (paymentPayload)
|
|
1517
|
+
Facilitator->>Facilitator: Execute transferWithAuthorization on Base
|
|
1518
|
+
Facilitator-->>Server: tx hash
|
|
1519
|
+
Server-->>Client: 200 OK + data
|
|
1520
|
+
```
|
|
1521
|
+
|
|
1522
|
+
### MPP Client-Side Bridge (evmChargeWithBridge)
|
|
1523
|
+
|
|
1524
|
+
The server exposes a standard `evm/charge` MPP method. The client detects it's on a different chain and bridges transparently — the server never knows.
|
|
1525
|
+
|
|
1526
|
+
```mermaid
|
|
1527
|
+
sequenceDiagram
|
|
1528
|
+
participant Client as Client (Tempo)
|
|
1529
|
+
participant Server as Server (SKALE)
|
|
1530
|
+
participant Bridge as RelAI Bridge API
|
|
1531
|
+
participant Facilitator as Facilitator
|
|
1532
|
+
|
|
1533
|
+
Client->>Server: GET /api/data
|
|
1534
|
+
Server-->>Client: 402 + WWW-Authenticate: Payment (evm/charge, chainId=SKALE)
|
|
1535
|
+
|
|
1536
|
+
Note over Client: Source chain ≠ target chain → bridge
|
|
1537
|
+
|
|
1538
|
+
Client->>Bridge: GET /bridge/info
|
|
1539
|
+
Bridge-->>Client: source chains, payTo, fees
|
|
1540
|
+
|
|
1541
|
+
Client->>Client: ERC-20 transfer on Tempo → bridge payTo
|
|
1542
|
+
Client->>Client: Sign settle message (ECDSA)
|
|
1543
|
+
Client->>Bridge: POST /bridge/settle (sourceTxHash, signature, targetAccept)
|
|
1544
|
+
Bridge->>Bridge: Verify source tx on-chain + signature
|
|
1545
|
+
Bridge->>Bridge: Sign EIP-3009 authorization (SKALE)
|
|
1546
|
+
Bridge-->>Client: xPayment + targetTxId=pending
|
|
1547
|
+
|
|
1548
|
+
Client->>Facilitator: POST /settle (paymentPayload, paymentRequirements)
|
|
1549
|
+
Facilitator->>Facilitator: Execute transferWithAuthorization on SKALE
|
|
1550
|
+
Facilitator-->>Client: tx hash (SKALE)
|
|
1551
|
+
|
|
1552
|
+
Client->>Server: GET /api/data + Authorization: Payment (hash=SKALE tx)
|
|
1553
|
+
Server->>Server: Verify ERC-20 Transfer on SKALE (standard evm/charge verify)
|
|
1554
|
+
Server-->>Client: 200 OK + data
|
|
1555
|
+
```
|
|
1556
|
+
|
|
1557
|
+
### MPP Server-Side Bridge (bridge/charge method)
|
|
1558
|
+
|
|
1559
|
+
The server explicitly exposes a `bridge/charge` method alongside its direct method. The client picks bridge/charge when it can't pay directly. Use this when the target chain is not EVM (e.g. Solana).
|
|
1560
|
+
|
|
1561
|
+
```mermaid
|
|
1562
|
+
sequenceDiagram
|
|
1563
|
+
participant Client as Client (Tempo)
|
|
1564
|
+
participant Server as Server (SKALE)
|
|
1565
|
+
participant Bridge as RelAI Bridge API
|
|
1566
|
+
participant Facilitator as Facilitator
|
|
1567
|
+
|
|
1568
|
+
Client->>Server: GET /api/data
|
|
1569
|
+
Server->>Bridge: GET /bridge/info (auto-discover)
|
|
1570
|
+
Server-->>Client: 402 + WWW-Authenticate: Payment<br/>(evm/charge + bridge/charge)
|
|
1571
|
+
|
|
1572
|
+
Note over Client: Can't match evm/charge → picks bridge/charge
|
|
1573
|
+
|
|
1574
|
+
Client->>Client: ERC-20 transfer on Tempo → bridge payTo
|
|
1575
|
+
Client->>Client: Sign settle message (ECDSA)
|
|
1576
|
+
Client->>Bridge: POST /bridge/settle (sourceTxHash, signature, targetAccept)
|
|
1577
|
+
Bridge->>Bridge: Verify source tx on-chain + signature
|
|
1578
|
+
Bridge->>Bridge: Sign EIP-3009 authorization (SKALE)
|
|
1579
|
+
Bridge-->>Client: xPayment + targetTxId=pending
|
|
1580
|
+
|
|
1581
|
+
Client->>Facilitator: POST /settle (paymentPayload, paymentRequirements)
|
|
1582
|
+
Facilitator->>Facilitator: Execute transferWithAuthorization on SKALE
|
|
1583
|
+
Facilitator-->>Client: tx hash (SKALE)
|
|
1584
|
+
|
|
1585
|
+
Client->>Server: Authorization: Payment (targetTxHash=SKALE tx)
|
|
1586
|
+
Server->>Server: bridge/charge verify() — check ERC-20 Transfer on SKALE
|
|
1587
|
+
Server-->>Client: 200 OK + data
|
|
1588
|
+
```
|
|
1589
|
+
|
|
1590
|
+
### When to use which approach
|
|
1591
|
+
|
|
1592
|
+
| Approach | Server config | Client config | Best for |
|
|
1593
|
+
|----------|--------------|---------------|----------|
|
|
1594
|
+
| **x402 auto-bridge** | Standard x402 (no change) | `bridge: { enabled: true }` | Solana client → EVM server |
|
|
1595
|
+
| **MPP client-side** | Standard `evm/charge` (no change) | `evmChargeWithBridge()` | EVM client → different EVM server |
|
|
1596
|
+
| **MPP server-side** | `evm/charge` + `bridge/charge` | `bridgeCharge()` client | Any client → Solana server, or when server wants to control bridge options |
|
|
1597
|
+
|
|
1598
|
+
See [`examples/bridge/`](./examples/bridge/) for runnable examples of each approach.
|
|
1599
|
+
|
|
1600
|
+
---
|
|
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
|
+
|
|
1486
1737
|
## Development
|
|
1487
1738
|
|
|
1488
1739
|
```bash
|
package/dist/bridge.cjs
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/bridge.ts
|
|
21
|
+
var bridge_exports = {};
|
|
22
|
+
__export(bridge_exports, {
|
|
23
|
+
DEFAULT_EVM_RPC: () => DEFAULT_EVM_RPC,
|
|
24
|
+
computeSourceAmount: () => computeSourceAmount,
|
|
25
|
+
getBridgeInfo: () => getBridgeInfo,
|
|
26
|
+
selectSourceChain: () => selectSourceChain,
|
|
27
|
+
settleBridge: () => settleBridge
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(bridge_exports);
|
|
30
|
+
var RELAI_API_BASE = "https://api.relai.fi";
|
|
31
|
+
var _cacheMap = /* @__PURE__ */ new Map();
|
|
32
|
+
var CACHE_TTL = 5 * 60 * 1e3;
|
|
33
|
+
async function getBridgeInfo(baseUrl = RELAI_API_BASE) {
|
|
34
|
+
const key = baseUrl.replace(/\/$/, "");
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
const cached = _cacheMap.get(key);
|
|
37
|
+
if (cached && now - cached.time < CACHE_TTL) return cached.info;
|
|
38
|
+
const url = `${key}/bridge/info`;
|
|
39
|
+
const res = await fetch(url);
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
if (cached) return cached.info;
|
|
42
|
+
throw new Error(`[relai:bridge] Failed to fetch ${url}: ${res.status}`);
|
|
43
|
+
}
|
|
44
|
+
const data = await res.json();
|
|
45
|
+
const info = {
|
|
46
|
+
settleEndpoint: data.settleEndpoint,
|
|
47
|
+
supportedSourceChains: data.supportedSourceChains || [],
|
|
48
|
+
supportedSourceAssets: data.supportedSourceAssets || [],
|
|
49
|
+
payTo: data.payTo || {},
|
|
50
|
+
feePayerSvm: data.feePayerSvm ?? null,
|
|
51
|
+
feeBps: data.feeBps ?? 100,
|
|
52
|
+
paymentFacilitator: data.paymentFacilitator || "https://facilitator.x402.fi"
|
|
53
|
+
};
|
|
54
|
+
_cacheMap.set(key, { info, time: now });
|
|
55
|
+
return info;
|
|
56
|
+
}
|
|
57
|
+
async function settleBridge(settleEndpoint, body) {
|
|
58
|
+
const res = await fetch(settleEndpoint, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: { "Content-Type": "application/json" },
|
|
61
|
+
body: JSON.stringify(body)
|
|
62
|
+
});
|
|
63
|
+
if (!res.ok) {
|
|
64
|
+
const err = await res.json().catch(() => ({}));
|
|
65
|
+
throw new Error(`[relai:bridge] settle failed: ${err.error || res.status}${err.details ? " \u2014 " + err.details : ""}`);
|
|
66
|
+
}
|
|
67
|
+
return res.json();
|
|
68
|
+
}
|
|
69
|
+
function selectSourceChain(supportedChains, hasEvmWallet, hasSolanaWallet, preferredSourceChainId) {
|
|
70
|
+
if (preferredSourceChainId && hasEvmWallet) {
|
|
71
|
+
const preferred = `eip155:${preferredSourceChainId}`;
|
|
72
|
+
if (supportedChains.includes(preferred)) {
|
|
73
|
+
return { type: "evm", chain: preferred };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (hasSolanaWallet) {
|
|
77
|
+
const sol = supportedChains.find((c) => c.startsWith("solana:"));
|
|
78
|
+
if (sol) return { type: "solana", chain: sol };
|
|
79
|
+
}
|
|
80
|
+
if (hasEvmWallet) {
|
|
81
|
+
for (const chain of supportedChains) {
|
|
82
|
+
if (chain.startsWith("eip155:")) {
|
|
83
|
+
return { type: "evm", chain };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
function computeSourceAmount(targetAmount, feeBps) {
|
|
90
|
+
const fee = targetAmount * BigInt(feeBps) / 10000n;
|
|
91
|
+
return targetAmount + fee;
|
|
92
|
+
}
|
|
93
|
+
var DEFAULT_EVM_RPC = {
|
|
94
|
+
"eip155:8453": "https://mainnet.base.org",
|
|
95
|
+
"eip155:137": "https://polygon-rpc.com",
|
|
96
|
+
"eip155:1": "https://eth.llamarpc.com",
|
|
97
|
+
"eip155:42161": "https://arb1.arbitrum.io/rpc",
|
|
98
|
+
"eip155:43114": "https://api.avax.network/ext/bc/C/rpc",
|
|
99
|
+
"eip155:1187947933": "https://skale-base.skalenodes.com/v1/base"
|
|
100
|
+
};
|
|
101
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
102
|
+
0 && (module.exports = {
|
|
103
|
+
DEFAULT_EVM_RPC,
|
|
104
|
+
computeSourceAmount,
|
|
105
|
+
getBridgeInfo,
|
|
106
|
+
selectSourceChain,
|
|
107
|
+
settleBridge
|
|
108
|
+
});
|
|
109
|
+
//# sourceMappingURL=bridge.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bridge.ts"],"sourcesContent":["/**\n * Core bridge utilities — shared between x402 and MPP clients.\n *\n * Auto-discovers bridge info from the RelAI API and provides helpers\n * for executing cross-chain payments transparently.\n *\n * Usage (MPP):\n * ```ts\n * import { evmChargeWithBridge } from '@relai-fi/x402/mpp/with-bridge'\n * ```\n *\n * Usage (x402):\n * ```ts\n * import { createX402Client } from '@relai-fi/x402'\n * const client = createX402Client({ ..., bridge: { enabled: true } })\n * ```\n */\n\nconst RELAI_API_BASE = 'https://api.relai.fi'\n\nexport interface BridgeInfo {\n settleEndpoint: string\n supportedSourceChains: string[]\n supportedSourceAssets: string[]\n payTo: Record<string, string>\n feePayerSvm: string | null\n feeBps: number\n paymentFacilitator: string\n}\n\nexport interface BridgeSettleRequest {\n /** x402 mode: base64-encoded payment header */\n sourcePayment?: string\n /** MPP mode: source tx hash */\n sourceTxHash?: string\n /** MPP mode: wallet address that sent the source tx */\n senderAddress?: string\n /** MPP mode: signature of (sourceTxHash + JSON(targetAccept)) */\n senderSignature?: string\n /** CAIP-2 source chain identifier */\n sourceChain: string\n targetAccept: {\n scheme: 'exact'\n network: string\n asset: string\n payTo: string\n amount: string\n }\n /** x402 payment requirements (full 402 body) — needed by settle for x402 flow */\n requirements?: any\n resource?: string\n paymentFacilitator?: string | null\n}\n\nexport interface BridgeSettleResponse {\n success?: boolean\n targetTxId?: string\n sourceTxId?: string\n /** x402 payment header (returned by x402-flavored settle) */\n xPayment?: string\n}\n\n// ── Cache (keyed by base URL) ──────────────────────────────────────────\n\nconst _cacheMap = new Map<string, { info: BridgeInfo; time: number }>()\nconst CACHE_TTL = 5 * 60 * 1000 // 5 minutes\n\n/**\n * Fetch bridge info from the RelAI API (cached per base URL for 5 minutes).\n */\nexport async function getBridgeInfo(baseUrl = RELAI_API_BASE): Promise<BridgeInfo> {\n const key = baseUrl.replace(/\\/$/, '')\n const now = Date.now()\n const cached = _cacheMap.get(key)\n if (cached && now - cached.time < CACHE_TTL) return cached.info\n\n const url = `${key}/bridge/info`\n const res = await fetch(url)\n if (!res.ok) {\n if (cached) return cached.info // stale cache better than failure\n throw new Error(`[relai:bridge] Failed to fetch ${url}: ${res.status}`)\n }\n\n const data = (await res.json()) as any\n const info: BridgeInfo = {\n settleEndpoint: data.settleEndpoint,\n supportedSourceChains: data.supportedSourceChains || [],\n supportedSourceAssets: data.supportedSourceAssets || [],\n payTo: data.payTo || {},\n feePayerSvm: data.feePayerSvm ?? null,\n feeBps: data.feeBps ?? 100,\n paymentFacilitator: data.paymentFacilitator || 'https://facilitator.x402.fi',\n }\n _cacheMap.set(key, { info, time: now })\n return info\n}\n\n/**\n * Call the bridge settle endpoint.\n */\nexport async function settleBridge(\n settleEndpoint: string,\n body: BridgeSettleRequest,\n): Promise<BridgeSettleResponse> {\n const res = await fetch(settleEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n\n if (!res.ok) {\n const err = (await res.json().catch(() => ({}))) as any\n throw new Error(`[relai:bridge] settle failed: ${err.error || res.status}${err.details ? ' — ' + err.details : ''}`)\n }\n\n return res.json() as Promise<BridgeSettleResponse>\n}\n\n/**\n * Select a source chain from supported chains that matches the available wallet.\n */\nexport function selectSourceChain(\n supportedChains: string[],\n hasEvmWallet: boolean,\n hasSolanaWallet: boolean,\n preferredSourceChainId?: number,\n): { type: 'evm' | 'solana'; chain: string } | null {\n // If a specific source chain is preferred, try it first\n if (preferredSourceChainId && hasEvmWallet) {\n const preferred = `eip155:${preferredSourceChainId}`\n if (supportedChains.includes(preferred)) {\n return { type: 'evm', chain: preferred }\n }\n }\n\n // Solana first (lower fees)\n if (hasSolanaWallet) {\n const sol = supportedChains.find((c) => c.startsWith('solana:'))\n if (sol) return { type: 'solana', chain: sol }\n }\n\n // Then any EVM chain\n if (hasEvmWallet) {\n for (const chain of supportedChains) {\n if (chain.startsWith('eip155:')) {\n return { type: 'evm', chain }\n }\n }\n }\n\n return null\n}\n\n/**\n * Compute source amount including bridge fee.\n */\nexport function computeSourceAmount(targetAmount: bigint, feeBps: number): bigint {\n const fee = (targetAmount * BigInt(feeBps)) / 10000n\n return targetAmount + fee\n}\n\n// Known EVM RPC URLs (fallback)\nexport const DEFAULT_EVM_RPC: Record<string, string> = {\n 'eip155:8453': 'https://mainnet.base.org',\n 'eip155:137': 'https://polygon-rpc.com',\n 'eip155:1': 'https://eth.llamarpc.com',\n 'eip155:42161': 'https://arb1.arbitrum.io/rpc',\n 'eip155:43114': 'https://api.avax.network/ext/bc/C/rpc',\n 'eip155:1187947933': 'https://skale-base.skalenodes.com/v1/base',\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkBA,IAAM,iBAAiB;AA8CvB,IAAM,YAAY,oBAAI,IAAgD;AACtE,IAAM,YAAY,IAAI,KAAK;AAK3B,eAAsB,cAAc,UAAU,gBAAqC;AACjF,QAAM,MAAM,QAAQ,QAAQ,OAAO,EAAE;AACrC,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,SAAS,UAAU,IAAI,GAAG;AAChC,MAAI,UAAU,MAAM,OAAO,OAAO,UAAW,QAAO,OAAO;AAE3D,QAAM,MAAM,GAAG,GAAG;AAClB,QAAM,MAAM,MAAM,MAAM,GAAG;AAC3B,MAAI,CAAC,IAAI,IAAI;AACX,QAAI,OAAQ,QAAO,OAAO;AAC1B,UAAM,IAAI,MAAM,kCAAkC,GAAG,KAAK,IAAI,MAAM,EAAE;AAAA,EACxE;AAEA,QAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,QAAM,OAAmB;AAAA,IACvB,gBAAgB,KAAK;AAAA,IACrB,uBAAuB,KAAK,yBAAyB,CAAC;AAAA,IACtD,uBAAuB,KAAK,yBAAyB,CAAC;AAAA,IACtD,OAAO,KAAK,SAAS,CAAC;AAAA,IACtB,aAAa,KAAK,eAAe;AAAA,IACjC,QAAQ,KAAK,UAAU;AAAA,IACvB,oBAAoB,KAAK,sBAAsB;AAAA,EACjD;AACA,YAAU,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI,CAAC;AACtC,SAAO;AACT;AAKA,eAAsB,aACpB,gBACA,MAC+B;AAC/B,QAAM,MAAM,MAAM,MAAM,gBAAgB;AAAA,IACtC,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU,IAAI;AAAA,EAC3B,CAAC;AAED,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,MAAO,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC9C,UAAM,IAAI,MAAM,iCAAiC,IAAI,SAAS,IAAI,MAAM,GAAG,IAAI,UAAU,aAAQ,IAAI,UAAU,EAAE,EAAE;AAAA,EACrH;AAEA,SAAO,IAAI,KAAK;AAClB;AAKO,SAAS,kBACd,iBACA,cACA,iBACA,wBACkD;AAElD,MAAI,0BAA0B,cAAc;AAC1C,UAAM,YAAY,UAAU,sBAAsB;AAClD,QAAI,gBAAgB,SAAS,SAAS,GAAG;AACvC,aAAO,EAAE,MAAM,OAAO,OAAO,UAAU;AAAA,IACzC;AAAA,EACF;AAGA,MAAI,iBAAiB;AACnB,UAAM,MAAM,gBAAgB,KAAK,CAAC,MAAM,EAAE,WAAW,SAAS,CAAC;AAC/D,QAAI,IAAK,QAAO,EAAE,MAAM,UAAU,OAAO,IAAI;AAAA,EAC/C;AAGA,MAAI,cAAc;AAChB,eAAW,SAAS,iBAAiB;AACnC,UAAI,MAAM,WAAW,SAAS,GAAG;AAC/B,eAAO,EAAE,MAAM,OAAO,MAAM;AAAA,MAC9B;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,oBAAoB,cAAsB,QAAwB;AAChF,QAAM,MAAO,eAAe,OAAO,MAAM,IAAK;AAC9C,SAAO,eAAe;AACxB;AAGO,IAAM,kBAA0C;AAAA,EACrD,eAAe;AAAA,EACf,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,qBAAqB;AACvB;","names":[]}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core bridge utilities — shared between x402 and MPP clients.
|
|
3
|
+
*
|
|
4
|
+
* Auto-discovers bridge info from the RelAI API and provides helpers
|
|
5
|
+
* for executing cross-chain payments transparently.
|
|
6
|
+
*
|
|
7
|
+
* Usage (MPP):
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { evmChargeWithBridge } from '@relai-fi/x402/mpp/with-bridge'
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Usage (x402):
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { createX402Client } from '@relai-fi/x402'
|
|
15
|
+
* const client = createX402Client({ ..., bridge: { enabled: true } })
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
interface BridgeInfo {
|
|
19
|
+
settleEndpoint: string;
|
|
20
|
+
supportedSourceChains: string[];
|
|
21
|
+
supportedSourceAssets: string[];
|
|
22
|
+
payTo: Record<string, string>;
|
|
23
|
+
feePayerSvm: string | null;
|
|
24
|
+
feeBps: number;
|
|
25
|
+
paymentFacilitator: string;
|
|
26
|
+
}
|
|
27
|
+
interface BridgeSettleRequest {
|
|
28
|
+
/** x402 mode: base64-encoded payment header */
|
|
29
|
+
sourcePayment?: string;
|
|
30
|
+
/** MPP mode: source tx hash */
|
|
31
|
+
sourceTxHash?: string;
|
|
32
|
+
/** MPP mode: wallet address that sent the source tx */
|
|
33
|
+
senderAddress?: string;
|
|
34
|
+
/** MPP mode: signature of (sourceTxHash + JSON(targetAccept)) */
|
|
35
|
+
senderSignature?: string;
|
|
36
|
+
/** CAIP-2 source chain identifier */
|
|
37
|
+
sourceChain: string;
|
|
38
|
+
targetAccept: {
|
|
39
|
+
scheme: 'exact';
|
|
40
|
+
network: string;
|
|
41
|
+
asset: string;
|
|
42
|
+
payTo: string;
|
|
43
|
+
amount: string;
|
|
44
|
+
};
|
|
45
|
+
/** x402 payment requirements (full 402 body) — needed by settle for x402 flow */
|
|
46
|
+
requirements?: any;
|
|
47
|
+
resource?: string;
|
|
48
|
+
paymentFacilitator?: string | null;
|
|
49
|
+
}
|
|
50
|
+
interface BridgeSettleResponse {
|
|
51
|
+
success?: boolean;
|
|
52
|
+
targetTxId?: string;
|
|
53
|
+
sourceTxId?: string;
|
|
54
|
+
/** x402 payment header (returned by x402-flavored settle) */
|
|
55
|
+
xPayment?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Fetch bridge info from the RelAI API (cached per base URL for 5 minutes).
|
|
59
|
+
*/
|
|
60
|
+
declare function getBridgeInfo(baseUrl?: string): Promise<BridgeInfo>;
|
|
61
|
+
/**
|
|
62
|
+
* Call the bridge settle endpoint.
|
|
63
|
+
*/
|
|
64
|
+
declare function settleBridge(settleEndpoint: string, body: BridgeSettleRequest): Promise<BridgeSettleResponse>;
|
|
65
|
+
/**
|
|
66
|
+
* Select a source chain from supported chains that matches the available wallet.
|
|
67
|
+
*/
|
|
68
|
+
declare function selectSourceChain(supportedChains: string[], hasEvmWallet: boolean, hasSolanaWallet: boolean, preferredSourceChainId?: number): {
|
|
69
|
+
type: 'evm' | 'solana';
|
|
70
|
+
chain: string;
|
|
71
|
+
} | null;
|
|
72
|
+
/**
|
|
73
|
+
* Compute source amount including bridge fee.
|
|
74
|
+
*/
|
|
75
|
+
declare function computeSourceAmount(targetAmount: bigint, feeBps: number): bigint;
|
|
76
|
+
declare const DEFAULT_EVM_RPC: Record<string, string>;
|
|
77
|
+
|
|
78
|
+
export { type BridgeInfo, type BridgeSettleRequest, type BridgeSettleResponse, DEFAULT_EVM_RPC, computeSourceAmount, getBridgeInfo, selectSourceChain, settleBridge };
|
package/dist/bridge.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core bridge utilities — shared between x402 and MPP clients.
|
|
3
|
+
*
|
|
4
|
+
* Auto-discovers bridge info from the RelAI API and provides helpers
|
|
5
|
+
* for executing cross-chain payments transparently.
|
|
6
|
+
*
|
|
7
|
+
* Usage (MPP):
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { evmChargeWithBridge } from '@relai-fi/x402/mpp/with-bridge'
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Usage (x402):
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { createX402Client } from '@relai-fi/x402'
|
|
15
|
+
* const client = createX402Client({ ..., bridge: { enabled: true } })
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
interface BridgeInfo {
|
|
19
|
+
settleEndpoint: string;
|
|
20
|
+
supportedSourceChains: string[];
|
|
21
|
+
supportedSourceAssets: string[];
|
|
22
|
+
payTo: Record<string, string>;
|
|
23
|
+
feePayerSvm: string | null;
|
|
24
|
+
feeBps: number;
|
|
25
|
+
paymentFacilitator: string;
|
|
26
|
+
}
|
|
27
|
+
interface BridgeSettleRequest {
|
|
28
|
+
/** x402 mode: base64-encoded payment header */
|
|
29
|
+
sourcePayment?: string;
|
|
30
|
+
/** MPP mode: source tx hash */
|
|
31
|
+
sourceTxHash?: string;
|
|
32
|
+
/** MPP mode: wallet address that sent the source tx */
|
|
33
|
+
senderAddress?: string;
|
|
34
|
+
/** MPP mode: signature of (sourceTxHash + JSON(targetAccept)) */
|
|
35
|
+
senderSignature?: string;
|
|
36
|
+
/** CAIP-2 source chain identifier */
|
|
37
|
+
sourceChain: string;
|
|
38
|
+
targetAccept: {
|
|
39
|
+
scheme: 'exact';
|
|
40
|
+
network: string;
|
|
41
|
+
asset: string;
|
|
42
|
+
payTo: string;
|
|
43
|
+
amount: string;
|
|
44
|
+
};
|
|
45
|
+
/** x402 payment requirements (full 402 body) — needed by settle for x402 flow */
|
|
46
|
+
requirements?: any;
|
|
47
|
+
resource?: string;
|
|
48
|
+
paymentFacilitator?: string | null;
|
|
49
|
+
}
|
|
50
|
+
interface BridgeSettleResponse {
|
|
51
|
+
success?: boolean;
|
|
52
|
+
targetTxId?: string;
|
|
53
|
+
sourceTxId?: string;
|
|
54
|
+
/** x402 payment header (returned by x402-flavored settle) */
|
|
55
|
+
xPayment?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Fetch bridge info from the RelAI API (cached per base URL for 5 minutes).
|
|
59
|
+
*/
|
|
60
|
+
declare function getBridgeInfo(baseUrl?: string): Promise<BridgeInfo>;
|
|
61
|
+
/**
|
|
62
|
+
* Call the bridge settle endpoint.
|
|
63
|
+
*/
|
|
64
|
+
declare function settleBridge(settleEndpoint: string, body: BridgeSettleRequest): Promise<BridgeSettleResponse>;
|
|
65
|
+
/**
|
|
66
|
+
* Select a source chain from supported chains that matches the available wallet.
|
|
67
|
+
*/
|
|
68
|
+
declare function selectSourceChain(supportedChains: string[], hasEvmWallet: boolean, hasSolanaWallet: boolean, preferredSourceChainId?: number): {
|
|
69
|
+
type: 'evm' | 'solana';
|
|
70
|
+
chain: string;
|
|
71
|
+
} | null;
|
|
72
|
+
/**
|
|
73
|
+
* Compute source amount including bridge fee.
|
|
74
|
+
*/
|
|
75
|
+
declare function computeSourceAmount(targetAmount: bigint, feeBps: number): bigint;
|
|
76
|
+
declare const DEFAULT_EVM_RPC: Record<string, string>;
|
|
77
|
+
|
|
78
|
+
export { type BridgeInfo, type BridgeSettleRequest, type BridgeSettleResponse, DEFAULT_EVM_RPC, computeSourceAmount, getBridgeInfo, selectSourceChain, settleBridge };
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// src/bridge.ts
|
|
2
|
+
var RELAI_API_BASE = "https://api.relai.fi";
|
|
3
|
+
var _cacheMap = /* @__PURE__ */ new Map();
|
|
4
|
+
var CACHE_TTL = 5 * 60 * 1e3;
|
|
5
|
+
async function getBridgeInfo(baseUrl = RELAI_API_BASE) {
|
|
6
|
+
const key = baseUrl.replace(/\/$/, "");
|
|
7
|
+
const now = Date.now();
|
|
8
|
+
const cached = _cacheMap.get(key);
|
|
9
|
+
if (cached && now - cached.time < CACHE_TTL) return cached.info;
|
|
10
|
+
const url = `${key}/bridge/info`;
|
|
11
|
+
const res = await fetch(url);
|
|
12
|
+
if (!res.ok) {
|
|
13
|
+
if (cached) return cached.info;
|
|
14
|
+
throw new Error(`[relai:bridge] Failed to fetch ${url}: ${res.status}`);
|
|
15
|
+
}
|
|
16
|
+
const data = await res.json();
|
|
17
|
+
const info = {
|
|
18
|
+
settleEndpoint: data.settleEndpoint,
|
|
19
|
+
supportedSourceChains: data.supportedSourceChains || [],
|
|
20
|
+
supportedSourceAssets: data.supportedSourceAssets || [],
|
|
21
|
+
payTo: data.payTo || {},
|
|
22
|
+
feePayerSvm: data.feePayerSvm ?? null,
|
|
23
|
+
feeBps: data.feeBps ?? 100,
|
|
24
|
+
paymentFacilitator: data.paymentFacilitator || "https://facilitator.x402.fi"
|
|
25
|
+
};
|
|
26
|
+
_cacheMap.set(key, { info, time: now });
|
|
27
|
+
return info;
|
|
28
|
+
}
|
|
29
|
+
async function settleBridge(settleEndpoint, body) {
|
|
30
|
+
const res = await fetch(settleEndpoint, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: { "Content-Type": "application/json" },
|
|
33
|
+
body: JSON.stringify(body)
|
|
34
|
+
});
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
const err = await res.json().catch(() => ({}));
|
|
37
|
+
throw new Error(`[relai:bridge] settle failed: ${err.error || res.status}${err.details ? " \u2014 " + err.details : ""}`);
|
|
38
|
+
}
|
|
39
|
+
return res.json();
|
|
40
|
+
}
|
|
41
|
+
function selectSourceChain(supportedChains, hasEvmWallet, hasSolanaWallet, preferredSourceChainId) {
|
|
42
|
+
if (preferredSourceChainId && hasEvmWallet) {
|
|
43
|
+
const preferred = `eip155:${preferredSourceChainId}`;
|
|
44
|
+
if (supportedChains.includes(preferred)) {
|
|
45
|
+
return { type: "evm", chain: preferred };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (hasSolanaWallet) {
|
|
49
|
+
const sol = supportedChains.find((c) => c.startsWith("solana:"));
|
|
50
|
+
if (sol) return { type: "solana", chain: sol };
|
|
51
|
+
}
|
|
52
|
+
if (hasEvmWallet) {
|
|
53
|
+
for (const chain of supportedChains) {
|
|
54
|
+
if (chain.startsWith("eip155:")) {
|
|
55
|
+
return { type: "evm", chain };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function computeSourceAmount(targetAmount, feeBps) {
|
|
62
|
+
const fee = targetAmount * BigInt(feeBps) / 10000n;
|
|
63
|
+
return targetAmount + fee;
|
|
64
|
+
}
|
|
65
|
+
var DEFAULT_EVM_RPC = {
|
|
66
|
+
"eip155:8453": "https://mainnet.base.org",
|
|
67
|
+
"eip155:137": "https://polygon-rpc.com",
|
|
68
|
+
"eip155:1": "https://eth.llamarpc.com",
|
|
69
|
+
"eip155:42161": "https://arb1.arbitrum.io/rpc",
|
|
70
|
+
"eip155:43114": "https://api.avax.network/ext/bc/C/rpc",
|
|
71
|
+
"eip155:1187947933": "https://skale-base.skalenodes.com/v1/base"
|
|
72
|
+
};
|
|
73
|
+
export {
|
|
74
|
+
DEFAULT_EVM_RPC,
|
|
75
|
+
computeSourceAmount,
|
|
76
|
+
getBridgeInfo,
|
|
77
|
+
selectSourceChain,
|
|
78
|
+
settleBridge
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bridge.ts"],"sourcesContent":["/**\n * Core bridge utilities — shared between x402 and MPP clients.\n *\n * Auto-discovers bridge info from the RelAI API and provides helpers\n * for executing cross-chain payments transparently.\n *\n * Usage (MPP):\n * ```ts\n * import { evmChargeWithBridge } from '@relai-fi/x402/mpp/with-bridge'\n * ```\n *\n * Usage (x402):\n * ```ts\n * import { createX402Client } from '@relai-fi/x402'\n * const client = createX402Client({ ..., bridge: { enabled: true } })\n * ```\n */\n\nconst RELAI_API_BASE = 'https://api.relai.fi'\n\nexport interface BridgeInfo {\n settleEndpoint: string\n supportedSourceChains: string[]\n supportedSourceAssets: string[]\n payTo: Record<string, string>\n feePayerSvm: string | null\n feeBps: number\n paymentFacilitator: string\n}\n\nexport interface BridgeSettleRequest {\n /** x402 mode: base64-encoded payment header */\n sourcePayment?: string\n /** MPP mode: source tx hash */\n sourceTxHash?: string\n /** MPP mode: wallet address that sent the source tx */\n senderAddress?: string\n /** MPP mode: signature of (sourceTxHash + JSON(targetAccept)) */\n senderSignature?: string\n /** CAIP-2 source chain identifier */\n sourceChain: string\n targetAccept: {\n scheme: 'exact'\n network: string\n asset: string\n payTo: string\n amount: string\n }\n /** x402 payment requirements (full 402 body) — needed by settle for x402 flow */\n requirements?: any\n resource?: string\n paymentFacilitator?: string | null\n}\n\nexport interface BridgeSettleResponse {\n success?: boolean\n targetTxId?: string\n sourceTxId?: string\n /** x402 payment header (returned by x402-flavored settle) */\n xPayment?: string\n}\n\n// ── Cache (keyed by base URL) ──────────────────────────────────────────\n\nconst _cacheMap = new Map<string, { info: BridgeInfo; time: number }>()\nconst CACHE_TTL = 5 * 60 * 1000 // 5 minutes\n\n/**\n * Fetch bridge info from the RelAI API (cached per base URL for 5 minutes).\n */\nexport async function getBridgeInfo(baseUrl = RELAI_API_BASE): Promise<BridgeInfo> {\n const key = baseUrl.replace(/\\/$/, '')\n const now = Date.now()\n const cached = _cacheMap.get(key)\n if (cached && now - cached.time < CACHE_TTL) return cached.info\n\n const url = `${key}/bridge/info`\n const res = await fetch(url)\n if (!res.ok) {\n if (cached) return cached.info // stale cache better than failure\n throw new Error(`[relai:bridge] Failed to fetch ${url}: ${res.status}`)\n }\n\n const data = (await res.json()) as any\n const info: BridgeInfo = {\n settleEndpoint: data.settleEndpoint,\n supportedSourceChains: data.supportedSourceChains || [],\n supportedSourceAssets: data.supportedSourceAssets || [],\n payTo: data.payTo || {},\n feePayerSvm: data.feePayerSvm ?? null,\n feeBps: data.feeBps ?? 100,\n paymentFacilitator: data.paymentFacilitator || 'https://facilitator.x402.fi',\n }\n _cacheMap.set(key, { info, time: now })\n return info\n}\n\n/**\n * Call the bridge settle endpoint.\n */\nexport async function settleBridge(\n settleEndpoint: string,\n body: BridgeSettleRequest,\n): Promise<BridgeSettleResponse> {\n const res = await fetch(settleEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n\n if (!res.ok) {\n const err = (await res.json().catch(() => ({}))) as any\n throw new Error(`[relai:bridge] settle failed: ${err.error || res.status}${err.details ? ' — ' + err.details : ''}`)\n }\n\n return res.json() as Promise<BridgeSettleResponse>\n}\n\n/**\n * Select a source chain from supported chains that matches the available wallet.\n */\nexport function selectSourceChain(\n supportedChains: string[],\n hasEvmWallet: boolean,\n hasSolanaWallet: boolean,\n preferredSourceChainId?: number,\n): { type: 'evm' | 'solana'; chain: string } | null {\n // If a specific source chain is preferred, try it first\n if (preferredSourceChainId && hasEvmWallet) {\n const preferred = `eip155:${preferredSourceChainId}`\n if (supportedChains.includes(preferred)) {\n return { type: 'evm', chain: preferred }\n }\n }\n\n // Solana first (lower fees)\n if (hasSolanaWallet) {\n const sol = supportedChains.find((c) => c.startsWith('solana:'))\n if (sol) return { type: 'solana', chain: sol }\n }\n\n // Then any EVM chain\n if (hasEvmWallet) {\n for (const chain of supportedChains) {\n if (chain.startsWith('eip155:')) {\n return { type: 'evm', chain }\n }\n }\n }\n\n return null\n}\n\n/**\n * Compute source amount including bridge fee.\n */\nexport function computeSourceAmount(targetAmount: bigint, feeBps: number): bigint {\n const fee = (targetAmount * BigInt(feeBps)) / 10000n\n return targetAmount + fee\n}\n\n// Known EVM RPC URLs (fallback)\nexport const DEFAULT_EVM_RPC: Record<string, string> = {\n 'eip155:8453': 'https://mainnet.base.org',\n 'eip155:137': 'https://polygon-rpc.com',\n 'eip155:1': 'https://eth.llamarpc.com',\n 'eip155:42161': 'https://arb1.arbitrum.io/rpc',\n 'eip155:43114': 'https://api.avax.network/ext/bc/C/rpc',\n 'eip155:1187947933': 'https://skale-base.skalenodes.com/v1/base',\n}\n"],"mappings":";AAkBA,IAAM,iBAAiB;AA8CvB,IAAM,YAAY,oBAAI,IAAgD;AACtE,IAAM,YAAY,IAAI,KAAK;AAK3B,eAAsB,cAAc,UAAU,gBAAqC;AACjF,QAAM,MAAM,QAAQ,QAAQ,OAAO,EAAE;AACrC,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,SAAS,UAAU,IAAI,GAAG;AAChC,MAAI,UAAU,MAAM,OAAO,OAAO,UAAW,QAAO,OAAO;AAE3D,QAAM,MAAM,GAAG,GAAG;AAClB,QAAM,MAAM,MAAM,MAAM,GAAG;AAC3B,MAAI,CAAC,IAAI,IAAI;AACX,QAAI,OAAQ,QAAO,OAAO;AAC1B,UAAM,IAAI,MAAM,kCAAkC,GAAG,KAAK,IAAI,MAAM,EAAE;AAAA,EACxE;AAEA,QAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,QAAM,OAAmB;AAAA,IACvB,gBAAgB,KAAK;AAAA,IACrB,uBAAuB,KAAK,yBAAyB,CAAC;AAAA,IACtD,uBAAuB,KAAK,yBAAyB,CAAC;AAAA,IACtD,OAAO,KAAK,SAAS,CAAC;AAAA,IACtB,aAAa,KAAK,eAAe;AAAA,IACjC,QAAQ,KAAK,UAAU;AAAA,IACvB,oBAAoB,KAAK,sBAAsB;AAAA,EACjD;AACA,YAAU,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI,CAAC;AACtC,SAAO;AACT;AAKA,eAAsB,aACpB,gBACA,MAC+B;AAC/B,QAAM,MAAM,MAAM,MAAM,gBAAgB;AAAA,IACtC,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU,IAAI;AAAA,EAC3B,CAAC;AAED,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,MAAO,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC9C,UAAM,IAAI,MAAM,iCAAiC,IAAI,SAAS,IAAI,MAAM,GAAG,IAAI,UAAU,aAAQ,IAAI,UAAU,EAAE,EAAE;AAAA,EACrH;AAEA,SAAO,IAAI,KAAK;AAClB;AAKO,SAAS,kBACd,iBACA,cACA,iBACA,wBACkD;AAElD,MAAI,0BAA0B,cAAc;AAC1C,UAAM,YAAY,UAAU,sBAAsB;AAClD,QAAI,gBAAgB,SAAS,SAAS,GAAG;AACvC,aAAO,EAAE,MAAM,OAAO,OAAO,UAAU;AAAA,IACzC;AAAA,EACF;AAGA,MAAI,iBAAiB;AACnB,UAAM,MAAM,gBAAgB,KAAK,CAAC,MAAM,EAAE,WAAW,SAAS,CAAC;AAC/D,QAAI,IAAK,QAAO,EAAE,MAAM,UAAU,OAAO,IAAI;AAAA,EAC/C;AAGA,MAAI,cAAc;AAChB,eAAW,SAAS,iBAAiB;AACnC,UAAI,MAAM,WAAW,SAAS,GAAG;AAC/B,eAAO,EAAE,MAAM,OAAO,MAAM;AAAA,MAC9B;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,oBAAoB,cAAsB,QAAwB;AAChF,QAAM,MAAO,eAAe,OAAO,MAAM,IAAK;AAC9C,SAAO,eAAe;AACxB;AAGO,IAAM,kBAA0C;AAAA,EACrD,eAAe;AAAA,EACf,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,qBAAqB;AACvB;","names":[]}
|