cornerstone-autonomous-agent 1.0.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/.env.example +49 -0
- package/LICENSE.md +384 -0
- package/README.md +163 -0
- package/SKILL-clawdhub.md +180 -0
- package/adapters/anthropic/tools.json +58 -0
- package/adapters/local/README.md +11 -0
- package/adapters/openai/openapi.yaml +72 -0
- package/adapters/openclaw/SKILL.md +47 -0
- package/package.json +71 -0
- package/src/agent/agent.js +52 -0
- package/src/agent/index.js +4 -0
- package/src/agent/llm.js +25 -0
- package/src/agent/tools/localTools.js +259 -0
- package/src/agent/tools/mcpTools.js +69 -0
- package/src/balance.js +264 -0
- package/src/check-update.js +191 -0
- package/src/contract.js +390 -0
- package/src/credit-aptos-agent.js +103 -0
- package/src/lib/aptos/balance.js +32 -0
- package/src/lib/aptos/config.js +48 -0
- package/src/lib/aptos/index.js +4 -0
- package/src/lib/aptos/signPayment.js +94 -0
- package/src/lib/aptos/wallet.js +143 -0
- package/src/lib/chains.js +157 -0
- package/src/lib/evm/index.js +1 -0
- package/src/lib/evm/signPayment.js +91 -0
- package/src/lib/gas.js +186 -0
- package/src/lib/mcp/client.js +225 -0
- package/src/lib/mcp/index.js +1 -0
- package/src/lib/rpc.js +175 -0
- package/src/lib/wallet.js +255 -0
- package/src/lib/x402/client.js +195 -0
- package/src/lib/x402/index.js +7 -0
- package/src/lib/x402/types.js +50 -0
- package/src/register-aptos-agent.js +92 -0
- package/src/run-agent.js +65 -0
- package/src/setup-aptos.js +57 -0
- package/src/setup.js +104 -0
- package/src/show-agent-addresses.js +32 -0
- package/src/swap.js +449 -0
- package/src/transfer.js +326 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wallet state management
|
|
3
|
+
* Handles wallet generation, loading, saving, and client creation.
|
|
4
|
+
* Supports EVM_PRIVATE_KEY env (single wallet) or multi-wallet file with optional network (testnet/mainnet).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, chmodSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import { createWalletClient, http } from 'viem';
|
|
11
|
+
import { privateKeyToAccount, generatePrivateKey } from 'viem/accounts';
|
|
12
|
+
import { getChain } from './chains.js';
|
|
13
|
+
|
|
14
|
+
const LEGACY_PATH = join(homedir(), '.evm-wallet.json');
|
|
15
|
+
|
|
16
|
+
function getWalletPath() {
|
|
17
|
+
const envPath = process.env.EVM_WALLET_PATH;
|
|
18
|
+
if (envPath) {
|
|
19
|
+
return envPath.startsWith('~') ? join(homedir(), envPath.slice(1)) : envPath;
|
|
20
|
+
}
|
|
21
|
+
return join(homedir(), '.evm-wallet.json');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getWalletsPath() {
|
|
25
|
+
const base = process.env.EVM_WALLET_PATH
|
|
26
|
+
? (process.env.EVM_WALLET_PATH.startsWith('~')
|
|
27
|
+
? join(homedir(), process.env.EVM_WALLET_PATH.slice(1))
|
|
28
|
+
: process.env.EVM_WALLET_PATH)
|
|
29
|
+
: join(homedir(), '.evm-wallets.json');
|
|
30
|
+
return base.endsWith('.json') && base.includes('evm-wallet')
|
|
31
|
+
? base.replace('.evm-wallet.json', '.evm-wallets.json').replace('.evm-wallet', '.evm-wallets')
|
|
32
|
+
: join(homedir(), '.evm-wallets.json');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate a new wallet
|
|
37
|
+
* @returns {Object} Wallet object with address and private key
|
|
38
|
+
*/
|
|
39
|
+
export function generate() {
|
|
40
|
+
const privateKey = generatePrivateKey();
|
|
41
|
+
const account = privateKeyToAccount(privateKey);
|
|
42
|
+
return {
|
|
43
|
+
address: account.address,
|
|
44
|
+
privateKey,
|
|
45
|
+
createdAt: new Date().toISOString(),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Load default wallet from EVM_PRIVATE_KEY env or state file(s)
|
|
51
|
+
* @returns {Object|null} Wallet object or null if no wallet exists
|
|
52
|
+
*/
|
|
53
|
+
export function load() {
|
|
54
|
+
try {
|
|
55
|
+
const pk = (process.env.EVM_PRIVATE_KEY || '').trim();
|
|
56
|
+
if (pk) {
|
|
57
|
+
const normalizedPk = pk.startsWith('0x') ? pk : `0x${pk}`;
|
|
58
|
+
const account = privateKeyToAccount(normalizedPk);
|
|
59
|
+
return {
|
|
60
|
+
address: account.address,
|
|
61
|
+
privateKey: normalizedPk,
|
|
62
|
+
createdAt: new Date().toISOString(),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const multi = loadAll();
|
|
66
|
+
if (!multi.wallets.length) return null;
|
|
67
|
+
const idx = Math.min(multi.defaultIndex ?? 0, multi.wallets.length - 1);
|
|
68
|
+
return multi.wallets[idx];
|
|
69
|
+
} catch (error) {
|
|
70
|
+
throw new Error(`Failed to load wallet: ${error.message}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @returns {{ wallets: Array<{ address: string, privateKey: string, network?: string, createdAt?: string }>, defaultIndex: number }}
|
|
76
|
+
*/
|
|
77
|
+
export function loadAll() {
|
|
78
|
+
try {
|
|
79
|
+
const pk = (process.env.EVM_PRIVATE_KEY || '').trim();
|
|
80
|
+
if (pk) {
|
|
81
|
+
const normalizedPk = pk.startsWith('0x') ? pk : `0x${pk}`;
|
|
82
|
+
const account = privateKeyToAccount(normalizedPk);
|
|
83
|
+
return {
|
|
84
|
+
wallets: [{ address: account.address, privateKey: normalizedPk, createdAt: new Date().toISOString() }],
|
|
85
|
+
defaultIndex: 0,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const walletsPath = getWalletsPath();
|
|
89
|
+
if (existsSync(walletsPath)) {
|
|
90
|
+
const data = readFileSync(walletsPath, 'utf8');
|
|
91
|
+
const parsed = JSON.parse(data);
|
|
92
|
+
const wallets = Array.isArray(parsed.wallets) ? parsed.wallets : [];
|
|
93
|
+
const defaultIndex = typeof parsed.defaultIndex === 'number' ? parsed.defaultIndex : 0;
|
|
94
|
+
return { wallets, defaultIndex };
|
|
95
|
+
}
|
|
96
|
+
const legacyPath = getWalletPath();
|
|
97
|
+
if (existsSync(legacyPath)) {
|
|
98
|
+
const data = readFileSync(legacyPath, 'utf8');
|
|
99
|
+
const wallet = JSON.parse(data);
|
|
100
|
+
if (wallet.privateKey && wallet.address) {
|
|
101
|
+
const migrated = {
|
|
102
|
+
wallets: [{ ...wallet, createdAt: wallet.createdAt || new Date().toISOString() }],
|
|
103
|
+
defaultIndex: 0,
|
|
104
|
+
};
|
|
105
|
+
saveAll(migrated);
|
|
106
|
+
return migrated;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { wallets: [], defaultIndex: 0 };
|
|
110
|
+
} catch (e) {
|
|
111
|
+
throw new Error(`Failed to load EVM wallets: ${e.message}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {{ wallets: Array, defaultIndex: number }} data
|
|
117
|
+
*/
|
|
118
|
+
export function saveAll(data) {
|
|
119
|
+
try {
|
|
120
|
+
if ((process.env.EVM_PRIVATE_KEY || '').trim()) return;
|
|
121
|
+
const out = {
|
|
122
|
+
wallets: (data.wallets || []).map((w) => ({
|
|
123
|
+
...w,
|
|
124
|
+
createdAt: w.createdAt || new Date().toISOString(),
|
|
125
|
+
})),
|
|
126
|
+
defaultIndex: data.defaultIndex ?? 0,
|
|
127
|
+
};
|
|
128
|
+
const path = getWalletsPath();
|
|
129
|
+
writeFileSync(path, JSON.stringify(out, null, 2), 'utf8');
|
|
130
|
+
chmodSync(path, 0o600);
|
|
131
|
+
} catch (e) {
|
|
132
|
+
throw new Error(`Failed to save EVM wallets: ${e.message}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Save wallet: add or update. When EVM_PRIVATE_KEY is set, no-op.
|
|
138
|
+
* @param {Object} wallet - Wallet object to save
|
|
139
|
+
* @param {{ setDefault?: boolean }} options
|
|
140
|
+
*/
|
|
141
|
+
export function save(wallet, options = {}) {
|
|
142
|
+
if ((process.env.EVM_PRIVATE_KEY || '').trim()) return;
|
|
143
|
+
const data = loadAll();
|
|
144
|
+
const normalized = { ...wallet, createdAt: wallet.createdAt || new Date().toISOString() };
|
|
145
|
+
const idx = data.wallets.findIndex(
|
|
146
|
+
(w) => (w.address || '').toLowerCase() === (wallet.address || '').toLowerCase()
|
|
147
|
+
);
|
|
148
|
+
if (idx >= 0) {
|
|
149
|
+
data.wallets[idx] = normalized;
|
|
150
|
+
} else {
|
|
151
|
+
data.wallets.push(normalized);
|
|
152
|
+
}
|
|
153
|
+
if (options.setDefault !== false && (data.wallets.length === 1 || options.setDefault === true)) {
|
|
154
|
+
const i = data.wallets.findIndex((w) => w.address === wallet.address);
|
|
155
|
+
data.defaultIndex = i >= 0 ? i : data.wallets.length - 1;
|
|
156
|
+
}
|
|
157
|
+
saveAll(data);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get viem account from stored wallet (default or by index/address)
|
|
162
|
+
* @param {number|string} [indexOrAddress]
|
|
163
|
+
*/
|
|
164
|
+
export function getAccount(indexOrAddress) {
|
|
165
|
+
const wallet = indexOrAddress !== undefined ? getWalletAt(indexOrAddress) : load();
|
|
166
|
+
if (!wallet) {
|
|
167
|
+
throw new Error('No wallet found. Run setup.js or create_evm_wallet first.');
|
|
168
|
+
}
|
|
169
|
+
return privateKeyToAccount(wallet.privateKey);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get wallet address (default)
|
|
174
|
+
* @returns {string} Wallet address
|
|
175
|
+
*/
|
|
176
|
+
export function getAddress() {
|
|
177
|
+
const wallet = load();
|
|
178
|
+
if (!wallet) {
|
|
179
|
+
throw new Error('No wallet found. Run setup.js or create_evm_wallet first.');
|
|
180
|
+
}
|
|
181
|
+
return wallet.address;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get wallet by index or by address. Returns default if no arg.
|
|
186
|
+
* @param {number|string} [indexOrAddress]
|
|
187
|
+
* @returns {Object|null}
|
|
188
|
+
*/
|
|
189
|
+
export function getWalletAt(indexOrAddress) {
|
|
190
|
+
const data = loadAll();
|
|
191
|
+
if (data.wallets.length === 0) return null;
|
|
192
|
+
if (typeof indexOrAddress === 'number') {
|
|
193
|
+
return data.wallets[indexOrAddress] ?? null;
|
|
194
|
+
}
|
|
195
|
+
if (typeof indexOrAddress === 'string' && indexOrAddress.trim()) {
|
|
196
|
+
const addr = indexOrAddress.trim().toLowerCase();
|
|
197
|
+
return data.wallets.find((w) => (w.address || '').toLowerCase() === addr) ?? null;
|
|
198
|
+
}
|
|
199
|
+
const idx = Math.min(data.defaultIndex ?? 0, data.wallets.length - 1);
|
|
200
|
+
return data.wallets[idx] ?? null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Create viem wallet client for a specific chain (uses default wallet)
|
|
205
|
+
* @param {string} chainName - Chain name
|
|
206
|
+
* @returns {Object} Viem wallet client
|
|
207
|
+
*/
|
|
208
|
+
export function getWalletClient(chainName) {
|
|
209
|
+
const chain = getChain(chainName);
|
|
210
|
+
const account = getAccount();
|
|
211
|
+
const viemChain = {
|
|
212
|
+
id: chain.chainId,
|
|
213
|
+
name: chain.name,
|
|
214
|
+
nativeCurrency: {
|
|
215
|
+
name: chain.nativeToken.symbol,
|
|
216
|
+
symbol: chain.nativeToken.symbol,
|
|
217
|
+
decimals: chain.nativeToken.decimals,
|
|
218
|
+
},
|
|
219
|
+
rpcUrls: { default: { http: chain.rpcs }, public: { http: chain.rpcs } },
|
|
220
|
+
blockExplorers: { default: { name: chain.explorer.name, url: chain.explorer.url } },
|
|
221
|
+
};
|
|
222
|
+
return createWalletClient({
|
|
223
|
+
account,
|
|
224
|
+
chain: viemChain,
|
|
225
|
+
transport: http(chain.rpcs[0], { retryCount: 3, timeout: 30_000 }),
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Check if wallet exists (EVM_PRIVATE_KEY or wallet file(s))
|
|
231
|
+
* @returns {boolean}
|
|
232
|
+
*/
|
|
233
|
+
export function exists() {
|
|
234
|
+
if ((process.env.EVM_PRIVATE_KEY || '').trim()) return true;
|
|
235
|
+
return existsSync(getWalletsPath()) || existsSync(getWalletPath());
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get default wallet info (safe - no private key)
|
|
240
|
+
* @returns {Object|null}
|
|
241
|
+
*/
|
|
242
|
+
export function getWalletInfo() {
|
|
243
|
+
const wallet = load();
|
|
244
|
+
if (!wallet) return null;
|
|
245
|
+
return { address: wallet.address, network: wallet.network || null, createdAt: wallet.createdAt };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get all wallet infos (no private keys)
|
|
250
|
+
* @returns {Array<{ address: string, network?: string }>}
|
|
251
|
+
*/
|
|
252
|
+
export function getAllWalletInfos() {
|
|
253
|
+
const data = loadAll();
|
|
254
|
+
return data.wallets.map((w) => ({ address: w.address, network: w.network || null }));
|
|
255
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* x402 client: fetch with retry on 402 Payment Required.
|
|
3
|
+
* On 402: parse requirements → build payment (Aptos/EVM) → verify → settle → retry with PAYMENT-SIGNATURE.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getFirstPaymentRequirements, isAptosNetwork, isEvmNetwork } from './types.js';
|
|
7
|
+
|
|
8
|
+
const DEFAULT_MAX_RETRIES = 2;
|
|
9
|
+
const PAYMENT_SIGNATURE_HEADER = 'PAYMENT-SIGNATURE';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Normalize facilitator base URL for /verify and /settle.
|
|
13
|
+
* @param {string} url - Base URL (may end with /facilitator or /)
|
|
14
|
+
* @returns {{ verifyUrl: string, settleUrl: string }}
|
|
15
|
+
*/
|
|
16
|
+
function normalizeFacilitatorUrl(url) {
|
|
17
|
+
const base = url.replace(/\/+$/, '');
|
|
18
|
+
const hasFacilitator = base.endsWith('/facilitator');
|
|
19
|
+
const prefix = hasFacilitator ? base : base;
|
|
20
|
+
return {
|
|
21
|
+
verifyUrl: `${prefix}/verify`,
|
|
22
|
+
settleUrl: `${prefix}/settle`,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Timeout for verify (ms); slightly above server/facilitator 30s */
|
|
27
|
+
const VERIFY_TIMEOUT_MS = 35_000;
|
|
28
|
+
/** Timeout for settle (ms); slightly above server/facilitator 60s */
|
|
29
|
+
const SETTLE_TIMEOUT_MS = 65_000;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Verify payment with facilitator.
|
|
33
|
+
* @param {string} facilitatorUrl - Facilitator base URL
|
|
34
|
+
* @param {Object} paymentPayload - Payment payload from wallet
|
|
35
|
+
* @param {import('./types.js').PaymentRequirements} paymentRequirements - Payment requirements
|
|
36
|
+
* @returns {Promise<Object>} Verification result
|
|
37
|
+
*/
|
|
38
|
+
export async function verifyPayment(facilitatorUrl, paymentPayload, paymentRequirements) {
|
|
39
|
+
const { verifyUrl } = normalizeFacilitatorUrl(facilitatorUrl);
|
|
40
|
+
const controller = new AbortController();
|
|
41
|
+
const timeoutId = setTimeout(() => controller.abort(), VERIFY_TIMEOUT_MS);
|
|
42
|
+
try {
|
|
43
|
+
const res = await fetch(verifyUrl, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: { 'Content-Type': 'application/json' },
|
|
46
|
+
body: JSON.stringify({
|
|
47
|
+
x402Version: 2,
|
|
48
|
+
paymentPayload: {
|
|
49
|
+
x402Version: 2,
|
|
50
|
+
...paymentPayload,
|
|
51
|
+
network: paymentRequirements.network,
|
|
52
|
+
scheme: paymentRequirements.scheme
|
|
53
|
+
},
|
|
54
|
+
paymentRequirements,
|
|
55
|
+
}),
|
|
56
|
+
signal: controller.signal,
|
|
57
|
+
});
|
|
58
|
+
const body = await res.json().catch(() => ({ isValid: false, invalidReason: 'invalid_response' }));
|
|
59
|
+
if (body && body.isValid === false && body.invalidReason) {
|
|
60
|
+
const extra = [body.message, body.detail, body.error].filter(Boolean).join(' ');
|
|
61
|
+
if (extra) {
|
|
62
|
+
body.invalidReason = body.invalidReason + ` (${extra})`;
|
|
63
|
+
console.warn('Facilitator verify failed:', body.invalidReason, body);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return body;
|
|
67
|
+
} catch (e) {
|
|
68
|
+
if (e.name === 'AbortError') {
|
|
69
|
+
return { isValid: false, invalidReason: 'facilitator_timeout: Request timed out' };
|
|
70
|
+
}
|
|
71
|
+
throw e;
|
|
72
|
+
} finally {
|
|
73
|
+
clearTimeout(timeoutId);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Settle payment with facilitator.
|
|
79
|
+
* @param {string} facilitatorUrl - Facilitator base URL
|
|
80
|
+
* @param {Object} paymentPayload - Payment payload from wallet
|
|
81
|
+
* @param {import('./types.js').PaymentRequirements} paymentRequirements - Payment requirements
|
|
82
|
+
* @param {Object} [verification] - Verification result (optional)
|
|
83
|
+
* @returns {Promise<Object>} Settlement result
|
|
84
|
+
*/
|
|
85
|
+
export async function settlePayment(facilitatorUrl, paymentPayload, paymentRequirements, verification = {}) {
|
|
86
|
+
const { settleUrl } = normalizeFacilitatorUrl(facilitatorUrl);
|
|
87
|
+
const controller = new AbortController();
|
|
88
|
+
const timeoutId = setTimeout(() => controller.abort(), SETTLE_TIMEOUT_MS);
|
|
89
|
+
try {
|
|
90
|
+
const res = await fetch(settleUrl, {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
headers: { 'Content-Type': 'application/json' },
|
|
93
|
+
body: JSON.stringify({
|
|
94
|
+
x402Version: 2,
|
|
95
|
+
paymentPayload: {
|
|
96
|
+
x402Version: 2,
|
|
97
|
+
...paymentPayload,
|
|
98
|
+
network: paymentRequirements.network,
|
|
99
|
+
scheme: paymentRequirements.scheme
|
|
100
|
+
},
|
|
101
|
+
paymentRequirements,
|
|
102
|
+
verification,
|
|
103
|
+
}),
|
|
104
|
+
signal: controller.signal,
|
|
105
|
+
});
|
|
106
|
+
return await res.json().catch(() => ({ success: false, errorReason: 'invalid_response' }));
|
|
107
|
+
} catch (e) {
|
|
108
|
+
if (e.name === 'AbortError') {
|
|
109
|
+
return { success: false, errorReason: 'settlement_timeout: Request timed out' };
|
|
110
|
+
}
|
|
111
|
+
throw e;
|
|
112
|
+
} finally {
|
|
113
|
+
clearTimeout(timeoutId);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Fetch with x402 retry: on 402, pay via facilitator then retry with PAYMENT-SIGNATURE.
|
|
119
|
+
* @param {string} url - Request URL
|
|
120
|
+
* @param {RequestInit} [options] - Fetch options (method, headers, body, etc.)
|
|
121
|
+
* @param {Object} context - x402 context
|
|
122
|
+
* @param {string} context.facilitatorUrl - Facilitator base URL (e.g. https://x402-navy.vercel.app/facilitator)
|
|
123
|
+
* @param {(req: import('./types.js').PaymentRequirements) => Promise<Object>} [context.getAptosPaymentPayload]
|
|
124
|
+
* @param {(req: import('./types.js').PaymentRequirements) => Promise<Object>} [context.getEvmPaymentPayload]
|
|
125
|
+
* @param {number} [context.maxRetries]
|
|
126
|
+
* @returns {Promise<Object>} Parsed JSON response body (after retry if 402)
|
|
127
|
+
*/
|
|
128
|
+
export async function fetchWithX402Retry(url, options = {}, context = {}) {
|
|
129
|
+
const maxRetries = context.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
130
|
+
const { verifyUrl, settleUrl } = normalizeFacilitatorUrl(context.facilitatorUrl || '');
|
|
131
|
+
|
|
132
|
+
const doRequest = async (extraHeaders = {}) => {
|
|
133
|
+
const headers = { ...(options.headers || {}), ...extraHeaders };
|
|
134
|
+
const res = await fetch(url, { ...options, headers });
|
|
135
|
+
const contentType = res.headers.get('content-type') || '';
|
|
136
|
+
const isJson = contentType.includes('application/json');
|
|
137
|
+
const body = isJson ? await res.json().catch(() => ({})) : await res.text().catch(() => '');
|
|
138
|
+
|
|
139
|
+
if (res.status !== 402) {
|
|
140
|
+
if (typeof body === 'object') return body;
|
|
141
|
+
return { raw: body, status: res.status };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const requirements = getFirstPaymentRequirements(body);
|
|
145
|
+
if (!requirements) {
|
|
146
|
+
const reason = body?.invalidReason || 'missing_payment_requirements';
|
|
147
|
+
throw new Error(`402: ${reason}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const network = requirements.network || '';
|
|
151
|
+
let paymentPayload;
|
|
152
|
+
if (isAptosNetwork(network) && context.getAptosPaymentPayload) {
|
|
153
|
+
paymentPayload = await context.getAptosPaymentPayload(requirements);
|
|
154
|
+
} else if (isEvmNetwork(network) && context.getEvmPaymentPayload) {
|
|
155
|
+
paymentPayload = await context.getEvmPaymentPayload(requirements);
|
|
156
|
+
} else {
|
|
157
|
+
throw new Error(`402: unsupported network ${network}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const verifyRes = await fetch(verifyUrl, {
|
|
161
|
+
method: 'POST',
|
|
162
|
+
headers: { 'Content-Type': 'application/json' },
|
|
163
|
+
body: JSON.stringify({
|
|
164
|
+
paymentPayload: { ...paymentPayload, network, scheme: requirements.scheme },
|
|
165
|
+
paymentRequirements: requirements,
|
|
166
|
+
}),
|
|
167
|
+
});
|
|
168
|
+
const verifyResult = await verifyRes.json().catch(() => ({}));
|
|
169
|
+
if (!verifyResult?.isValid) {
|
|
170
|
+
throw new Error(`402 verify failed: ${verifyResult?.invalidReason || 'invalid'}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const settleRes = await fetch(settleUrl, {
|
|
174
|
+
method: 'POST',
|
|
175
|
+
headers: { 'Content-Type': 'application/json' },
|
|
176
|
+
body: JSON.stringify({
|
|
177
|
+
paymentPayload: { ...paymentPayload, network, scheme: requirements.scheme },
|
|
178
|
+
paymentRequirements: requirements,
|
|
179
|
+
verification: verifyResult,
|
|
180
|
+
}),
|
|
181
|
+
});
|
|
182
|
+
const settleResult = await settleRes.json().catch(() => ({}));
|
|
183
|
+
if (!settleResult?.success) {
|
|
184
|
+
throw new Error(`402 settle failed: ${settleResult?.errorReason || 'unknown'}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const signatureValue = typeof paymentPayload === 'string'
|
|
188
|
+
? paymentPayload
|
|
189
|
+
: (Buffer.isBuffer(paymentPayload) ? paymentPayload.toString('base64') : JSON.stringify(paymentPayload));
|
|
190
|
+
const retryHeaders = { [PAYMENT_SIGNATURE_HEADER]: signatureValue };
|
|
191
|
+
return doRequest(retryHeaders);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
return doRequest();
|
|
195
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* x402 payment requirement and 402 response types.
|
|
3
|
+
* Used for parsing 402 Payment Required and building payment payloads.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** @typedef {Object} PaymentRequirements
|
|
7
|
+
* @property {string} scheme - e.g. "exact"
|
|
8
|
+
* @property {string} network - e.g. "aptos:2", "eip155:84532"
|
|
9
|
+
* @property {string|number} amount - atomic units
|
|
10
|
+
* @property {string} asset - asset type or contract address
|
|
11
|
+
* @property {string} payTo - recipient address
|
|
12
|
+
* @property {string} [resource] - resource path
|
|
13
|
+
* @property {string} [description] - human-readable description
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** @typedef {Object} Payment402Body
|
|
17
|
+
* @property {PaymentRequirements|PaymentRequirements[]} paymentRequirements
|
|
18
|
+
* @property {string} [invalidReason] - e.g. "insufficient_funds"
|
|
19
|
+
* @property {string} [onramp_url]
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** Network constants: which tool uses which.
|
|
23
|
+
* - aptos:2, aptos:1: prediction, backtest (Aptos testnet/mainnet)
|
|
24
|
+
* - eip155:8453, eip155:84532: open_bank_account (Base, Base Sepolia)
|
|
25
|
+
*/
|
|
26
|
+
export const NETWORKS = {
|
|
27
|
+
APTOS_TESTNET: 'aptos:2',
|
|
28
|
+
APTOS_MAINNET: 'aptos:1',
|
|
29
|
+
BASE: 'eip155:8453',
|
|
30
|
+
BASE_SEPOLIA: 'eip155:84532',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function isAptosNetwork(network) {
|
|
34
|
+
return typeof network === 'string' && network.startsWith('aptos:');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isEvmNetwork(network) {
|
|
38
|
+
return typeof network === 'string' && network.startsWith('eip155:');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Normalize 402 body to a single PaymentRequirements object.
|
|
43
|
+
* @param {Payment402Body} body - Parsed 402 response body
|
|
44
|
+
* @returns {PaymentRequirements|null}
|
|
45
|
+
*/
|
|
46
|
+
export function getFirstPaymentRequirements(body) {
|
|
47
|
+
if (!body || !body.paymentRequirements) return null;
|
|
48
|
+
const pr = body.paymentRequirements;
|
|
49
|
+
return Array.isArray(pr) ? pr[0] ?? null : pr;
|
|
50
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Register an Aptos agent address on-chain so it can receive tokens.
|
|
4
|
+
* On Aptos, an account must exist before it can receive; the first transfer
|
|
5
|
+
* (or a zero/minimal transfer) creates it. Use this when tokens are "sent"
|
|
6
|
+
* (e.g. from the testnet faucet) but don't arrive at the agent address.
|
|
7
|
+
*
|
|
8
|
+
* Requires a sender with APT on the same network (testnet by default).
|
|
9
|
+
* Set REGISTER_SENDER_PRIVATE_KEY in env (hex, with or without 0x).
|
|
10
|
+
* Or omit to use the agent's own wallet (it must already have some APT).
|
|
11
|
+
*
|
|
12
|
+
* Usage: node src/register-aptos-agent.js [agent_address]
|
|
13
|
+
* agent_address: Aptos address to register (default: default agent wallet)
|
|
14
|
+
* Env: REGISTER_SENDER_PRIVATE_KEY (optional), APTOS_NETWORK=testnet|mainnet|devnet
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { getAptosConfig } from './lib/aptos/config.js';
|
|
18
|
+
import { getWalletInfo, load } from './lib/aptos/wallet.js';
|
|
19
|
+
|
|
20
|
+
const MIN_OCTAS = 1; // minimal amount so account is created and receives something
|
|
21
|
+
|
|
22
|
+
function parseArgs() {
|
|
23
|
+
const args = process.argv.slice(2);
|
|
24
|
+
const address = args[0]?.trim();
|
|
25
|
+
return { address };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function main() {
|
|
29
|
+
const { address: argAddress } = parseArgs();
|
|
30
|
+
const network = (process.env.APTOS_NETWORK || 'testnet').toLowerCase();
|
|
31
|
+
const cfg = getAptosConfig(network);
|
|
32
|
+
|
|
33
|
+
let recipientAddress = argAddress;
|
|
34
|
+
if (!recipientAddress) {
|
|
35
|
+
const info = getWalletInfo();
|
|
36
|
+
if (!info?.address) {
|
|
37
|
+
console.error('No agent address provided and no Aptos wallet found. Run: node src/setup-aptos.js');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
recipientAddress = info.address;
|
|
41
|
+
console.log('Using default agent address from wallet:', recipientAddress);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const senderKey = (process.env.REGISTER_SENDER_PRIVATE_KEY || '').trim();
|
|
45
|
+
let senderWallet = null;
|
|
46
|
+
if (senderKey && senderKey.length >= 64) {
|
|
47
|
+
const { Account, Ed25519PrivateKey } = await import('@aptos-labs/ts-sdk');
|
|
48
|
+
const pk = new Ed25519PrivateKey(senderKey.startsWith('0x') ? senderKey : '0x' + senderKey);
|
|
49
|
+
const account = Account.fromPrivateKey({ privateKey: pk, legacy: false });
|
|
50
|
+
senderWallet = { address: account.accountAddress.toString(), privateKey: senderKey.startsWith('0x') ? senderKey : '0x' + senderKey };
|
|
51
|
+
} else {
|
|
52
|
+
const w = load();
|
|
53
|
+
if (!w?.privateKey) {
|
|
54
|
+
console.error('Set REGISTER_SENDER_PRIVATE_KEY (hex key with APT on ' + network + ') or ensure agent wallet has APT.');
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
senderWallet = w;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { Aptos, AptosConfig, Network, Ed25519PrivateKey } = await import('@aptos-labs/ts-sdk');
|
|
61
|
+
const net = network === 'mainnet' ? Network.MAINNET : network === 'devnet' ? Network.DEVNET : Network.TESTNET;
|
|
62
|
+
const aptosConfig = new AptosConfig({ fullnode: cfg.nodeUrl, network: net });
|
|
63
|
+
const aptos = new Aptos(aptosConfig);
|
|
64
|
+
const privateKey = new Ed25519PrivateKey(senderWallet.privateKey);
|
|
65
|
+
const account = Account.fromPrivateKey({ privateKey, legacy: false });
|
|
66
|
+
|
|
67
|
+
console.log('Registering agent address on', network + ':', recipientAddress);
|
|
68
|
+
console.log('Sender:', senderWallet.address);
|
|
69
|
+
try {
|
|
70
|
+
const builder = await aptos.transaction.build.simple({
|
|
71
|
+
sender: account.accountAddress,
|
|
72
|
+
withFeePayer: false,
|
|
73
|
+
data: {
|
|
74
|
+
function: '0x1::aptos_account::transfer',
|
|
75
|
+
functionArguments: [recipientAddress, BigInt(MIN_OCTAS)],
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
const signed = await aptos.transaction.sign({ signer: account, transaction: builder });
|
|
79
|
+
const pending = await aptos.transaction.submit.simple({ transaction: signed });
|
|
80
|
+
await aptos.waitForTransaction({ transactionHash: pending.hash });
|
|
81
|
+
console.log('Done. Agent account is registered. Tx:', pending.hash);
|
|
82
|
+
console.log('You can now fund this address via the faucet or other transfers.');
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.error('Registration failed:', e.message);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
main().catch((e) => {
|
|
90
|
+
console.error(e);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
});
|
package/src/run-agent.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Entrypoint for the autonomous agent (x402 MCP + LangChain.js).
|
|
4
|
+
* Usage: node src/run-agent.js [message]
|
|
5
|
+
* If no message, runs demo prompt.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import 'dotenv/config';
|
|
9
|
+
import { createMcpClient } from './lib/mcp/index.js';
|
|
10
|
+
import { buildAptosPaymentPayload } from './lib/aptos/index.js';
|
|
11
|
+
import { getEvmPaymentPayload } from './lib/evm/index.js';
|
|
12
|
+
import { createAgent } from './agent/index.js';
|
|
13
|
+
|
|
14
|
+
const DEMO_PROMPT =
|
|
15
|
+
'Check my Aptos balance, then run a prediction for AAPL for 30 days and summarize the result.';
|
|
16
|
+
|
|
17
|
+
async function main() {
|
|
18
|
+
const message = process.argv.slice(2).join(' ').trim() || DEMO_PROMPT;
|
|
19
|
+
|
|
20
|
+
const mcpServerUrl = process.env.MCP_SERVER_URL || 'http://localhost:4023';
|
|
21
|
+
const defaultFacilitator = 'https://x402-navy.vercel.app/facilitator';
|
|
22
|
+
let facilitatorUrl = process.env.X402_FACILITATOR_URL || defaultFacilitator;
|
|
23
|
+
if (facilitatorUrl.includes('facilitator.x402.org')) {
|
|
24
|
+
facilitatorUrl = defaultFacilitator;
|
|
25
|
+
}
|
|
26
|
+
const evmFacilitatorUrl = process.env.X402_EVM_FACILITATOR_URL || facilitatorUrl;
|
|
27
|
+
|
|
28
|
+
let getAptosPaymentPayloadFn = null;
|
|
29
|
+
let getEvmPaymentPayloadFn = null;
|
|
30
|
+
try {
|
|
31
|
+
getAptosPaymentPayloadFn = (req) => buildAptosPaymentPayload(req);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
console.warn('Aptos payment not available:', e.message);
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
getEvmPaymentPayloadFn = (req) => getEvmPaymentPayload(req);
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.warn('EVM payment not available:', e.message);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const mcpClient = createMcpClient({
|
|
42
|
+
baseUrl: mcpServerUrl,
|
|
43
|
+
facilitatorUrl,
|
|
44
|
+
evmFacilitatorUrl,
|
|
45
|
+
getAptosPaymentPayload: getAptosPaymentPayloadFn,
|
|
46
|
+
getEvmPaymentPayload: getEvmPaymentPayloadFn,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const { runAgent } = await createAgent({ mcpClient });
|
|
50
|
+
|
|
51
|
+
console.log('Running agent with message:', message);
|
|
52
|
+
const result = await runAgent(message);
|
|
53
|
+
const messages = result?.messages ?? [];
|
|
54
|
+
const last = messages[messages.length - 1];
|
|
55
|
+
if (last?.content) {
|
|
56
|
+
console.log('Agent response:', typeof last.content === 'string' ? last.content : JSON.stringify(last.content));
|
|
57
|
+
} else {
|
|
58
|
+
console.log('Result:', JSON.stringify(result, null, 2));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
main().catch((e) => {
|
|
63
|
+
console.error(e);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
});
|