x402-proxy 0.9.4 → 0.10.1
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/CHANGELOG.md +38 -1
- package/README.md +7 -3
- package/dist/bin/cli.js +1067 -156
- package/dist/openclaw/plugin.d.ts +12 -56
- package/dist/openclaw/plugin.js +958 -221
- package/dist/openclaw.plugin.json +69 -4
- package/dist/{setup-Dp5fS7ob.js → setup-DtKrojW1.js} +1 -1
- package/dist/setup-EX1_teNg.js +3 -0
- package/dist/{status-BZTToWE_.js → status-B5oH1nHv.js} +2 -2
- package/dist/status-DlR8yBrK.js +3 -0
- package/dist/wallet-7XKcknNZ.js +3 -0
- package/dist/{wallet-CqUc-ZFn.js → wallet-DqGROXlC.js} +18 -28
- package/openclaw.plugin.json +69 -4
- package/package.json +13 -4
- package/skills/SKILL.md +3 -1
- package/skills/references/openclaw-plugin.md +21 -11
- package/dist/setup-B6xRV8Ue.js +0 -3
- package/dist/status-Bj3U-W2M.js +0 -3
- package/dist/wallet-4C9EL4Iv.js +0 -3
- /package/dist/{derive-EDXzwKW2.js → derive-CY0En_Y3.js} +0 -0
package/dist/openclaw/plugin.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import os, { homedir } from "node:os";
|
|
2
2
|
import path, { dirname, join } from "node:path";
|
|
3
|
-
import { SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR, address, appendTransactionMessageInstructions, createDefaultRpcTransport, createKeyPairSignerFromBytes, createSolanaRpc, createSolanaRpcFromTransport, createTransactionMessage, getAddressEncoder, getBase64EncodedWireTransaction, getProgramDerivedAddress, isSolanaError, mainnet, partiallySignTransactionMessageWithSigners, pipe,
|
|
3
|
+
import { SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR, address, appendTransactionMessageInstructions, createDefaultRpcTransport, createKeyPairSignerFromBytes, createSolanaRpc, createSolanaRpcFromTransport, createTransactionMessage, getAddressEncoder, getBase64EncodedWireTransaction, getProgramDerivedAddress, isSolanaError, mainnet, partiallySignTransactionMessageWithSigners, pipe, setTransactionMessageComputeUnitLimit, setTransactionMessageComputeUnitPrice, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash } from "@solana/kit";
|
|
4
4
|
import { decodePaymentResponseHeader, wrapFetchWithPayment, x402Client } from "@x402/fetch";
|
|
5
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
5
6
|
import fs, { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
6
7
|
import "yaml";
|
|
7
|
-
import {
|
|
8
|
-
import { TOKEN_PROGRAM_ADDRESS } from "@solana-program/token";
|
|
9
|
-
import { TOKEN_2022_PROGRAM_ADDRESS, fetchMint, findAssociatedTokenPda, getTransferCheckedInstruction } from "@solana-program/token-2022";
|
|
8
|
+
import { TOKEN_PROGRAM_ADDRESS, findAssociatedTokenPda, getTransferCheckedInstruction } from "@solana-program/token";
|
|
10
9
|
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
11
10
|
import { base58 } from "@scure/base";
|
|
12
11
|
import "@x402/evm";
|
|
@@ -22,6 +21,8 @@ import { HDKey } from "@scure/bip32";
|
|
|
22
21
|
import { mnemonicToSeedSync } from "@scure/bip39";
|
|
23
22
|
import "@scure/bip39/wordlists/english.js";
|
|
24
23
|
import { Type } from "@sinclair/typebox";
|
|
24
|
+
import { buildCommand } from "@stricli/core";
|
|
25
|
+
import pc from "picocolors";
|
|
25
26
|
//#region src/handler.ts
|
|
26
27
|
/**
|
|
27
28
|
* Extract the on-chain transaction signature from an x402 payment response header.
|
|
@@ -59,6 +60,94 @@ function createX402ProxyHandler(opts) {
|
|
|
59
60
|
shiftPayment: () => paymentQueue.shift()
|
|
60
61
|
};
|
|
61
62
|
}
|
|
63
|
+
const TEMPO_NETWORK = "eip155:4217";
|
|
64
|
+
/**
|
|
65
|
+
* Create an MPP proxy handler using mppx client.
|
|
66
|
+
* Dynamically imports mppx/client to keep startup fast.
|
|
67
|
+
*/
|
|
68
|
+
async function createMppProxyHandler(opts) {
|
|
69
|
+
const { Mppx, tempo } = await import("mppx/client");
|
|
70
|
+
const { privateKeyToAccount } = await import("viem/accounts");
|
|
71
|
+
const account = privateKeyToAccount(opts.evmKey);
|
|
72
|
+
const maxDeposit = opts.maxDeposit ?? "1";
|
|
73
|
+
const paymentQueue = [];
|
|
74
|
+
let lastChallengeAmount;
|
|
75
|
+
const mppx = Mppx.create({
|
|
76
|
+
methods: [tempo({
|
|
77
|
+
account,
|
|
78
|
+
maxDeposit
|
|
79
|
+
})],
|
|
80
|
+
polyfill: false,
|
|
81
|
+
onChallenge: async (challenge) => {
|
|
82
|
+
const req = challenge.request;
|
|
83
|
+
if (req.amount) lastChallengeAmount = (Number(req.amount) / 10 ** (req.decimals ?? 6)).toString();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
let session;
|
|
87
|
+
return {
|
|
88
|
+
async fetch(input, init) {
|
|
89
|
+
const response = await mppx.fetch(typeof input === "string" ? input : input.toString(), init);
|
|
90
|
+
const receiptHeader = response.headers.get("Payment-Receipt");
|
|
91
|
+
if (receiptHeader) {
|
|
92
|
+
try {
|
|
93
|
+
const receipt = JSON.parse(Buffer.from(receiptHeader, "base64url").toString());
|
|
94
|
+
paymentQueue.push({
|
|
95
|
+
protocol: "mpp",
|
|
96
|
+
network: TEMPO_NETWORK,
|
|
97
|
+
amount: lastChallengeAmount,
|
|
98
|
+
receipt
|
|
99
|
+
});
|
|
100
|
+
} catch {
|
|
101
|
+
paymentQueue.push({
|
|
102
|
+
protocol: "mpp",
|
|
103
|
+
network: TEMPO_NETWORK,
|
|
104
|
+
amount: lastChallengeAmount
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
lastChallengeAmount = void 0;
|
|
108
|
+
}
|
|
109
|
+
return response;
|
|
110
|
+
},
|
|
111
|
+
async sse(input, init) {
|
|
112
|
+
session ??= tempo.session({
|
|
113
|
+
account,
|
|
114
|
+
maxDeposit
|
|
115
|
+
});
|
|
116
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
117
|
+
const iterable = await session.sse(url, init);
|
|
118
|
+
paymentQueue.push({
|
|
119
|
+
protocol: "mpp",
|
|
120
|
+
network: TEMPO_NETWORK,
|
|
121
|
+
intent: "session"
|
|
122
|
+
});
|
|
123
|
+
return iterable;
|
|
124
|
+
},
|
|
125
|
+
shiftPayment: () => paymentQueue.shift(),
|
|
126
|
+
async close() {
|
|
127
|
+
if (session?.opened) {
|
|
128
|
+
const receipt = await session.close();
|
|
129
|
+
if (receipt) {
|
|
130
|
+
const spentUsdc = receipt.spent ? (Number(receipt.spent) / 1e6).toString() : void 0;
|
|
131
|
+
paymentQueue.push({
|
|
132
|
+
protocol: "mpp",
|
|
133
|
+
network: TEMPO_NETWORK,
|
|
134
|
+
intent: "session",
|
|
135
|
+
amount: spentUsdc,
|
|
136
|
+
channelId: session.channelId ?? void 0,
|
|
137
|
+
receipt: {
|
|
138
|
+
method: receipt.method,
|
|
139
|
+
reference: receipt.reference,
|
|
140
|
+
status: receipt.status,
|
|
141
|
+
timestamp: receipt.timestamp,
|
|
142
|
+
acceptedCumulative: receipt.acceptedCumulative,
|
|
143
|
+
txHash: receipt.txHash
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
62
151
|
//#endregion
|
|
63
152
|
//#region src/lib/config.ts
|
|
64
153
|
const APP_NAME = "x402-proxy";
|
|
@@ -87,7 +176,7 @@ function loadWalletFile() {
|
|
|
87
176
|
//#region src/lib/optimized-svm-scheme.ts
|
|
88
177
|
const MEMO_PROGRAM_ADDRESS = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr";
|
|
89
178
|
const COMPUTE_UNIT_LIMIT = 2e4;
|
|
90
|
-
const COMPUTE_UNIT_PRICE_MICROLAMPORTS =
|
|
179
|
+
const COMPUTE_UNIT_PRICE_MICROLAMPORTS = 1n;
|
|
91
180
|
const USDC_MINT$1 = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
92
181
|
const USDC_DECIMALS$1 = 6;
|
|
93
182
|
const MAINNET_RPC_URLS = ["https://api.mainnet.solana.com", "https://public.rpc.solanavibestation.com"];
|
|
@@ -133,35 +222,24 @@ var OptimizedSvmScheme = class {
|
|
|
133
222
|
async createPaymentPayload(x402Version, paymentRequirements) {
|
|
134
223
|
const rpc = this.rpc;
|
|
135
224
|
const asset = paymentRequirements.asset;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (asset === USDC_MINT$1) {
|
|
139
|
-
tokenProgramAddress = TOKEN_PROGRAM_ADDRESS;
|
|
140
|
-
decimals = USDC_DECIMALS$1;
|
|
141
|
-
} else {
|
|
142
|
-
const tokenMint = await fetchMint(rpc, asset);
|
|
143
|
-
tokenProgramAddress = tokenMint.programAddress;
|
|
144
|
-
if (tokenProgramAddress !== TOKEN_PROGRAM_ADDRESS && tokenProgramAddress !== TOKEN_2022_PROGRAM_ADDRESS) throw new Error("Asset was not created by a known token program");
|
|
145
|
-
decimals = tokenMint.data.decimals;
|
|
146
|
-
}
|
|
147
|
-
const [sourceATA] = await findAssociatedTokenPda({
|
|
225
|
+
if (asset !== USDC_MINT$1) throw new Error(`Unsupported asset: ${asset}. Only USDC is supported.`);
|
|
226
|
+
const [[sourceATA], [destinationATA]] = await Promise.all([findAssociatedTokenPda({
|
|
148
227
|
mint: asset,
|
|
149
228
|
owner: this.signer.address,
|
|
150
|
-
tokenProgram:
|
|
151
|
-
})
|
|
152
|
-
const [destinationATA] = await findAssociatedTokenPda({
|
|
229
|
+
tokenProgram: TOKEN_PROGRAM_ADDRESS
|
|
230
|
+
}), findAssociatedTokenPda({
|
|
153
231
|
mint: asset,
|
|
154
232
|
owner: paymentRequirements.payTo,
|
|
155
|
-
tokenProgram:
|
|
156
|
-
});
|
|
233
|
+
tokenProgram: TOKEN_PROGRAM_ADDRESS
|
|
234
|
+
})]);
|
|
157
235
|
const transferIx = getTransferCheckedInstruction({
|
|
158
236
|
source: sourceATA,
|
|
159
237
|
mint: asset,
|
|
160
238
|
destination: destinationATA,
|
|
161
239
|
authority: this.signer,
|
|
162
240
|
amount: BigInt(paymentRequirements.amount),
|
|
163
|
-
decimals
|
|
164
|
-
}
|
|
241
|
+
decimals: USDC_DECIMALS$1
|
|
242
|
+
});
|
|
165
243
|
const feePayer = paymentRequirements.extra?.feePayer;
|
|
166
244
|
if (!feePayer) throw new Error("feePayer is required in paymentRequirements.extra for SVM transactions");
|
|
167
245
|
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
|
|
@@ -173,7 +251,7 @@ var OptimizedSvmScheme = class {
|
|
|
173
251
|
};
|
|
174
252
|
return {
|
|
175
253
|
x402Version,
|
|
176
|
-
payload: { transaction: getBase64EncodedWireTransaction(await partiallySignTransactionMessageWithSigners(pipe(createTransactionMessage({ version: 0 }), (tx) => setTransactionMessageComputeUnitPrice(COMPUTE_UNIT_PRICE_MICROLAMPORTS, tx), (tx) =>
|
|
254
|
+
payload: { transaction: getBase64EncodedWireTransaction(await partiallySignTransactionMessageWithSigners(pipe(createTransactionMessage({ version: 0 }), (tx) => setTransactionMessageComputeUnitPrice(COMPUTE_UNIT_PRICE_MICROLAMPORTS, tx), (tx) => setTransactionMessageComputeUnitLimit(COMPUTE_UNIT_LIMIT, tx), (tx) => setTransactionMessageFeePayer(feePayer, tx), (tx) => appendTransactionMessageInstructions([transferIx, memoIx], tx), (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx)))) }
|
|
177
255
|
};
|
|
178
256
|
}
|
|
179
257
|
};
|
|
@@ -441,7 +519,7 @@ async function loadSvmWallet(keypairPath) {
|
|
|
441
519
|
//#endregion
|
|
442
520
|
//#region src/openclaw/solana.ts
|
|
443
521
|
const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
444
|
-
const TOKEN_PROGRAM = address("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
|
|
522
|
+
const TOKEN_PROGRAM$1 = address("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
|
|
445
523
|
const ASSOCIATED_TOKEN_PROGRAM = address("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
|
|
446
524
|
async function findAta(mint, owner) {
|
|
447
525
|
const encoder = getAddressEncoder();
|
|
@@ -449,7 +527,7 @@ async function findAta(mint, owner) {
|
|
|
449
527
|
programAddress: ASSOCIATED_TOKEN_PROGRAM,
|
|
450
528
|
seeds: [
|
|
451
529
|
encoder.encode(owner),
|
|
452
|
-
encoder.encode(TOKEN_PROGRAM),
|
|
530
|
+
encoder.encode(TOKEN_PROGRAM$1),
|
|
453
531
|
encoder.encode(mint)
|
|
454
532
|
]
|
|
455
533
|
});
|
|
@@ -461,7 +539,7 @@ function transferCheckedIx(source, mint, destination, authority, amount, decimal
|
|
|
461
539
|
new DataView(data.buffer).setBigUint64(1, amount, true);
|
|
462
540
|
data[9] = decimals;
|
|
463
541
|
return {
|
|
464
|
-
programAddress: TOKEN_PROGRAM,
|
|
542
|
+
programAddress: TOKEN_PROGRAM$1,
|
|
465
543
|
accounts: [
|
|
466
544
|
{
|
|
467
545
|
address: source,
|
|
@@ -484,7 +562,7 @@ function transferCheckedIx(source, mint, destination, authority, amount, decimal
|
|
|
484
562
|
};
|
|
485
563
|
}
|
|
486
564
|
async function getTokenAccounts(rpcUrl, owner) {
|
|
487
|
-
const { value } = await createSolanaRpc(rpcUrl).getTokenAccountsByOwner(address(owner), { programId: TOKEN_PROGRAM }, { encoding: "jsonParsed" }).send();
|
|
565
|
+
const { value } = await createSolanaRpc(rpcUrl).getTokenAccountsByOwner(address(owner), { programId: TOKEN_PROGRAM$1 }, { encoding: "jsonParsed" }).send();
|
|
488
566
|
return value.map((v) => {
|
|
489
567
|
const info = v.account.data.parsed.info;
|
|
490
568
|
return {
|
|
@@ -530,6 +608,158 @@ async function transferUsdc(signer, rpcUrl, dest, amountRaw) {
|
|
|
530
608
|
return await rpc.sendTransaction(encoded, { encoding: "base64" }).send();
|
|
531
609
|
}
|
|
532
610
|
//#endregion
|
|
611
|
+
//#region src/lib/output.ts
|
|
612
|
+
function isTTY() {
|
|
613
|
+
return !!process.stderr.isTTY;
|
|
614
|
+
}
|
|
615
|
+
function info(msg) {
|
|
616
|
+
process.stderr.write(`${isTTY() ? pc.cyan(msg) : msg}\n`);
|
|
617
|
+
}
|
|
618
|
+
function dim(msg) {
|
|
619
|
+
process.stderr.write(`${isTTY() ? pc.dim(msg) : msg}\n`);
|
|
620
|
+
}
|
|
621
|
+
//#endregion
|
|
622
|
+
//#region src/commands/wallet.ts
|
|
623
|
+
const BASE_RPC = "https://mainnet.base.org";
|
|
624
|
+
const SOLANA_RPC = "https://api.mainnet-beta.solana.com";
|
|
625
|
+
const TEMPO_RPC = "https://rpc.presto.tempo.xyz";
|
|
626
|
+
const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
627
|
+
const USDC_TEMPO = "0x20C000000000000000000000b9537d11c60E8b50";
|
|
628
|
+
const USDC_SOLANA_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
629
|
+
const TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
630
|
+
const ATA_PROGRAM = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
|
|
631
|
+
async function rpcCall(url, method, params) {
|
|
632
|
+
const res = await fetch(url, {
|
|
633
|
+
method: "POST",
|
|
634
|
+
headers: { "Content-Type": "application/json" },
|
|
635
|
+
body: JSON.stringify({
|
|
636
|
+
jsonrpc: "2.0",
|
|
637
|
+
method,
|
|
638
|
+
params,
|
|
639
|
+
id: 1
|
|
640
|
+
})
|
|
641
|
+
});
|
|
642
|
+
if (!res.ok) throw new Error(`RPC ${method} failed: ${res.status} ${res.statusText}`);
|
|
643
|
+
return res.json();
|
|
644
|
+
}
|
|
645
|
+
async function fetchEvmBalances(address) {
|
|
646
|
+
const usdcData = `0x70a08231${address.slice(2).padStart(64, "0")}`;
|
|
647
|
+
const [ethRes, usdcRes] = await Promise.all([rpcCall(BASE_RPC, "eth_getBalance", [address, "latest"]), rpcCall(BASE_RPC, "eth_call", [{
|
|
648
|
+
to: USDC_BASE,
|
|
649
|
+
data: usdcData
|
|
650
|
+
}, "latest"])]);
|
|
651
|
+
return {
|
|
652
|
+
eth: ethRes.result ? (Number(BigInt(ethRes.result)) / 0xde0b6b3a7640000).toFixed(6) : "?",
|
|
653
|
+
usdc: usdcRes.result ? formatUsdcValue(Number(BigInt(usdcRes.result)) / 1e6) : "?"
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
async function fetchTempoBalances(address) {
|
|
657
|
+
const res = await rpcCall(TEMPO_RPC, "eth_call", [{
|
|
658
|
+
to: USDC_TEMPO,
|
|
659
|
+
data: `0x70a08231${address.slice(2).padStart(64, "0")}`
|
|
660
|
+
}, "latest"]);
|
|
661
|
+
return { usdc: res.result ? formatUsdcValue(Number(BigInt(res.result)) / 1e6) : "?" };
|
|
662
|
+
}
|
|
663
|
+
async function getUsdcAta(owner) {
|
|
664
|
+
const encoder = getAddressEncoder();
|
|
665
|
+
const [ata] = await getProgramDerivedAddress({
|
|
666
|
+
programAddress: address(ATA_PROGRAM),
|
|
667
|
+
seeds: [
|
|
668
|
+
encoder.encode(address(owner)),
|
|
669
|
+
encoder.encode(address(TOKEN_PROGRAM)),
|
|
670
|
+
encoder.encode(address(USDC_SOLANA_MINT))
|
|
671
|
+
]
|
|
672
|
+
});
|
|
673
|
+
return ata;
|
|
674
|
+
}
|
|
675
|
+
async function fetchSolanaBalances(ownerAddress) {
|
|
676
|
+
const ata = await getUsdcAta(ownerAddress);
|
|
677
|
+
const [solRes, usdcRes] = await Promise.all([rpcCall(SOLANA_RPC, "getBalance", [ownerAddress]), rpcCall(SOLANA_RPC, "getTokenAccountBalance", [ata])]);
|
|
678
|
+
const sol = solRes.result?.value != null ? (solRes.result.value / 1e9).toFixed(6) : "?";
|
|
679
|
+
const usdcVal = usdcRes.result?.value;
|
|
680
|
+
return {
|
|
681
|
+
sol,
|
|
682
|
+
usdc: usdcVal ? formatUsdcValue(Number(usdcVal.uiAmountString)) : "0"
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
function balanceLine(usdc, native, nativeSymbol) {
|
|
686
|
+
return pc.dim(` (${usdc} USDC, ${native} ${nativeSymbol})`);
|
|
687
|
+
}
|
|
688
|
+
async function fetchAllBalances(evmAddress, solanaAddress) {
|
|
689
|
+
const [evmResult, solResult, tempoResult] = await Promise.allSettled([
|
|
690
|
+
evmAddress ? fetchEvmBalances(evmAddress) : Promise.resolve(null),
|
|
691
|
+
solanaAddress ? fetchSolanaBalances(solanaAddress) : Promise.resolve(null),
|
|
692
|
+
evmAddress ? fetchTempoBalances(evmAddress) : Promise.resolve(null)
|
|
693
|
+
]);
|
|
694
|
+
return {
|
|
695
|
+
evm: evmResult.status === "fulfilled" ? evmResult.value : null,
|
|
696
|
+
sol: solResult.status === "fulfilled" ? solResult.value : null,
|
|
697
|
+
tempo: tempoResult.status === "fulfilled" ? tempoResult.value : null
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
buildCommand({
|
|
701
|
+
docs: { brief: "Show wallet addresses and balances" },
|
|
702
|
+
parameters: {
|
|
703
|
+
flags: { verbose: {
|
|
704
|
+
kind: "boolean",
|
|
705
|
+
brief: "Show transaction IDs",
|
|
706
|
+
default: false
|
|
707
|
+
} },
|
|
708
|
+
positional: {
|
|
709
|
+
kind: "tuple",
|
|
710
|
+
parameters: []
|
|
711
|
+
}
|
|
712
|
+
},
|
|
713
|
+
async func(flags) {
|
|
714
|
+
const wallet = resolveWallet();
|
|
715
|
+
if (wallet.source === "none") {
|
|
716
|
+
console.log(pc.yellow("No wallet configured."));
|
|
717
|
+
console.log(pc.dim(`\nRun:\n ${pc.cyan("$ npx x402-proxy setup")}\n\nOr set ${pc.cyan("X402_PROXY_WALLET_MNEMONIC")} environment variable.`));
|
|
718
|
+
process.exit(1);
|
|
719
|
+
}
|
|
720
|
+
console.log();
|
|
721
|
+
info("Wallet");
|
|
722
|
+
console.log();
|
|
723
|
+
console.log(pc.dim(` Source: ${wallet.source}`));
|
|
724
|
+
const { evm, sol, tempo } = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
|
|
725
|
+
if (wallet.evmAddress) {
|
|
726
|
+
const bal = evm ? balanceLine(evm.usdc, evm.eth, "ETH") : pc.dim(" (network error)");
|
|
727
|
+
console.log(` Base: ${pc.green(wallet.evmAddress)}${bal}`);
|
|
728
|
+
}
|
|
729
|
+
if (wallet.evmAddress) {
|
|
730
|
+
const bal = tempo ? pc.dim(` (${tempo.usdc} USDC)`) : pc.dim(" (network error)");
|
|
731
|
+
console.log(` Tempo: ${pc.green(wallet.evmAddress)}${bal}`);
|
|
732
|
+
}
|
|
733
|
+
if (wallet.solanaAddress) {
|
|
734
|
+
const bal = sol ? balanceLine(sol.usdc, sol.sol, "SOL") : pc.dim(" (network error)");
|
|
735
|
+
console.log(` Solana: ${pc.green(wallet.solanaAddress)}${bal}`);
|
|
736
|
+
}
|
|
737
|
+
const evmEmpty = !evm || Number(evm.usdc) === 0;
|
|
738
|
+
const solEmpty = !sol || Number(sol.usdc) === 0;
|
|
739
|
+
const tempoEmpty = !tempo || Number(tempo.usdc) === 0;
|
|
740
|
+
if (evmEmpty && solEmpty && tempoEmpty) {
|
|
741
|
+
console.log();
|
|
742
|
+
dim(" Send USDC to any address above to start using paid APIs.");
|
|
743
|
+
}
|
|
744
|
+
console.log();
|
|
745
|
+
const records = readHistory(getHistoryPath());
|
|
746
|
+
if (records.length > 0) {
|
|
747
|
+
const spend = calcSpend(records);
|
|
748
|
+
const recent = records.slice(-10);
|
|
749
|
+
dim(" Recent transactions:");
|
|
750
|
+
for (const r of recent) {
|
|
751
|
+
const line = formatTxLine(r, { verbose: flags.verbose }).replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
752
|
+
console.log(line);
|
|
753
|
+
}
|
|
754
|
+
console.log();
|
|
755
|
+
console.log(pc.dim(` Today: ${formatAmount(spend.today, "USDC")} | Total: ${formatAmount(spend.total, "USDC")} | ${spend.count} tx`));
|
|
756
|
+
} else dim(" No transactions yet.");
|
|
757
|
+
console.log();
|
|
758
|
+
console.log(pc.dim(" See also: wallet history, wallet export-key"));
|
|
759
|
+
console.log();
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
//#endregion
|
|
533
763
|
//#region src/openclaw/tools.ts
|
|
534
764
|
const SOL_MAINNET = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
535
765
|
const USDC_DECIMALS = 6;
|
|
@@ -540,6 +770,11 @@ function paymentAmount(payment) {
|
|
|
540
770
|
const parsed = Number.parseFloat(payment.amount);
|
|
541
771
|
return Number.isNaN(parsed) ? void 0 : parsed / 10 ** USDC_DECIMALS;
|
|
542
772
|
}
|
|
773
|
+
function parseMppAmount(value) {
|
|
774
|
+
if (!value) return void 0;
|
|
775
|
+
const parsed = Number(value);
|
|
776
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
777
|
+
}
|
|
543
778
|
function toolResult(text) {
|
|
544
779
|
return {
|
|
545
780
|
content: [{
|
|
@@ -549,11 +784,23 @@ function toolResult(text) {
|
|
|
549
784
|
details: {}
|
|
550
785
|
};
|
|
551
786
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
787
|
+
function addressForNetwork(evmAddress, solanaAddress, network) {
|
|
788
|
+
if (network?.startsWith("eip155:")) return evmAddress ?? solanaAddress;
|
|
789
|
+
if (network?.startsWith("solana:")) return solanaAddress ?? evmAddress;
|
|
790
|
+
return solanaAddress ?? evmAddress;
|
|
791
|
+
}
|
|
792
|
+
function walletAddressForNetwork(ctx, network) {
|
|
793
|
+
return addressForNetwork(ctx.getEvmWalletAddress(), ctx.getSolanaWalletAddress(), network) ?? "unknown";
|
|
794
|
+
}
|
|
795
|
+
async function getWalletSnapshot(rpcUrl, solanaWallet, evmWallet, historyPath) {
|
|
796
|
+
const [{ ui, raw }, sol, tokens, balances] = await Promise.all([
|
|
797
|
+
solanaWallet ? getUsdcBalance(rpcUrl, solanaWallet) : Promise.resolve({
|
|
798
|
+
ui: "0",
|
|
799
|
+
raw: 0n
|
|
800
|
+
}),
|
|
801
|
+
solanaWallet ? getSolBalance(rpcUrl, solanaWallet) : Promise.resolve("0"),
|
|
802
|
+
solanaWallet ? getTokenAccounts(rpcUrl, solanaWallet).catch(() => []) : Promise.resolve([]),
|
|
803
|
+
fetchAllBalances(evmWallet ?? void 0, solanaWallet ?? void 0)
|
|
557
804
|
]);
|
|
558
805
|
const records = readHistory(historyPath);
|
|
559
806
|
return {
|
|
@@ -561,61 +808,80 @@ async function getWalletSnapshot(rpcUrl, wallet, historyPath) {
|
|
|
561
808
|
raw,
|
|
562
809
|
sol,
|
|
563
810
|
tokens,
|
|
811
|
+
balances,
|
|
564
812
|
records,
|
|
565
813
|
spend: calcSpend(records)
|
|
566
814
|
};
|
|
567
815
|
}
|
|
568
|
-
function
|
|
816
|
+
function createWalletTool(ctx) {
|
|
569
817
|
return {
|
|
570
|
-
name: "
|
|
571
|
-
label: "Wallet
|
|
572
|
-
description: "Check wallet
|
|
818
|
+
name: "x_wallet",
|
|
819
|
+
label: "Wallet Status",
|
|
820
|
+
description: "Check wallet readiness, balances, and spend history for x402 and MPP payments before making paid requests.",
|
|
573
821
|
parameters: Type.Object({}),
|
|
574
822
|
async execute() {
|
|
575
|
-
|
|
576
|
-
|
|
823
|
+
await ctx.ensureReady();
|
|
824
|
+
const solanaWallet = ctx.getSolanaWalletAddress();
|
|
825
|
+
const evmWallet = ctx.getEvmWalletAddress();
|
|
826
|
+
if (!solanaWallet && !evmWallet) return toolResult("Wallet not configured yet. Run `x402-proxy setup` or set X402_PROXY_WALLET_MNEMONIC.");
|
|
577
827
|
try {
|
|
578
|
-
const snap = await getWalletSnapshot(ctx.rpcUrl,
|
|
828
|
+
const snap = await getWalletSnapshot(ctx.rpcUrl, solanaWallet, evmWallet, ctx.historyPath);
|
|
579
829
|
const total = Number.parseFloat(snap.ui);
|
|
580
830
|
const available = Math.max(0, total - INFERENCE_RESERVE);
|
|
581
831
|
const tokenLines = snap.tokens.slice(0, 5).map((t) => {
|
|
582
832
|
const short = `${t.mint.slice(0, 4)}...${t.mint.slice(-4)}`;
|
|
583
833
|
return `${Number.parseFloat(t.amount).toLocaleString("en-US", { maximumFractionDigits: 0 })} (${short})`;
|
|
584
834
|
});
|
|
585
|
-
|
|
586
|
-
`
|
|
587
|
-
`
|
|
588
|
-
`
|
|
589
|
-
`
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
`
|
|
593
|
-
|
|
594
|
-
|
|
835
|
+
const lines = [
|
|
836
|
+
`Default protocol: ${ctx.getDefaultRequestProtocol()}`,
|
|
837
|
+
`MPP budget: ${ctx.getDefaultMppSessionBudget()} USDC`,
|
|
838
|
+
`MPP ready: ${evmWallet ? "yes" : "no"}`,
|
|
839
|
+
`x402 ready: ${solanaWallet ? "yes" : "no"}`
|
|
840
|
+
];
|
|
841
|
+
if (evmWallet) {
|
|
842
|
+
lines.push(`Base wallet: ${evmWallet}`);
|
|
843
|
+
lines.push(`Base balance: ${snap.balances.evm?.usdc ?? "?"} USDC`);
|
|
844
|
+
lines.push(`Tempo balance: ${snap.balances.tempo?.usdc ?? "?"} USDC`);
|
|
845
|
+
}
|
|
846
|
+
if (solanaWallet) {
|
|
847
|
+
lines.push(`Solana wallet: ${solanaWallet}`);
|
|
848
|
+
lines.push(`Solana: ${snap.sol} SOL`);
|
|
849
|
+
lines.push(`Solana USDC: ${snap.ui} USDC`);
|
|
850
|
+
lines.push(`Available for x402 tools: ${available.toFixed(2)} USDC`);
|
|
851
|
+
lines.push(`Reserved for inference: ${INFERENCE_RESERVE.toFixed(2)} USDC`);
|
|
852
|
+
}
|
|
853
|
+
lines.push(`Spent today: ${formatAmount(snap.spend.today, "USDC")}`);
|
|
854
|
+
lines.push(`Total spent: ${formatAmount(snap.spend.total, "USDC")} (${snap.spend.count} txs)`);
|
|
855
|
+
if (tokenLines.length > 0) lines.push(`Tokens held: ${tokenLines.join(", ")}`);
|
|
856
|
+
if (!evmWallet) lines.push("MPP setup hint: set X402_PROXY_WALLET_MNEMONIC or X402_PROXY_WALLET_EVM_KEY, or run x402-proxy setup.");
|
|
857
|
+
if (!solanaWallet) lines.push("x402 setup hint: add a Solana wallet or mnemonic if you want to pay Solana x402 endpoints.");
|
|
858
|
+
return toolResult(lines.join("\n"));
|
|
595
859
|
} catch (err) {
|
|
596
|
-
return toolResult(`Failed to check
|
|
860
|
+
return toolResult(`Failed to check wallet status: ${String(err)}`);
|
|
597
861
|
}
|
|
598
862
|
}
|
|
599
863
|
};
|
|
600
864
|
}
|
|
601
|
-
function
|
|
865
|
+
function createRequestTool(ctx) {
|
|
602
866
|
return {
|
|
603
|
-
name: "
|
|
604
|
-
label: "
|
|
605
|
-
description:
|
|
867
|
+
name: "x_request",
|
|
868
|
+
label: "Paid Request",
|
|
869
|
+
description: "Call a paid HTTP endpoint with automatic x402 or MPP settlement. Defaults to the plugin protocol and supports explicit protocol override.",
|
|
606
870
|
parameters: Type.Object({
|
|
607
|
-
url: Type.String({ description: "The
|
|
871
|
+
url: Type.String({ description: "The paid endpoint URL" }),
|
|
608
872
|
method: Type.Optional(Type.String({ description: "HTTP method (default: GET)" })),
|
|
609
873
|
params: Type.Optional(Type.String({ description: "For GET: query params as JSON object. For POST/PUT/PATCH: JSON request body." })),
|
|
610
|
-
headers: Type.Optional(Type.String({ description: "Custom HTTP headers as JSON object" }))
|
|
874
|
+
headers: Type.Optional(Type.String({ description: "Custom HTTP headers as JSON object" })),
|
|
875
|
+
protocol: Type.Optional(Type.Union([
|
|
876
|
+
Type.Literal("x402"),
|
|
877
|
+
Type.Literal("mpp"),
|
|
878
|
+
Type.Literal("auto")
|
|
879
|
+
], { description: "Override the default payment protocol for this request." }))
|
|
611
880
|
}),
|
|
612
881
|
async execute(_id, params) {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
const { ui } = await getUsdcBalance(ctx.rpcUrl, walletAddress);
|
|
617
|
-
if (Number.parseFloat(ui) <= INFERENCE_RESERVE) return toolResult(`Insufficient funds. Balance: ${ui} USDC, reserved for inference: ${INFERENCE_RESERVE.toFixed(2)} USDC. Top up wallet: ${walletAddress}`);
|
|
618
|
-
} catch {}
|
|
882
|
+
await ctx.ensureReady();
|
|
883
|
+
const solanaWallet = ctx.getSolanaWalletAddress();
|
|
884
|
+
const evmWallet = ctx.getEvmWalletAddress();
|
|
619
885
|
const method = (params.method || "GET").toUpperCase();
|
|
620
886
|
let url = params.url;
|
|
621
887
|
const reqInit = { method };
|
|
@@ -638,35 +904,101 @@ function createPaymentTool(ctx) {
|
|
|
638
904
|
...reqInit.headers
|
|
639
905
|
};
|
|
640
906
|
}
|
|
907
|
+
const protocol = params.protocol && [
|
|
908
|
+
"x402",
|
|
909
|
+
"mpp",
|
|
910
|
+
"auto"
|
|
911
|
+
].includes(params.protocol) ? params.protocol : ctx.getDefaultRequestProtocol();
|
|
641
912
|
const toolStartMs = Date.now();
|
|
913
|
+
if ((protocol === "mpp" || protocol === "auto") && !ctx.getEvmKey()) return toolResult("MPP request failed: no EVM wallet configured. Run x402-proxy setup or set X402_PROXY_WALLET_EVM_KEY.");
|
|
914
|
+
if (protocol === "x402") {
|
|
915
|
+
if (!solanaWallet) return toolResult("x402 request failed: no Solana wallet configured. Add a mnemonic or Solana key first.");
|
|
916
|
+
try {
|
|
917
|
+
const { ui } = await getUsdcBalance(ctx.rpcUrl, solanaWallet);
|
|
918
|
+
if (Number.parseFloat(ui) <= INFERENCE_RESERVE) return toolResult(`Insufficient funds. Balance: ${ui} USDC, reserved for inference: ${INFERENCE_RESERVE.toFixed(2)} USDC. Top up wallet: ${solanaWallet}`);
|
|
919
|
+
} catch {}
|
|
920
|
+
const proxy = ctx.getX402Proxy();
|
|
921
|
+
if (!proxy) return toolResult("x402 wallet is not ready yet. Try again in a moment.");
|
|
922
|
+
try {
|
|
923
|
+
const response = await proxy.x402Fetch(url, reqInit);
|
|
924
|
+
const body = await response.text();
|
|
925
|
+
if (response.status === 402) {
|
|
926
|
+
const payment = proxy.shiftPayment();
|
|
927
|
+
appendHistory(ctx.historyPath, {
|
|
928
|
+
t: Date.now(),
|
|
929
|
+
ok: false,
|
|
930
|
+
kind: "x402_payment",
|
|
931
|
+
net: payment?.network ?? "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
932
|
+
from: walletAddressForNetwork(ctx, payment?.network),
|
|
933
|
+
label: url,
|
|
934
|
+
ms: Date.now() - toolStartMs,
|
|
935
|
+
error: "payment_required"
|
|
936
|
+
});
|
|
937
|
+
return toolResult(`Payment failed (402): ${body.substring(0, 500)}. Wallet: ${walletAddressForNetwork(ctx, payment?.network)}`);
|
|
938
|
+
}
|
|
939
|
+
const payment = proxy.shiftPayment();
|
|
940
|
+
const amount = paymentAmount(payment);
|
|
941
|
+
appendHistory(ctx.historyPath, {
|
|
942
|
+
t: Date.now(),
|
|
943
|
+
ok: true,
|
|
944
|
+
kind: "x402_payment",
|
|
945
|
+
net: payment?.network ?? "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
946
|
+
from: walletAddressForNetwork(ctx, payment?.network),
|
|
947
|
+
to: payment?.payTo,
|
|
948
|
+
tx: extractTxSignature(response),
|
|
949
|
+
amount,
|
|
950
|
+
token: "USDC",
|
|
951
|
+
label: url,
|
|
952
|
+
ms: Date.now() - toolStartMs
|
|
953
|
+
});
|
|
954
|
+
const truncated = body.length > MAX_RESPONSE_CHARS ? `${body.substring(0, MAX_RESPONSE_CHARS)}\n\n[Truncated - response was ${body.length} chars]` : body;
|
|
955
|
+
return toolResult(`HTTP ${response.status}\n\n${truncated}`);
|
|
956
|
+
} catch (err) {
|
|
957
|
+
ctx.getX402Proxy()?.shiftPayment();
|
|
958
|
+
appendHistory(ctx.historyPath, {
|
|
959
|
+
t: Date.now(),
|
|
960
|
+
ok: false,
|
|
961
|
+
kind: "x402_payment",
|
|
962
|
+
net: SOL_MAINNET,
|
|
963
|
+
from: solanaWallet,
|
|
964
|
+
label: params.url,
|
|
965
|
+
error: String(err).substring(0, 200)
|
|
966
|
+
});
|
|
967
|
+
const msg = String(err);
|
|
968
|
+
return toolResult(msg.includes("Simulation failed") || msg.includes("insufficient") ? `Payment failed - insufficient funds. Wallet: ${solanaWallet}. Error: ${msg}` : `Request failed: ${msg}`);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
const evmKey = ctx.getEvmKey();
|
|
972
|
+
if (!evmKey) return toolResult("MPP request failed: no EVM wallet configured. Run x402-proxy setup or set X402_PROXY_WALLET_EVM_KEY.");
|
|
973
|
+
const mpp = await createMppProxyHandler({
|
|
974
|
+
evmKey,
|
|
975
|
+
maxDeposit: ctx.getDefaultMppSessionBudget()
|
|
976
|
+
});
|
|
642
977
|
try {
|
|
643
|
-
const response = await
|
|
978
|
+
const response = await mpp.fetch(url, reqInit);
|
|
644
979
|
const body = await response.text();
|
|
980
|
+
const payment = mpp.shiftPayment();
|
|
645
981
|
if (response.status === 402) {
|
|
646
|
-
ctx.proxy.shiftPayment();
|
|
647
982
|
appendHistory(ctx.historyPath, {
|
|
648
983
|
t: Date.now(),
|
|
649
984
|
ok: false,
|
|
650
|
-
kind: "
|
|
651
|
-
net:
|
|
652
|
-
from:
|
|
985
|
+
kind: "mpp_payment",
|
|
986
|
+
net: payment?.network ?? "eip155:4217",
|
|
987
|
+
from: evmWallet ?? "unknown",
|
|
653
988
|
label: url,
|
|
654
989
|
ms: Date.now() - toolStartMs,
|
|
655
990
|
error: "payment_required"
|
|
656
991
|
});
|
|
657
|
-
return toolResult(`
|
|
992
|
+
return toolResult(`MPP payment failed (402): ${body.substring(0, 500)}`);
|
|
658
993
|
}
|
|
659
|
-
const payment = ctx.proxy.shiftPayment();
|
|
660
|
-
const amount = paymentAmount(payment);
|
|
661
994
|
appendHistory(ctx.historyPath, {
|
|
662
995
|
t: Date.now(),
|
|
663
996
|
ok: true,
|
|
664
|
-
kind: "
|
|
665
|
-
net:
|
|
666
|
-
from:
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
amount,
|
|
997
|
+
kind: "mpp_payment",
|
|
998
|
+
net: payment?.network ?? "eip155:4217",
|
|
999
|
+
from: evmWallet ?? "unknown",
|
|
1000
|
+
tx: extractTxSignature(response) ?? payment?.receipt?.reference,
|
|
1001
|
+
amount: parseMppAmount(payment?.amount),
|
|
670
1002
|
token: "USDC",
|
|
671
1003
|
label: url,
|
|
672
1004
|
ms: Date.now() - toolStartMs
|
|
@@ -674,18 +1006,18 @@ function createPaymentTool(ctx) {
|
|
|
674
1006
|
const truncated = body.length > MAX_RESPONSE_CHARS ? `${body.substring(0, MAX_RESPONSE_CHARS)}\n\n[Truncated - response was ${body.length} chars]` : body;
|
|
675
1007
|
return toolResult(`HTTP ${response.status}\n\n${truncated}`);
|
|
676
1008
|
} catch (err) {
|
|
677
|
-
ctx.proxy.shiftPayment();
|
|
678
1009
|
appendHistory(ctx.historyPath, {
|
|
679
1010
|
t: Date.now(),
|
|
680
1011
|
ok: false,
|
|
681
|
-
kind: "
|
|
682
|
-
net:
|
|
683
|
-
from:
|
|
1012
|
+
kind: "mpp_payment",
|
|
1013
|
+
net: TEMPO_NETWORK,
|
|
1014
|
+
from: evmWallet ?? "unknown",
|
|
684
1015
|
label: params.url,
|
|
685
1016
|
error: String(err).substring(0, 200)
|
|
686
1017
|
});
|
|
687
|
-
|
|
688
|
-
|
|
1018
|
+
return toolResult(`MPP request failed: ${String(err)}`);
|
|
1019
|
+
} finally {
|
|
1020
|
+
await mpp.close().catch(() => {});
|
|
689
1021
|
}
|
|
690
1022
|
}
|
|
691
1023
|
};
|
|
@@ -695,8 +1027,10 @@ function createPaymentTool(ctx) {
|
|
|
695
1027
|
const HISTORY_PAGE_SIZE = 5;
|
|
696
1028
|
const STATUS_HISTORY_COUNT = 3;
|
|
697
1029
|
const INLINE_HISTORY_TOKEN_THRESHOLD = 3;
|
|
1030
|
+
const SEND_CONFIRM_TTL_MS = 300 * 1e3;
|
|
698
1031
|
const TOKEN_SYMBOL_CACHE_MAX = 200;
|
|
699
1032
|
const tokenSymbolCache = /* @__PURE__ */ new Map();
|
|
1033
|
+
const pendingSends = /* @__PURE__ */ new Map();
|
|
700
1034
|
async function resolveTokenSymbols(mints) {
|
|
701
1035
|
const result = /* @__PURE__ */ new Map();
|
|
702
1036
|
const toResolve = [];
|
|
@@ -725,13 +1059,18 @@ async function resolveTokenSymbols(mints) {
|
|
|
725
1059
|
} catch {}
|
|
726
1060
|
return result;
|
|
727
1061
|
}
|
|
728
|
-
|
|
1062
|
+
function senderKey(cmdCtx) {
|
|
1063
|
+
return [
|
|
1064
|
+
cmdCtx.channel,
|
|
1065
|
+
cmdCtx.accountId ?? "",
|
|
1066
|
+
cmdCtx.senderId ?? cmdCtx.from ?? "",
|
|
1067
|
+
String(cmdCtx.messageThreadId ?? "")
|
|
1068
|
+
].join(":");
|
|
1069
|
+
}
|
|
1070
|
+
async function executeSend(amountStr, destination, wallet, signer, rpc, histPath) {
|
|
729
1071
|
if (!signer) return { text: "Wallet not loaded yet. Please wait for the gateway to finish starting." };
|
|
730
|
-
if (parts.length !== 2) return { text: "Usage: `/x_wallet send <amount|all> <address>`\n\n `/x_wallet send 0.5 7xKXtg...`\n `/x_wallet send all 7xKXtg...`" };
|
|
731
|
-
const [amountStr, destAddr] = parts;
|
|
732
|
-
if (destAddr.length < 32 || destAddr.length > 44) return { text: `Invalid Solana address: ${destAddr}` };
|
|
733
1072
|
try {
|
|
734
|
-
if (!await checkAtaExists(rpc,
|
|
1073
|
+
if (!await checkAtaExists(rpc, destination)) return { text: "Recipient does not have a USDC token account.\nThey need to receive USDC at least once to create one." };
|
|
735
1074
|
let amountRaw;
|
|
736
1075
|
let amountUi;
|
|
737
1076
|
if (amountStr.toLowerCase() === "all") {
|
|
@@ -745,20 +1084,20 @@ async function handleSend(parts, wallet, signer, rpc, histPath) {
|
|
|
745
1084
|
amountRaw = BigInt(Math.round(amount * 1e6));
|
|
746
1085
|
amountUi = amount.toString();
|
|
747
1086
|
}
|
|
748
|
-
const sig = await transferUsdc(signer, rpc,
|
|
1087
|
+
const sig = await transferUsdc(signer, rpc, destination, amountRaw);
|
|
749
1088
|
appendHistory(histPath, {
|
|
750
1089
|
t: Date.now(),
|
|
751
1090
|
ok: true,
|
|
752
1091
|
kind: "transfer",
|
|
753
1092
|
net: SOL_MAINNET,
|
|
754
1093
|
from: wallet,
|
|
755
|
-
to:
|
|
1094
|
+
to: destination,
|
|
756
1095
|
tx: sig,
|
|
757
1096
|
amount: Number.parseFloat(amountUi),
|
|
758
1097
|
token: "USDC",
|
|
759
|
-
label: `${
|
|
1098
|
+
label: `${destination.slice(0, 4)}...${destination.slice(-4)}`
|
|
760
1099
|
});
|
|
761
|
-
return { text: `Sent ${amountUi} USDC to \`${
|
|
1100
|
+
return { text: `Sent ${amountUi} USDC to \`${destination}\`\n[View transaction](https://solscan.io/tx/${sig})` };
|
|
762
1101
|
} catch (err) {
|
|
763
1102
|
appendHistory(histPath, {
|
|
764
1103
|
t: Date.now(),
|
|
@@ -766,7 +1105,7 @@ async function handleSend(parts, wallet, signer, rpc, histPath) {
|
|
|
766
1105
|
kind: "transfer",
|
|
767
1106
|
net: SOL_MAINNET,
|
|
768
1107
|
from: wallet,
|
|
769
|
-
to:
|
|
1108
|
+
to: destination,
|
|
770
1109
|
token: "USDC",
|
|
771
1110
|
error: String(err).substring(0, 200)
|
|
772
1111
|
});
|
|
@@ -794,31 +1133,46 @@ function handleHistory(histPath, page) {
|
|
|
794
1133
|
function createWalletCommand(ctx) {
|
|
795
1134
|
return {
|
|
796
1135
|
name: "x_wallet",
|
|
797
|
-
description: "Wallet status,
|
|
1136
|
+
description: "Wallet status, balances, payment readiness, and transaction history",
|
|
798
1137
|
acceptsArgs: true,
|
|
1138
|
+
requireAuth: true,
|
|
799
1139
|
handler: async (cmdCtx) => {
|
|
800
|
-
|
|
801
|
-
|
|
1140
|
+
await ctx.ensureReady();
|
|
1141
|
+
const solanaWallet = ctx.getSolanaWalletAddress();
|
|
1142
|
+
const evmWallet = ctx.getEvmWalletAddress();
|
|
1143
|
+
if (!solanaWallet && !evmWallet) return { text: "Wallet not configured yet.\nRun `x402-proxy setup` or set `X402_PROXY_WALLET_MNEMONIC` on the gateway host." };
|
|
802
1144
|
const parts = (cmdCtx.args?.trim() ?? "").split(/\s+/).filter(Boolean);
|
|
803
|
-
if (parts[0]?.toLowerCase() === "send") return handleSend(parts.slice(1), walletAddress, ctx.getSigner(), ctx.rpcUrl, ctx.historyPath);
|
|
804
1145
|
if (parts[0]?.toLowerCase() === "history") {
|
|
805
1146
|
const pageArg = parts[1];
|
|
806
1147
|
const page = pageArg ? Math.max(1, Number.parseInt(pageArg, 10) || 1) : 1;
|
|
807
1148
|
return handleHistory(ctx.historyPath, page);
|
|
808
1149
|
}
|
|
1150
|
+
if (parts[0]?.toLowerCase() === "send") return { text: "Use `/x_send <amount|all> <address>` for transfers." };
|
|
809
1151
|
try {
|
|
810
|
-
const snap = await getWalletSnapshot(ctx.rpcUrl,
|
|
811
|
-
const
|
|
812
|
-
const lines = [`x402-proxy v0.9.4`];
|
|
1152
|
+
const snap = await getWalletSnapshot(ctx.rpcUrl, solanaWallet, evmWallet, ctx.historyPath);
|
|
1153
|
+
const lines = [`x402-proxy v0.10.1`];
|
|
813
1154
|
const defaultModel = ctx.allModels[0];
|
|
814
1155
|
if (defaultModel) lines.push("", `**Model** - ${defaultModel.name} (${defaultModel.provider})`);
|
|
815
|
-
lines.push("", `**
|
|
816
|
-
lines.push(
|
|
817
|
-
|
|
1156
|
+
lines.push("", `**Protocol** - ${ctx.getDefaultRequestProtocol()}`);
|
|
1157
|
+
lines.push(`MPP session budget: ${ctx.getDefaultMppSessionBudget()} USDC`);
|
|
1158
|
+
lines.push(`MPP ready: ${evmWallet ? "yes" : "no"}`);
|
|
1159
|
+
lines.push(`x402 ready: ${solanaWallet ? "yes" : "no"}`);
|
|
1160
|
+
if (evmWallet) {
|
|
1161
|
+
lines.push("", "**EVM / Tempo**");
|
|
1162
|
+
lines.push(`\`${evmWallet}\``);
|
|
1163
|
+
lines.push(` Base: ${snap.balances.evm?.usdc ?? "?"} USDC`);
|
|
1164
|
+
lines.push(` Tempo: ${snap.balances.tempo?.usdc ?? "?"} USDC`);
|
|
1165
|
+
}
|
|
1166
|
+
if (solanaWallet) {
|
|
1167
|
+
const solscanUrl = `https://solscan.io/account/${solanaWallet}`;
|
|
1168
|
+
lines.push("", `**[Solana Wallet](${solscanUrl})**`, `\`${solanaWallet}\``);
|
|
1169
|
+
lines.push(` ${snap.sol} SOL`, ` ${snap.ui} USDC`);
|
|
1170
|
+
if (snap.spend.today > 0) lines.push(` -${snap.spend.today.toFixed(2)} USDC today`);
|
|
1171
|
+
}
|
|
818
1172
|
if (snap.tokens.length > 0) {
|
|
819
1173
|
const displayTokens = snap.tokens.slice(0, 10);
|
|
820
1174
|
const symbols = await resolveTokenSymbols(displayTokens.map((t) => t.mint));
|
|
821
|
-
lines.push("", "**Tokens**");
|
|
1175
|
+
lines.push("", "**Solana Tokens**");
|
|
822
1176
|
for (const t of displayTokens) {
|
|
823
1177
|
const label = symbols.get(t.mint) ?? `${t.mint.slice(0, 4)}...${t.mint.slice(-4)}`;
|
|
824
1178
|
const amt = Number.parseFloat(t.amount).toLocaleString("en-US", { maximumFractionDigits: 0 });
|
|
@@ -833,21 +1187,199 @@ function createWalletCommand(ctx) {
|
|
|
833
1187
|
for (const r of recentRecords) lines.push(formatTxLine(r));
|
|
834
1188
|
}
|
|
835
1189
|
}
|
|
836
|
-
lines.push("", "
|
|
1190
|
+
if (!evmWallet) lines.push("", "MPP setup: add an EVM wallet via `x402-proxy setup` or `X402_PROXY_WALLET_EVM_KEY`.");
|
|
1191
|
+
if (!solanaWallet) lines.push("", "x402 setup: add a Solana wallet or mnemonic if you need Solana x402.");
|
|
1192
|
+
lines.push("", "History: `/x_wallet history`", "Send: `/x_send <amount|all> <address>`");
|
|
837
1193
|
if (ctx.dashboardUrl) lines.push(`[Dashboard](${ctx.dashboardUrl})`);
|
|
838
1194
|
return { text: lines.join("\n") };
|
|
839
1195
|
} catch (err) {
|
|
840
|
-
return { text: `Failed to check
|
|
1196
|
+
return { text: `Failed to check wallet status: ${String(err)}` };
|
|
841
1197
|
}
|
|
842
1198
|
}
|
|
843
1199
|
};
|
|
844
1200
|
}
|
|
1201
|
+
function createSendCommand(ctx) {
|
|
1202
|
+
return {
|
|
1203
|
+
name: "x_send",
|
|
1204
|
+
description: "Send USDC from the plugin wallet with an explicit confirmation step",
|
|
1205
|
+
acceptsArgs: true,
|
|
1206
|
+
requireAuth: true,
|
|
1207
|
+
handler: async (cmdCtx) => {
|
|
1208
|
+
await ctx.ensureReady();
|
|
1209
|
+
const wallet = ctx.getSolanaWalletAddress();
|
|
1210
|
+
if (!wallet) return { text: "No Solana wallet configured. `/x_send` only works with the Solana USDC wallet." };
|
|
1211
|
+
const key = senderKey(cmdCtx);
|
|
1212
|
+
const now = Date.now();
|
|
1213
|
+
const pending = pendingSends.get(key);
|
|
1214
|
+
if (pending && now - pending.createdAt > SEND_CONFIRM_TTL_MS) pendingSends.delete(key);
|
|
1215
|
+
const parts = (cmdCtx.args?.trim() ?? "").split(/\s+/).filter(Boolean);
|
|
1216
|
+
if (parts.length === 0) return { text: "Usage: `/x_send <amount|all> <address>`\nConfirm with `/x_send confirm`\nCancel with `/x_send cancel`" };
|
|
1217
|
+
if (parts[0]?.toLowerCase() === "confirm") {
|
|
1218
|
+
const next = pendingSends.get(key);
|
|
1219
|
+
if (!next) return { text: "No pending transfer to confirm." };
|
|
1220
|
+
pendingSends.delete(key);
|
|
1221
|
+
return executeSend(next.amount, next.destination, wallet, ctx.getSigner(), ctx.rpcUrl, ctx.historyPath);
|
|
1222
|
+
}
|
|
1223
|
+
if (parts[0]?.toLowerCase() === "cancel") {
|
|
1224
|
+
pendingSends.delete(key);
|
|
1225
|
+
return { text: "Pending transfer cleared." };
|
|
1226
|
+
}
|
|
1227
|
+
if (parts.length !== 2) return { text: "Usage: `/x_send <amount|all> <address>`\nExample: `/x_send 0.5 7xKXtg...`" };
|
|
1228
|
+
const [amount, destination] = parts;
|
|
1229
|
+
if (destination.length < 32 || destination.length > 44) return { text: `Invalid Solana address: ${destination}` };
|
|
1230
|
+
pendingSends.set(key, {
|
|
1231
|
+
amount,
|
|
1232
|
+
destination,
|
|
1233
|
+
createdAt: now
|
|
1234
|
+
});
|
|
1235
|
+
return { text: `Pending transfer: send ${amount} USDC to \`${destination}\`\nConfirm within 5 minutes with \`/x_send confirm\`\nCancel with \`/x_send cancel\`` };
|
|
1236
|
+
}
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
//#endregion
|
|
1240
|
+
//#region src/openclaw/defaults.ts
|
|
1241
|
+
const DEFAULT_SURF_PROVIDER_ID = "surf";
|
|
1242
|
+
const DEFAULT_SURF_BASE_URL = "/x402-proxy/v1";
|
|
1243
|
+
const DEFAULT_SURF_UPSTREAM_URL = "https://surf.cascade.fyi/api/v1/inference";
|
|
1244
|
+
const DEFAULT_SURF_MODELS = [
|
|
1245
|
+
{
|
|
1246
|
+
id: "anthropic/claude-opus-4.6",
|
|
1247
|
+
name: "Claude Opus 4.6",
|
|
1248
|
+
maxTokens: 2e5,
|
|
1249
|
+
reasoning: true,
|
|
1250
|
+
input: ["text", "image"],
|
|
1251
|
+
cost: {
|
|
1252
|
+
input: .015,
|
|
1253
|
+
output: .075,
|
|
1254
|
+
cacheRead: .0015,
|
|
1255
|
+
cacheWrite: .01875
|
|
1256
|
+
},
|
|
1257
|
+
contextWindow: 2e5
|
|
1258
|
+
},
|
|
1259
|
+
{
|
|
1260
|
+
id: "anthropic/claude-sonnet-4.6",
|
|
1261
|
+
name: "Claude Sonnet 4.6",
|
|
1262
|
+
maxTokens: 2e5,
|
|
1263
|
+
reasoning: true,
|
|
1264
|
+
input: ["text", "image"],
|
|
1265
|
+
cost: {
|
|
1266
|
+
input: .003,
|
|
1267
|
+
output: .015,
|
|
1268
|
+
cacheRead: 3e-4,
|
|
1269
|
+
cacheWrite: .00375
|
|
1270
|
+
},
|
|
1271
|
+
contextWindow: 2e5
|
|
1272
|
+
},
|
|
1273
|
+
{
|
|
1274
|
+
id: "x-ai/grok-4.20-beta",
|
|
1275
|
+
name: "Grok 4.20 Beta",
|
|
1276
|
+
maxTokens: 131072,
|
|
1277
|
+
reasoning: true,
|
|
1278
|
+
input: ["text"],
|
|
1279
|
+
cost: {
|
|
1280
|
+
input: .003,
|
|
1281
|
+
output: .015,
|
|
1282
|
+
cacheRead: 0,
|
|
1283
|
+
cacheWrite: 0
|
|
1284
|
+
},
|
|
1285
|
+
contextWindow: 131072
|
|
1286
|
+
},
|
|
1287
|
+
{
|
|
1288
|
+
id: "minimax/minimax-m2.5",
|
|
1289
|
+
name: "MiniMax M2.5",
|
|
1290
|
+
maxTokens: 1e6,
|
|
1291
|
+
reasoning: false,
|
|
1292
|
+
input: ["text"],
|
|
1293
|
+
cost: {
|
|
1294
|
+
input: .001,
|
|
1295
|
+
output: .005,
|
|
1296
|
+
cacheRead: 0,
|
|
1297
|
+
cacheWrite: 0
|
|
1298
|
+
},
|
|
1299
|
+
contextWindow: 1e6
|
|
1300
|
+
},
|
|
1301
|
+
{
|
|
1302
|
+
id: "moonshotai/kimi-k2.5",
|
|
1303
|
+
name: "Kimi K2.5",
|
|
1304
|
+
maxTokens: 131072,
|
|
1305
|
+
reasoning: true,
|
|
1306
|
+
input: ["text"],
|
|
1307
|
+
cost: {
|
|
1308
|
+
input: .002,
|
|
1309
|
+
output: .008,
|
|
1310
|
+
cacheRead: 0,
|
|
1311
|
+
cacheWrite: 0
|
|
1312
|
+
},
|
|
1313
|
+
contextWindow: 131072
|
|
1314
|
+
},
|
|
1315
|
+
{
|
|
1316
|
+
id: "z-ai/glm-5",
|
|
1317
|
+
name: "GLM-5",
|
|
1318
|
+
maxTokens: 128e3,
|
|
1319
|
+
reasoning: false,
|
|
1320
|
+
input: ["text"],
|
|
1321
|
+
cost: {
|
|
1322
|
+
input: .001,
|
|
1323
|
+
output: .005,
|
|
1324
|
+
cacheRead: 0,
|
|
1325
|
+
cacheWrite: 0
|
|
1326
|
+
},
|
|
1327
|
+
contextWindow: 128e3
|
|
1328
|
+
}
|
|
1329
|
+
];
|
|
1330
|
+
function resolveProviders(config) {
|
|
1331
|
+
const defaultProtocol = resolveProtocol(config.protocol);
|
|
1332
|
+
const defaultMppSessionBudget = resolveMppSessionBudget(config.mppSessionBudget);
|
|
1333
|
+
const raw = config.providers ?? {};
|
|
1334
|
+
const entries = Object.entries(raw).length > 0 ? Object.entries(raw).map(([id, provider]) => ({
|
|
1335
|
+
id,
|
|
1336
|
+
baseUrl: provider.baseUrl || "/x402-proxy/v1",
|
|
1337
|
+
upstreamUrl: provider.upstreamUrl || "https://surf.cascade.fyi/api/v1/inference",
|
|
1338
|
+
protocol: resolveProtocol(provider.protocol, defaultProtocol),
|
|
1339
|
+
mppSessionBudget: resolveMppSessionBudget(provider.mppSessionBudget, defaultMppSessionBudget),
|
|
1340
|
+
models: provider.models && provider.models.length > 0 ? provider.models : DEFAULT_SURF_MODELS
|
|
1341
|
+
})) : [{
|
|
1342
|
+
id: DEFAULT_SURF_PROVIDER_ID,
|
|
1343
|
+
baseUrl: DEFAULT_SURF_BASE_URL,
|
|
1344
|
+
upstreamUrl: DEFAULT_SURF_UPSTREAM_URL,
|
|
1345
|
+
protocol: defaultProtocol,
|
|
1346
|
+
mppSessionBudget: defaultMppSessionBudget,
|
|
1347
|
+
models: DEFAULT_SURF_MODELS
|
|
1348
|
+
}];
|
|
1349
|
+
return {
|
|
1350
|
+
providers: entries,
|
|
1351
|
+
models: entries.flatMap((provider) => provider.models.map((model) => ({
|
|
1352
|
+
...model,
|
|
1353
|
+
provider: provider.id
|
|
1354
|
+
})))
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
function routePrefixForBaseUrl(baseUrl) {
|
|
1358
|
+
const segments = baseUrl.split("/").filter(Boolean);
|
|
1359
|
+
return segments.length > 0 ? `/${segments[0]}` : "/";
|
|
1360
|
+
}
|
|
1361
|
+
function resolveProtocol(value, fallback = "mpp") {
|
|
1362
|
+
return value === "x402" || value === "mpp" || value === "auto" ? value : fallback;
|
|
1363
|
+
}
|
|
1364
|
+
function resolveMppSessionBudget(value, fallback = "0.5") {
|
|
1365
|
+
return typeof value === "string" && value.trim().length > 0 ? value : fallback;
|
|
1366
|
+
}
|
|
845
1367
|
//#endregion
|
|
846
1368
|
//#region src/openclaw/route.ts
|
|
847
|
-
function
|
|
848
|
-
const {
|
|
1369
|
+
function createInferenceProxyRouteHandler(opts) {
|
|
1370
|
+
const { providers, getX402Proxy, getWalletAddress, getWalletAddressForNetwork, getEvmKey, historyPath, allModels, logger } = opts;
|
|
1371
|
+
const sortedProviders = providers.slice().sort((left, right) => right.baseUrl.length - left.baseUrl.length);
|
|
849
1372
|
return async (req, res) => {
|
|
850
1373
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
1374
|
+
const provider = sortedProviders.find((entry) => entry.baseUrl === "/" || url.pathname.startsWith(entry.baseUrl));
|
|
1375
|
+
if (!provider) {
|
|
1376
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1377
|
+
res.end(JSON.stringify({ error: {
|
|
1378
|
+
message: "Unknown inference route",
|
|
1379
|
+
code: "not_found"
|
|
1380
|
+
} }));
|
|
1381
|
+
return true;
|
|
1382
|
+
}
|
|
851
1383
|
const walletAddress = getWalletAddress();
|
|
852
1384
|
if (!walletAddress) {
|
|
853
1385
|
res.writeHead(503, { "Content-Type": "application/json" });
|
|
@@ -855,11 +1387,11 @@ function createX402RouteHandler(opts) {
|
|
|
855
1387
|
message: "Wallet not loaded yet",
|
|
856
1388
|
code: "not_ready"
|
|
857
1389
|
} }));
|
|
858
|
-
return;
|
|
1390
|
+
return true;
|
|
859
1391
|
}
|
|
860
|
-
const pathSuffix = url.pathname.slice(
|
|
861
|
-
const upstreamUrl =
|
|
862
|
-
logger.info(`
|
|
1392
|
+
const pathSuffix = provider.baseUrl === "/" ? url.pathname : url.pathname.slice(provider.baseUrl.length);
|
|
1393
|
+
const upstreamUrl = `${provider.upstreamUrl.replace(/\/+$/, "")}${pathSuffix.startsWith("/") ? pathSuffix : `/${pathSuffix}`}${url.search}`;
|
|
1394
|
+
logger.info(`proxy: intercepting ${upstreamUrl.substring(0, 80)}`);
|
|
863
1395
|
const chunks = [];
|
|
864
1396
|
for await (const chunk of req) chunks.push(Buffer.from(chunk));
|
|
865
1397
|
let body = Buffer.concat(chunks).toString("utf-8");
|
|
@@ -891,22 +1423,62 @@ function createX402RouteHandler(opts) {
|
|
|
891
1423
|
const method = req.method ?? "GET";
|
|
892
1424
|
const startMs = Date.now();
|
|
893
1425
|
try {
|
|
894
|
-
const
|
|
1426
|
+
const requestInit = {
|
|
895
1427
|
method,
|
|
896
1428
|
headers,
|
|
897
1429
|
body: ["GET", "HEAD"].includes(method) ? void 0 : body
|
|
898
|
-
}
|
|
1430
|
+
};
|
|
1431
|
+
const useMpp = provider.protocol === "mpp" || provider.protocol === "auto";
|
|
1432
|
+
const wantsStreaming = isChatCompletion && /"stream"\s*:\s*true/.test(body);
|
|
1433
|
+
if (useMpp) {
|
|
1434
|
+
const evmKey = getEvmKey();
|
|
1435
|
+
if (!evmKey) {
|
|
1436
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
1437
|
+
res.end(JSON.stringify({ error: {
|
|
1438
|
+
message: "MPP inference requires an EVM wallet. Configure X402_PROXY_WALLET_MNEMONIC or X402_PROXY_WALLET_EVM_KEY.",
|
|
1439
|
+
code: "mpp_wallet_missing"
|
|
1440
|
+
} }));
|
|
1441
|
+
return true;
|
|
1442
|
+
}
|
|
1443
|
+
return await handleMppRequest({
|
|
1444
|
+
req,
|
|
1445
|
+
res,
|
|
1446
|
+
upstreamUrl,
|
|
1447
|
+
requestInit,
|
|
1448
|
+
walletAddress,
|
|
1449
|
+
historyPath,
|
|
1450
|
+
logger,
|
|
1451
|
+
allModels,
|
|
1452
|
+
thinkingMode,
|
|
1453
|
+
wantsStreaming,
|
|
1454
|
+
startMs,
|
|
1455
|
+
evmKey,
|
|
1456
|
+
mppSessionBudget: provider.mppSessionBudget
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
const proxy = getX402Proxy();
|
|
1460
|
+
if (!proxy) {
|
|
1461
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
1462
|
+
res.end(JSON.stringify({ error: {
|
|
1463
|
+
message: "x402 wallet not loaded yet. Configure a Solana wallet or switch the provider to mpp.",
|
|
1464
|
+
code: "x402_wallet_missing"
|
|
1465
|
+
} }));
|
|
1466
|
+
return true;
|
|
1467
|
+
}
|
|
1468
|
+
const response = await proxy.x402Fetch(upstreamUrl, requestInit);
|
|
899
1469
|
if (response.status === 402) {
|
|
900
1470
|
const responseBody = await response.text();
|
|
901
1471
|
logger.error(`x402: payment failed, raw response: ${responseBody}`);
|
|
902
1472
|
const payment = proxy.shiftPayment();
|
|
903
1473
|
const amount = paymentAmount(payment);
|
|
1474
|
+
const paymentFrom = (payment?.network && getWalletAddressForNetwork?.(payment.network)) ?? walletAddress;
|
|
1475
|
+
const paymentNetwork = payment?.network ?? "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
904
1476
|
appendHistory(historyPath, {
|
|
905
1477
|
t: Date.now(),
|
|
906
1478
|
ok: false,
|
|
907
1479
|
kind: "x402_inference",
|
|
908
|
-
net:
|
|
909
|
-
from:
|
|
1480
|
+
net: paymentNetwork,
|
|
1481
|
+
from: paymentFrom,
|
|
910
1482
|
to: payment?.payTo,
|
|
911
1483
|
amount,
|
|
912
1484
|
token: amount != null ? "USDC" : void 0,
|
|
@@ -923,19 +1495,21 @@ function createX402RouteHandler(opts) {
|
|
|
923
1495
|
type: "x402_payment_error",
|
|
924
1496
|
code: "payment_failed"
|
|
925
1497
|
} }));
|
|
926
|
-
return;
|
|
1498
|
+
return true;
|
|
927
1499
|
}
|
|
928
1500
|
if (!response.ok && isChatCompletion) {
|
|
929
1501
|
const responseBody = await response.text();
|
|
930
1502
|
logger.error(`x402: upstream error ${response.status}: ${responseBody.substring(0, 300)}`);
|
|
931
1503
|
const payment = proxy.shiftPayment();
|
|
932
1504
|
const amount = paymentAmount(payment);
|
|
1505
|
+
const paymentFrom = (payment?.network && getWalletAddressForNetwork?.(payment.network)) ?? walletAddress;
|
|
1506
|
+
const paymentNetwork = payment?.network ?? "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
933
1507
|
appendHistory(historyPath, {
|
|
934
1508
|
t: Date.now(),
|
|
935
1509
|
ok: false,
|
|
936
1510
|
kind: "x402_inference",
|
|
937
|
-
net:
|
|
938
|
-
from:
|
|
1511
|
+
net: paymentNetwork,
|
|
1512
|
+
from: paymentFrom,
|
|
939
1513
|
to: payment?.payTo,
|
|
940
1514
|
amount,
|
|
941
1515
|
token: amount != null ? "USDC" : void 0,
|
|
@@ -948,12 +1522,14 @@ function createX402RouteHandler(opts) {
|
|
|
948
1522
|
type: "x402_upstream_error",
|
|
949
1523
|
code: "upstream_failed"
|
|
950
1524
|
} }));
|
|
951
|
-
return;
|
|
1525
|
+
return true;
|
|
952
1526
|
}
|
|
953
1527
|
logger.info(`x402: response ${response.status}`);
|
|
954
1528
|
const txSig = extractTxSignature(response);
|
|
955
1529
|
const payment = proxy.shiftPayment();
|
|
956
1530
|
const amount = paymentAmount(payment);
|
|
1531
|
+
const paymentFrom = (payment?.network && getWalletAddressForNetwork?.(payment.network)) ?? walletAddress;
|
|
1532
|
+
const paymentNetwork = payment?.network ?? "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
957
1533
|
const resHeaders = {};
|
|
958
1534
|
for (const [key, val] of response.headers.entries()) resHeaders[key] = val;
|
|
959
1535
|
res.writeHead(response.status, resHeaders);
|
|
@@ -963,20 +1539,18 @@ function createX402RouteHandler(opts) {
|
|
|
963
1539
|
t: Date.now(),
|
|
964
1540
|
ok: true,
|
|
965
1541
|
kind: "x402_inference",
|
|
966
|
-
net:
|
|
967
|
-
from:
|
|
1542
|
+
net: paymentNetwork,
|
|
1543
|
+
from: paymentFrom,
|
|
968
1544
|
to: payment?.payTo,
|
|
969
1545
|
tx: txSig,
|
|
970
1546
|
amount,
|
|
971
1547
|
token: "USDC",
|
|
972
1548
|
ms: Date.now() - startMs
|
|
973
1549
|
});
|
|
974
|
-
return;
|
|
1550
|
+
return true;
|
|
975
1551
|
}
|
|
976
1552
|
const ct = response.headers.get("content-type") || "";
|
|
977
|
-
const
|
|
978
|
-
let lastDataLine = "";
|
|
979
|
-
let residual = "";
|
|
1553
|
+
const sse = isChatCompletion && ct.includes("text/event-stream") ? createSseTracker() : null;
|
|
980
1554
|
const reader = response.body.getReader();
|
|
981
1555
|
const decoder = new TextDecoder();
|
|
982
1556
|
try {
|
|
@@ -984,27 +1558,23 @@ function createX402RouteHandler(opts) {
|
|
|
984
1558
|
const { done, value } = await reader.read();
|
|
985
1559
|
if (done) break;
|
|
986
1560
|
res.write(value);
|
|
987
|
-
|
|
988
|
-
const lines = (residual + decoder.decode(value, { stream: true })).split("\n");
|
|
989
|
-
residual = lines.pop() ?? "";
|
|
990
|
-
for (const line of lines) if (line.startsWith("data: ") && line !== "data: [DONE]") lastDataLine = line.slice(6);
|
|
991
|
-
}
|
|
1561
|
+
sse?.push(decoder.decode(value, { stream: true }));
|
|
992
1562
|
}
|
|
993
1563
|
} finally {
|
|
994
1564
|
reader.releaseLock();
|
|
995
1565
|
}
|
|
996
1566
|
res.end();
|
|
997
1567
|
const durationMs = Date.now() - startMs;
|
|
998
|
-
if (
|
|
999
|
-
const parsed = JSON.parse(
|
|
1568
|
+
if (sse?.lastData) try {
|
|
1569
|
+
const parsed = JSON.parse(sse.lastData);
|
|
1000
1570
|
const usage = parsed.usage;
|
|
1001
1571
|
const model = parsed.model ?? "";
|
|
1002
1572
|
appendHistory(historyPath, {
|
|
1003
1573
|
t: Date.now(),
|
|
1004
1574
|
ok: true,
|
|
1005
1575
|
kind: "x402_inference",
|
|
1006
|
-
net:
|
|
1007
|
-
from:
|
|
1576
|
+
net: paymentNetwork,
|
|
1577
|
+
from: paymentFrom,
|
|
1008
1578
|
to: payment?.payTo,
|
|
1009
1579
|
tx: txSig,
|
|
1010
1580
|
amount,
|
|
@@ -1024,8 +1594,8 @@ function createX402RouteHandler(opts) {
|
|
|
1024
1594
|
t: Date.now(),
|
|
1025
1595
|
ok: true,
|
|
1026
1596
|
kind: "x402_inference",
|
|
1027
|
-
net:
|
|
1028
|
-
from:
|
|
1597
|
+
net: paymentNetwork,
|
|
1598
|
+
from: paymentFrom,
|
|
1029
1599
|
to: payment?.payTo,
|
|
1030
1600
|
tx: txSig,
|
|
1031
1601
|
amount,
|
|
@@ -1037,19 +1607,19 @@ function createX402RouteHandler(opts) {
|
|
|
1037
1607
|
t: Date.now(),
|
|
1038
1608
|
ok: true,
|
|
1039
1609
|
kind: "x402_inference",
|
|
1040
|
-
net:
|
|
1041
|
-
from:
|
|
1610
|
+
net: paymentNetwork,
|
|
1611
|
+
from: paymentFrom,
|
|
1042
1612
|
to: payment?.payTo,
|
|
1043
1613
|
tx: txSig,
|
|
1044
1614
|
amount,
|
|
1045
1615
|
token: "USDC",
|
|
1046
1616
|
ms: durationMs
|
|
1047
1617
|
});
|
|
1048
|
-
return;
|
|
1618
|
+
return true;
|
|
1049
1619
|
} catch (err) {
|
|
1050
1620
|
const msg = String(err);
|
|
1051
1621
|
logger.error(`x402: fetch threw: ${msg}`);
|
|
1052
|
-
|
|
1622
|
+
getX402Proxy()?.shiftPayment();
|
|
1053
1623
|
appendHistory(historyPath, {
|
|
1054
1624
|
t: Date.now(),
|
|
1055
1625
|
ok: false,
|
|
@@ -1071,104 +1641,258 @@ function createX402RouteHandler(opts) {
|
|
|
1071
1641
|
code: "payment_failed"
|
|
1072
1642
|
} }));
|
|
1073
1643
|
}
|
|
1644
|
+
return true;
|
|
1074
1645
|
}
|
|
1075
1646
|
};
|
|
1076
1647
|
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
const raw = config.providers ?? {};
|
|
1081
|
-
const models = [];
|
|
1082
|
-
const upstreamOrigins = [];
|
|
1083
|
-
for (const [name, prov] of Object.entries(raw)) {
|
|
1084
|
-
if (prov.upstreamUrl) upstreamOrigins.push(prov.upstreamUrl);
|
|
1085
|
-
for (const m of prov.models) models.push({
|
|
1086
|
-
...m,
|
|
1087
|
-
provider: name
|
|
1088
|
-
});
|
|
1089
|
-
}
|
|
1648
|
+
function createSseTracker() {
|
|
1649
|
+
let residual = "";
|
|
1650
|
+
let lastDataLine = "";
|
|
1090
1651
|
return {
|
|
1091
|
-
|
|
1092
|
-
|
|
1652
|
+
push(text) {
|
|
1653
|
+
const lines = (residual + text).split("\n");
|
|
1654
|
+
residual = lines.pop() ?? "";
|
|
1655
|
+
for (const line of lines) if (line.startsWith("data: ") && line !== "data: [DONE]") lastDataLine = line.slice(6);
|
|
1656
|
+
},
|
|
1657
|
+
get lastData() {
|
|
1658
|
+
return lastDataLine;
|
|
1659
|
+
}
|
|
1093
1660
|
};
|
|
1094
1661
|
}
|
|
1662
|
+
async function closeMppSession(handler) {
|
|
1663
|
+
try {
|
|
1664
|
+
await handler.close();
|
|
1665
|
+
} catch {}
|
|
1666
|
+
}
|
|
1667
|
+
async function handleMppRequest(opts) {
|
|
1668
|
+
const { req, res, upstreamUrl, requestInit, walletAddress, historyPath, logger, allModels, thinkingMode, wantsStreaming, startMs, evmKey, mppSessionBudget } = opts;
|
|
1669
|
+
const mpp = await createMppProxyHandler({
|
|
1670
|
+
evmKey,
|
|
1671
|
+
maxDeposit: mppSessionBudget
|
|
1672
|
+
});
|
|
1673
|
+
try {
|
|
1674
|
+
if (wantsStreaming) {
|
|
1675
|
+
res.writeHead(200, {
|
|
1676
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
1677
|
+
"Cache-Control": "no-cache, no-transform",
|
|
1678
|
+
Connection: "keep-alive"
|
|
1679
|
+
});
|
|
1680
|
+
const sse = createSseTracker();
|
|
1681
|
+
const stream = await mpp.sse(upstreamUrl, requestInit);
|
|
1682
|
+
for await (const chunk of stream) {
|
|
1683
|
+
const text = String(chunk);
|
|
1684
|
+
res.write(text);
|
|
1685
|
+
sse.push(text);
|
|
1686
|
+
}
|
|
1687
|
+
res.end();
|
|
1688
|
+
mpp.shiftPayment();
|
|
1689
|
+
const payment = mpp.shiftPayment();
|
|
1690
|
+
appendInferenceHistory({
|
|
1691
|
+
historyPath,
|
|
1692
|
+
allModels,
|
|
1693
|
+
walletAddress,
|
|
1694
|
+
paymentNetwork: payment?.network ?? "eip155:4217",
|
|
1695
|
+
paymentTo: void 0,
|
|
1696
|
+
tx: payment?.receipt?.reference ?? payment?.channelId,
|
|
1697
|
+
amount: parseMppAmount(payment?.amount),
|
|
1698
|
+
thinkingMode,
|
|
1699
|
+
lastDataLine: sse.lastData,
|
|
1700
|
+
durationMs: Date.now() - startMs
|
|
1701
|
+
});
|
|
1702
|
+
return true;
|
|
1703
|
+
}
|
|
1704
|
+
const response = await mpp.fetch(upstreamUrl, requestInit);
|
|
1705
|
+
const tx = extractTxSignature(response);
|
|
1706
|
+
if (response.status === 402) {
|
|
1707
|
+
const responseBody = await response.text();
|
|
1708
|
+
logger.error(`mpp: payment failed, raw response: ${responseBody}`);
|
|
1709
|
+
appendHistory(historyPath, {
|
|
1710
|
+
t: Date.now(),
|
|
1711
|
+
ok: false,
|
|
1712
|
+
kind: "x402_inference",
|
|
1713
|
+
net: TEMPO_NETWORK,
|
|
1714
|
+
from: walletAddress,
|
|
1715
|
+
ms: Date.now() - startMs,
|
|
1716
|
+
error: "payment_required"
|
|
1717
|
+
});
|
|
1718
|
+
res.writeHead(402, { "Content-Type": "application/json" });
|
|
1719
|
+
res.end(JSON.stringify({ error: {
|
|
1720
|
+
message: `MPP payment failed: ${responseBody.substring(0, 200) || "unknown error"}. Wallet: ${walletAddress}`,
|
|
1721
|
+
type: "mpp_payment_error",
|
|
1722
|
+
code: "payment_failed"
|
|
1723
|
+
} }));
|
|
1724
|
+
return true;
|
|
1725
|
+
}
|
|
1726
|
+
const resHeaders = {};
|
|
1727
|
+
for (const [key, value] of response.headers.entries()) resHeaders[key] = value;
|
|
1728
|
+
res.writeHead(response.status, resHeaders);
|
|
1729
|
+
const responseBody = await response.text();
|
|
1730
|
+
res.end(responseBody);
|
|
1731
|
+
const payment = mpp.shiftPayment();
|
|
1732
|
+
appendInferenceHistory({
|
|
1733
|
+
historyPath,
|
|
1734
|
+
allModels,
|
|
1735
|
+
walletAddress,
|
|
1736
|
+
paymentNetwork: payment?.network ?? "eip155:4217",
|
|
1737
|
+
paymentTo: void 0,
|
|
1738
|
+
tx: tx ?? payment?.receipt?.reference,
|
|
1739
|
+
amount: parseMppAmount(payment?.amount),
|
|
1740
|
+
thinkingMode,
|
|
1741
|
+
lastDataLine: responseBody,
|
|
1742
|
+
durationMs: Date.now() - startMs
|
|
1743
|
+
});
|
|
1744
|
+
return true;
|
|
1745
|
+
} catch (err) {
|
|
1746
|
+
logger.error(`mpp: fetch threw: ${String(err)}`);
|
|
1747
|
+
appendHistory(historyPath, {
|
|
1748
|
+
t: Date.now(),
|
|
1749
|
+
ok: false,
|
|
1750
|
+
kind: "x402_inference",
|
|
1751
|
+
net: TEMPO_NETWORK,
|
|
1752
|
+
from: walletAddress,
|
|
1753
|
+
ms: Date.now() - startMs,
|
|
1754
|
+
error: String(err).substring(0, 200)
|
|
1755
|
+
});
|
|
1756
|
+
if (!res.headersSent) {
|
|
1757
|
+
res.writeHead(402, { "Content-Type": "application/json" });
|
|
1758
|
+
res.end(JSON.stringify({ error: {
|
|
1759
|
+
message: `MPP request failed: ${String(err)}`,
|
|
1760
|
+
type: "mpp_payment_error",
|
|
1761
|
+
code: "payment_failed"
|
|
1762
|
+
} }));
|
|
1763
|
+
}
|
|
1764
|
+
return true;
|
|
1765
|
+
} finally {
|
|
1766
|
+
await closeMppSession(mpp);
|
|
1767
|
+
if (!res.writableEnded) res.end();
|
|
1768
|
+
req.resume();
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
function appendInferenceHistory(opts) {
|
|
1772
|
+
const { historyPath, allModels, walletAddress, paymentNetwork, paymentTo, tx, amount, thinkingMode, lastDataLine, durationMs } = opts;
|
|
1773
|
+
try {
|
|
1774
|
+
const parsed = JSON.parse(lastDataLine);
|
|
1775
|
+
const usage = parsed.usage;
|
|
1776
|
+
const model = parsed.model ?? "";
|
|
1777
|
+
appendHistory(historyPath, {
|
|
1778
|
+
t: Date.now(),
|
|
1779
|
+
ok: true,
|
|
1780
|
+
kind: "x402_inference",
|
|
1781
|
+
net: paymentNetwork,
|
|
1782
|
+
from: walletAddress,
|
|
1783
|
+
to: paymentTo,
|
|
1784
|
+
tx,
|
|
1785
|
+
amount,
|
|
1786
|
+
token: "USDC",
|
|
1787
|
+
provider: allModels.find((entry) => entry.id === model || `${entry.provider}/${entry.id}` === model)?.provider,
|
|
1788
|
+
model,
|
|
1789
|
+
inputTokens: usage?.prompt_tokens ?? 0,
|
|
1790
|
+
outputTokens: usage?.completion_tokens ?? 0,
|
|
1791
|
+
reasoningTokens: usage?.completion_tokens_details?.reasoning_tokens,
|
|
1792
|
+
cacheRead: usage?.prompt_tokens_details?.cached_tokens,
|
|
1793
|
+
cacheWrite: usage?.prompt_tokens_details?.cache_creation_input_tokens,
|
|
1794
|
+
thinking: thinkingMode,
|
|
1795
|
+
ms: durationMs
|
|
1796
|
+
});
|
|
1797
|
+
} catch {
|
|
1798
|
+
appendHistory(historyPath, {
|
|
1799
|
+
t: Date.now(),
|
|
1800
|
+
ok: true,
|
|
1801
|
+
kind: "x402_inference",
|
|
1802
|
+
net: paymentNetwork,
|
|
1803
|
+
from: walletAddress,
|
|
1804
|
+
to: paymentTo,
|
|
1805
|
+
tx,
|
|
1806
|
+
amount,
|
|
1807
|
+
token: "USDC",
|
|
1808
|
+
ms: durationMs
|
|
1809
|
+
});
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
//#endregion
|
|
1813
|
+
//#region src/openclaw/plugin.ts
|
|
1095
1814
|
function register(api) {
|
|
1096
1815
|
const config = api.pluginConfig ?? {};
|
|
1097
1816
|
const explicitKeypairPath = config.keypairPath;
|
|
1098
1817
|
const rpcUrl = config.rpcUrl || "https://api.mainnet-beta.solana.com";
|
|
1099
1818
|
const dashboardUrl = config.dashboardUrl || "";
|
|
1100
|
-
const { models: allModels
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
const raw = config.providers ?? {};
|
|
1106
|
-
for (const [name, prov] of Object.entries(raw)) api.registerProvider({
|
|
1107
|
-
id: name,
|
|
1108
|
-
label: `${name} (x402)`,
|
|
1819
|
+
const { providers, models: allModels } = resolveProviders(config);
|
|
1820
|
+
const defaultProvider = providers[0];
|
|
1821
|
+
for (const provider of providers) api.registerProvider({
|
|
1822
|
+
id: provider.id,
|
|
1823
|
+
label: provider.id,
|
|
1109
1824
|
auth: [],
|
|
1110
1825
|
catalog: {
|
|
1111
1826
|
order: "simple",
|
|
1112
1827
|
run: async () => ({ provider: {
|
|
1113
|
-
baseUrl:
|
|
1828
|
+
baseUrl: provider.baseUrl,
|
|
1114
1829
|
api: "openai-completions",
|
|
1115
1830
|
authHeader: false,
|
|
1116
|
-
models:
|
|
1831
|
+
models: provider.models
|
|
1117
1832
|
} })
|
|
1118
1833
|
}
|
|
1119
1834
|
});
|
|
1120
|
-
api.logger.info(`x402-proxy: ${
|
|
1121
|
-
let
|
|
1835
|
+
api.logger.info(`x402-proxy: ${providers.map((provider) => `${provider.id}:${provider.protocol}`).join(", ")} - ${allModels.length} models`);
|
|
1836
|
+
let solanaWalletAddress = null;
|
|
1837
|
+
let evmWalletAddress = null;
|
|
1122
1838
|
let signerRef = null;
|
|
1123
1839
|
let proxyRef = null;
|
|
1124
|
-
let
|
|
1840
|
+
let evmKeyRef = null;
|
|
1841
|
+
let walletLoadPromise = null;
|
|
1125
1842
|
const historyPath = getHistoryPath();
|
|
1843
|
+
const routePrefixes = [...new Set(providers.map((provider) => routePrefixForBaseUrl(provider.baseUrl)))];
|
|
1844
|
+
const handler = createInferenceProxyRouteHandler({
|
|
1845
|
+
providers,
|
|
1846
|
+
getX402Proxy: () => proxyRef,
|
|
1847
|
+
getWalletAddress: () => solanaWalletAddress ?? evmWalletAddress,
|
|
1848
|
+
getWalletAddressForNetwork: (network) => addressForNetwork(evmWalletAddress, solanaWalletAddress, network),
|
|
1849
|
+
getEvmKey: () => evmKeyRef,
|
|
1850
|
+
historyPath,
|
|
1851
|
+
allModels,
|
|
1852
|
+
logger: api.logger
|
|
1853
|
+
});
|
|
1854
|
+
for (const path of routePrefixes) api.registerHttpRoute({
|
|
1855
|
+
path,
|
|
1856
|
+
match: "prefix",
|
|
1857
|
+
auth: "plugin",
|
|
1858
|
+
handler
|
|
1859
|
+
});
|
|
1860
|
+
api.logger.info(`proxy: HTTP routes ${routePrefixes.join(", ")} registered for ${providers.map((provider) => provider.upstreamUrl).join(", ")}`);
|
|
1126
1861
|
async function ensureWalletLoaded() {
|
|
1127
|
-
if (
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
walletAddress = signer.address;
|
|
1134
|
-
} else {
|
|
1862
|
+
if (walletLoadPromise) {
|
|
1863
|
+
await walletLoadPromise;
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1866
|
+
walletLoadPromise = (async () => {
|
|
1867
|
+
try {
|
|
1135
1868
|
const resolution = resolveWallet();
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1869
|
+
evmKeyRef = resolution.evmKey ?? null;
|
|
1870
|
+
evmWalletAddress = resolution.evmAddress ?? null;
|
|
1871
|
+
if (explicitKeypairPath) {
|
|
1872
|
+
signerRef = await loadSvmWallet(explicitKeypairPath.startsWith("~/") ? join(homedir(), explicitKeypairPath.slice(2)) : explicitKeypairPath);
|
|
1873
|
+
solanaWalletAddress = signerRef.address;
|
|
1874
|
+
} else if (resolution.solanaKey && resolution.solanaAddress) {
|
|
1875
|
+
signerRef = await createKeyPairSignerFromBytes(resolution.solanaKey);
|
|
1876
|
+
solanaWalletAddress = resolution.solanaAddress;
|
|
1877
|
+
} else {
|
|
1878
|
+
signerRef = null;
|
|
1879
|
+
solanaWalletAddress = null;
|
|
1880
|
+
}
|
|
1881
|
+
if (!solanaWalletAddress && !evmWalletAddress) {
|
|
1882
|
+
api.logger.error("x402-proxy: no wallet found. Run `x402-proxy setup` to create one, or set X402_PROXY_WALLET_MNEMONIC / X402_PROXY_WALLET_EVM_KEY.");
|
|
1139
1883
|
return;
|
|
1140
1884
|
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1885
|
+
if (signerRef) {
|
|
1886
|
+
const client = new x402Client();
|
|
1887
|
+
client.register(SOL_MAINNET, new OptimizedSvmScheme(signerRef, { rpcUrl }));
|
|
1888
|
+
proxyRef = createX402ProxyHandler({ client });
|
|
1889
|
+
} else proxyRef = null;
|
|
1890
|
+
api.logger.info(`wallets: solana=${solanaWalletAddress ?? "missing"} evm=${evmWalletAddress ?? "missing"}`);
|
|
1891
|
+
} catch (err) {
|
|
1892
|
+
api.logger.error(`wallet load failed: ${err}`);
|
|
1143
1893
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
} catch (err) {
|
|
1147
|
-
walletLoading = false;
|
|
1148
|
-
api.logger.error(`x402: failed to load wallet: ${err}`);
|
|
1149
|
-
return;
|
|
1150
|
-
}
|
|
1151
|
-
const client = new x402Client();
|
|
1152
|
-
client.register(SOL_MAINNET, new OptimizedSvmScheme(signer, { rpcUrl }));
|
|
1153
|
-
proxyRef = createX402ProxyHandler({ client });
|
|
1154
|
-
const upstreamOrigin = upstreamOrigins[0];
|
|
1155
|
-
if (upstreamOrigin) {
|
|
1156
|
-
const handler = createX402RouteHandler({
|
|
1157
|
-
upstreamOrigin,
|
|
1158
|
-
proxy: proxyRef,
|
|
1159
|
-
getWalletAddress: () => walletAddress,
|
|
1160
|
-
historyPath,
|
|
1161
|
-
allModels,
|
|
1162
|
-
logger: api.logger
|
|
1163
|
-
});
|
|
1164
|
-
api.registerHttpRoute({
|
|
1165
|
-
path: "/x402",
|
|
1166
|
-
match: "prefix",
|
|
1167
|
-
auth: "plugin",
|
|
1168
|
-
handler
|
|
1169
|
-
});
|
|
1170
|
-
api.logger.info(`x402: HTTP route registered for ${upstreamOrigin}`);
|
|
1171
|
-
}
|
|
1894
|
+
})();
|
|
1895
|
+
await walletLoadPromise;
|
|
1172
1896
|
}
|
|
1173
1897
|
ensureWalletLoaded();
|
|
1174
1898
|
api.registerService({
|
|
@@ -1179,27 +1903,40 @@ function register(api) {
|
|
|
1179
1903
|
async stop() {}
|
|
1180
1904
|
});
|
|
1181
1905
|
const toolCtx = {
|
|
1182
|
-
|
|
1906
|
+
ensureReady: ensureWalletLoaded,
|
|
1907
|
+
getSolanaWalletAddress: () => solanaWalletAddress,
|
|
1908
|
+
getEvmWalletAddress: () => evmWalletAddress,
|
|
1183
1909
|
getSigner: () => signerRef,
|
|
1910
|
+
getX402Proxy: () => proxyRef,
|
|
1911
|
+
getEvmKey: () => evmKeyRef,
|
|
1912
|
+
getDefaultRequestProtocol: () => defaultProvider?.protocol ?? "mpp",
|
|
1913
|
+
getDefaultMppSessionBudget: () => defaultProvider?.mppSessionBudget ?? "0.5",
|
|
1184
1914
|
rpcUrl,
|
|
1185
1915
|
historyPath,
|
|
1186
|
-
get proxy() {
|
|
1187
|
-
if (!proxyRef) throw new Error("x402 proxy not initialized yet");
|
|
1188
|
-
return proxyRef;
|
|
1189
|
-
},
|
|
1190
1916
|
allModels
|
|
1191
1917
|
};
|
|
1192
|
-
api.registerTool(
|
|
1193
|
-
api.registerTool(
|
|
1918
|
+
api.registerTool(createWalletTool(toolCtx), { names: ["x_balance"] });
|
|
1919
|
+
api.registerTool(createRequestTool(toolCtx), { names: ["x_payment"] });
|
|
1194
1920
|
const cmdCtx = {
|
|
1195
|
-
|
|
1921
|
+
ensureReady: ensureWalletLoaded,
|
|
1922
|
+
getSolanaWalletAddress: () => solanaWalletAddress,
|
|
1923
|
+
getEvmWalletAddress: () => evmWalletAddress,
|
|
1196
1924
|
getSigner: () => signerRef,
|
|
1925
|
+
getDefaultRequestProtocol: () => defaultProvider?.protocol ?? "mpp",
|
|
1926
|
+
getDefaultMppSessionBudget: () => defaultProvider?.mppSessionBudget ?? "0.5",
|
|
1197
1927
|
rpcUrl,
|
|
1198
1928
|
dashboardUrl,
|
|
1199
1929
|
historyPath,
|
|
1200
1930
|
allModels
|
|
1201
1931
|
};
|
|
1202
1932
|
api.registerCommand(createWalletCommand(cmdCtx));
|
|
1933
|
+
api.registerCommand(createSendCommand(cmdCtx));
|
|
1203
1934
|
}
|
|
1935
|
+
var plugin_default = definePluginEntry({
|
|
1936
|
+
id: "x402-proxy",
|
|
1937
|
+
name: "mpp/x402 Payments Proxy",
|
|
1938
|
+
description: "x402 and MPP payments, wallet tools, and paid inference proxying",
|
|
1939
|
+
register
|
|
1940
|
+
});
|
|
1204
1941
|
//#endregion
|
|
1205
|
-
export { register };
|
|
1942
|
+
export { plugin_default as default, register };
|