@relai-fi/x402 0.6.3 → 0.6.4

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 CHANGED
@@ -1207,6 +1207,282 @@ Client Server Facilitator
1207
1207
 
1208
1208
  ---
1209
1209
 
1210
+ ## MPP (Machine Payment Protocol)
1211
+
1212
+ MPP is an alternative payment channel that works alongside x402. Instead of the client building and signing blockchain transactions, MPP delegates signing to specialized payment providers — simpler client code, same `protect()` middleware on the server.
1213
+
1214
+ | | x402 | MPP |
1215
+ |---|------|-----|
1216
+ | **Payment flow** | Client builds tx (SPL / EIP-3009), signs, server settles via facilitator | Provider handles signing & broadcasting via challenge-response |
1217
+ | **Header protocol** | `X-PAYMENT` (base64 JSON) | `WWW-Authenticate: Payment` / `Authorization: Payment` |
1218
+ | **Supported providers** | — | Tempo (EVM), Solana (`@solana/mpp`), EVM/SKALE (built-in), Stripe |
1219
+
1220
+ When both are enabled the server returns a 402 with **both** an MPP challenge (`WWW-Authenticate`) and the standard x402 `accepts` body. The client tries MPP first, then falls back to x402.
1221
+
1222
+ ---
1223
+
1224
+ ### MPP Server Setup
1225
+
1226
+ #### Tempo (EVM)
1227
+
1228
+ ```typescript
1229
+ import express from 'express';
1230
+ import Relai from '@relai-fi/x402/server';
1231
+ import { Mppx, tempo } from 'mppx/server';
1232
+
1233
+ const TEMPO_USDC = '0x20C000000000000000000000b9537d11c60E8b50';
1234
+
1235
+ const mppx = Mppx.create({
1236
+ secretKey: process.env.MPP_SECRET_KEY!,
1237
+ methods: [
1238
+ tempo.charge({
1239
+ recipient: '0xYourWallet',
1240
+ currency: TEMPO_USDC,
1241
+ decimals: 6,
1242
+ }),
1243
+ ],
1244
+ });
1245
+
1246
+ const relai = new Relai({
1247
+ network: 'base',
1248
+ mpp: mppx,
1249
+ });
1250
+
1251
+ const app = express();
1252
+
1253
+ app.get('/api/data', relai.protect({
1254
+ payTo: '0xYourWallet',
1255
+ price: 0.01,
1256
+ }), (req, res) => {
1257
+ res.json({ data: 'Paid via MPP Tempo or x402' });
1258
+ });
1259
+ ```
1260
+
1261
+ #### Solana (`@solana/mpp`)
1262
+
1263
+ `@solana/mpp` expects amounts in **base units** (1 000 000 = 1 USDC), while the SDK passes USD. A thin wrapper with a handler cache is needed:
1264
+
1265
+ ```typescript
1266
+ import Relai from '@relai-fi/x402/server';
1267
+ import { Mppx, solana } from '@solana/mpp/server';
1268
+
1269
+ const SOLANA_USDC = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
1270
+ const DECIMALS = 6;
1271
+
1272
+ const mppx = Mppx.create({
1273
+ secretKey: process.env.MPP_SECRET_KEY!,
1274
+ methods: [
1275
+ solana.charge({
1276
+ recipient: 'YourSolanaWallet',
1277
+ splToken: SOLANA_USDC,
1278
+ decimals: DECIMALS,
1279
+ network: 'mainnet-beta',
1280
+ rpcUrl: process.env.SOLANA_RPC_URL,
1281
+ }),
1282
+ ],
1283
+ });
1284
+
1285
+ // Wrap to convert USD → base units and cache handlers (mppx.charge is stateful)
1286
+ const handlerCache = new Map<string, ReturnType<typeof mppx.charge>>();
1287
+ const mppSolanaWrapper = {
1288
+ charge(params: Record<string, unknown>) {
1289
+ const usdAmount = parseFloat(params.amount as string);
1290
+ const baseUnits = Math.round(usdAmount * 10 ** DECIMALS).toString();
1291
+ if (!handlerCache.has(baseUnits)) {
1292
+ handlerCache.set(baseUnits, mppx.charge({ ...params, amount: baseUnits }));
1293
+ }
1294
+ return handlerCache.get(baseUnits)!;
1295
+ },
1296
+ };
1297
+
1298
+ const relai = new Relai({
1299
+ network: 'solana',
1300
+ mpp: mppSolanaWrapper as any,
1301
+ });
1302
+ ```
1303
+
1304
+ #### EVM — SKALE, Base, Polygon, Avalanche, Ethereum, Telos (built-in)
1305
+
1306
+ The SDK ships a built-in EVM MPP method that works with any EVM chain. Supported chains:
1307
+
1308
+ | Chain | Chain ID | Gas | Notes |
1309
+ |-------|----------|-----|-------|
1310
+ | SKALE Base | 1187947933 | Free | Gas-free USDC micropayments |
1311
+ | SKALE Base Sepolia | 324705682 | Free | Testnet |
1312
+ | SKALE BITE | 103698795 | Free | Gas-free, encrypted mempool |
1313
+ | Base | 8453 | ETH | Low fees (~$0.001/tx) |
1314
+ | Polygon | 137 | POL | Low fees |
1315
+ | Avalanche | 43114 | AVAX | Low fees |
1316
+ | Ethereum | 1 | ETH | Higher fees |
1317
+ | Telos | 40 | TLOS | Low fees |
1318
+
1319
+ SKALE chains are **gas-free**, making them ideal for zero-cost micropayments. The same USD→base-units wrapper pattern applies:
1320
+
1321
+ ```typescript
1322
+ import Relai from '@relai-fi/x402/server';
1323
+ import { Mppx } from 'mppx/server';
1324
+ import { evmCharge } from '@relai-fi/x402/mpp/evm-server';
1325
+
1326
+ const DECIMALS = 6;
1327
+
1328
+ const mppx = Mppx.create({
1329
+ secretKey: process.env.MPP_SECRET_KEY!,
1330
+ methods: [
1331
+ evmCharge({
1332
+ recipient: '0xYourWallet',
1333
+ tokenAddress: '0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20', // USDC on SKALE Base
1334
+ chainId: 1187947933, // SKALE Base
1335
+ rpcUrl: 'https://skale-base.skalenodes.com/v1/base',
1336
+ decimals: DECIMALS,
1337
+ network: 'skale-base',
1338
+ }),
1339
+ ],
1340
+ });
1341
+
1342
+ // Same wrapper pattern as Solana
1343
+ const handlerCache = new Map<string, ReturnType<typeof mppx.charge>>();
1344
+ const mppEvmWrapper = {
1345
+ charge(params: Record<string, unknown>) {
1346
+ const usdAmount = parseFloat(params.amount as string);
1347
+ const baseUnits = Math.round(usdAmount * 10 ** DECIMALS).toString();
1348
+ if (!handlerCache.has(baseUnits)) {
1349
+ handlerCache.set(baseUnits, mppx.charge({ ...params, amount: baseUnits }));
1350
+ }
1351
+ return handlerCache.get(baseUnits)!;
1352
+ },
1353
+ };
1354
+
1355
+ const relai = new Relai({
1356
+ network: 'skale-base',
1357
+ mpp: mppEvmWrapper as any,
1358
+ });
1359
+ ```
1360
+
1361
+ ---
1362
+
1363
+ ### MPP Client Setup
1364
+
1365
+ #### Tempo (EVM)
1366
+
1367
+ ```typescript
1368
+ import { createX402Client } from '@relai-fi/x402/client';
1369
+ import { Mppx, tempo } from 'mppx/client';
1370
+ import { privateKeyToAccount } from 'viem/accounts';
1371
+
1372
+ const account = privateKeyToAccount(process.env.TEMPO_PRIVATE_KEY as `0x${string}`);
1373
+
1374
+ const mppx = Mppx.create({
1375
+ methods: [tempo.charge({ account })],
1376
+ polyfill: false,
1377
+ onChallenge: async (challenge, { createCredential }) => {
1378
+ console.log(`Amount: ${challenge.request?.amount}`);
1379
+ return await createCredential();
1380
+ },
1381
+ });
1382
+
1383
+ const client = createX402Client({ mpp: mppx });
1384
+ const response = await client.fetch('https://api.example.com/protected');
1385
+ ```
1386
+
1387
+ #### Solana
1388
+
1389
+ ```typescript
1390
+ import { createX402Client } from '@relai-fi/x402/client';
1391
+ import { Mppx, solana } from '@solana/mpp/client';
1392
+ import { createKeyPairSignerFromBytes } from '@solana/kit';
1393
+ import bs58 from 'bs58';
1394
+
1395
+ const signer = await createKeyPairSignerFromBytes(
1396
+ new Uint8Array(bs58.decode(process.env.SOLANA_PRIVATE_KEY!))
1397
+ );
1398
+
1399
+ const mppx = Mppx.create({
1400
+ methods: [solana.charge({ signer })],
1401
+ polyfill: false,
1402
+ onChallenge: async (challenge, { createCredential }) => {
1403
+ console.log(`Amount: ${challenge.request?.amount}`);
1404
+ return await createCredential();
1405
+ },
1406
+ });
1407
+
1408
+ const client = createX402Client({ mpp: mppx });
1409
+ const response = await client.fetch('https://api.example.com/protected');
1410
+ ```
1411
+
1412
+ #### EVM — SKALE, Base, Polygon, Avalanche, Ethereum, Telos (built-in)
1413
+
1414
+ ```typescript
1415
+ import { createX402Client } from '@relai-fi/x402/client';
1416
+ import { Mppx } from 'mppx/client';
1417
+ import { evmCharge } from '@relai-fi/x402/mpp/evm-client';
1418
+ import { privateKeyToAccount } from 'viem/accounts';
1419
+
1420
+ const account = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`);
1421
+
1422
+ const mppx = Mppx.create({
1423
+ methods: [evmCharge({ account })],
1424
+ polyfill: false,
1425
+ onChallenge: async (challenge, { createCredential }) => {
1426
+ const req = challenge.request as any;
1427
+ console.log(`Chain: ${req?.methodDetails?.network}, Amount: ${req?.amount}`);
1428
+ return await createCredential();
1429
+ },
1430
+ });
1431
+
1432
+ const client = createX402Client({ mpp: mppx });
1433
+ const response = await client.fetch('https://api.example.com/protected');
1434
+ ```
1435
+
1436
+ ---
1437
+
1438
+ ### MPP + Plugins
1439
+
1440
+ All existing plugins work with MPP payments. `afterSettled` fires on both success and failure for both x402 and MPP:
1441
+
1442
+ ```typescript
1443
+ import { freeTier, shield, circuitBreaker, refund } from '@relai-fi/x402/plugins';
1444
+
1445
+ const relai = new Relai({
1446
+ network: 'base',
1447
+ mpp: mppx,
1448
+ plugins: [
1449
+ shield({ healthUrl: 'https://my-api.com/health' }),
1450
+ circuitBreaker({ failureThreshold: 5 }),
1451
+ freeTier({ perBuyerLimit: 5, resetPeriod: 'daily' }),
1452
+ refund({ triggerCodes: [500, 502, 503] }),
1453
+ ],
1454
+ });
1455
+ ```
1456
+
1457
+ ### MPP API Reference
1458
+
1459
+ **Server — `MppServerHandler`**
1460
+
1461
+ ```typescript
1462
+ interface MppServerHandler {
1463
+ charge(params: Record<string, unknown>): (request: Request) => Promise<MppChargeResult>;
1464
+ }
1465
+
1466
+ type MppChargeResult = {
1467
+ status: number;
1468
+ challenge?: Response; // 402 with WWW-Authenticate header
1469
+ withReceipt?: (response: Response) => Response; // Attaches Payment-Receipt header
1470
+ receipt?: { method?: string; reference?: string; status?: string };
1471
+ };
1472
+ ```
1473
+
1474
+ **Client — `MppHandler`**
1475
+
1476
+ ```typescript
1477
+ interface MppHandler {
1478
+ createCredential(response: Response): Promise<string>;
1479
+ }
1480
+ ```
1481
+
1482
+ Both interfaces are exported from `@relai-fi/x402`.
1483
+
1484
+ ---
1485
+
1210
1486
  ## Development
1211
1487
 
1212
1488
  ```bash