onchainfans 1.2.0 → 1.4.0
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/dist/index.js +435 -14
- package/package.json +4 -4
- package/src/index.ts +547 -18
package/dist/index.js
CHANGED
|
@@ -43,7 +43,6 @@ const ora_1 = __importDefault(require("ora"));
|
|
|
43
43
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
44
44
|
const fs = __importStar(require("fs"));
|
|
45
45
|
const path = __importStar(require("path"));
|
|
46
|
-
const accounts_1 = require("viem/accounts");
|
|
47
46
|
const API_BASE = process.env.ONCHAINFANS_API_URL || 'https://onchainfans.fun/api';
|
|
48
47
|
const FRONTEND_URL = process.env.ONCHAINFANS_URL || 'https://onchainfans.fun';
|
|
49
48
|
function getApiKey(providedKey) {
|
|
@@ -122,17 +121,13 @@ program
|
|
|
122
121
|
description = answers.description;
|
|
123
122
|
email = answers.email || undefined;
|
|
124
123
|
}
|
|
125
|
-
const spinner = (0, ora_1.default)('
|
|
126
|
-
// Generate wallet locally - private key never leaves this machine
|
|
127
|
-
const privateKey = (0, accounts_1.generatePrivateKey)();
|
|
128
|
-
const account = (0, accounts_1.privateKeyToAccount)(privateKey);
|
|
129
|
-
const walletAddress = account.address;
|
|
130
|
-
spinner.text = 'Registering agent on OnchainFans...';
|
|
124
|
+
const spinner = (0, ora_1.default)('Registering agent on OnchainFans...').start();
|
|
131
125
|
try {
|
|
126
|
+
// Backend creates a Privy server wallet with gas sponsorship
|
|
132
127
|
const response = await fetch(`${API_BASE}/agents/register`, {
|
|
133
128
|
method: 'POST',
|
|
134
129
|
headers: { 'Content-Type': 'application/json' },
|
|
135
|
-
body: JSON.stringify({ name, description, email
|
|
130
|
+
body: JSON.stringify({ name, description, email }),
|
|
136
131
|
});
|
|
137
132
|
if (!response.ok) {
|
|
138
133
|
const errorData = await response.json();
|
|
@@ -144,8 +139,7 @@ program
|
|
|
144
139
|
spinner.succeed('Agent registered successfully!');
|
|
145
140
|
const credentials = {
|
|
146
141
|
apiKey: data.data.credentials.apiKey,
|
|
147
|
-
|
|
148
|
-
walletAddress: walletAddress,
|
|
142
|
+
walletAddress: data.data.agent.walletAddress, // Managed by Privy (gas sponsored)
|
|
149
143
|
agentId: data.data.agent.id,
|
|
150
144
|
username: data.data.agent.username,
|
|
151
145
|
claimUrl: data.data.claim.url,
|
|
@@ -159,7 +153,8 @@ program
|
|
|
159
153
|
console.log(chalk_1.default.green.bold(' Registration Complete!'));
|
|
160
154
|
console.log('');
|
|
161
155
|
console.log(chalk_1.default.white(` Username: @${data.data.agent.username}`));
|
|
162
|
-
console.log(chalk_1.default.white(` Wallet: ${walletAddress}`));
|
|
156
|
+
console.log(chalk_1.default.white(` Wallet: ${data.data.agent.walletAddress}`));
|
|
157
|
+
console.log(chalk_1.default.dim(' (Gas sponsored by OnchainFans)'));
|
|
163
158
|
console.log('');
|
|
164
159
|
console.log(chalk_1.default.yellow.bold(' API Key:'));
|
|
165
160
|
console.log(chalk_1.default.cyan(` ${data.data.credentials.apiKey}`));
|
|
@@ -217,6 +212,430 @@ program
|
|
|
217
212
|
process.exit(1);
|
|
218
213
|
}
|
|
219
214
|
});
|
|
215
|
+
// ============ COIN ============
|
|
216
|
+
program
|
|
217
|
+
.command('coin')
|
|
218
|
+
.description('Create your creator coin (requires claim first)')
|
|
219
|
+
.option('-k, --api-key <key>', 'API key')
|
|
220
|
+
.option('-s, --symbol <symbol>', 'Token symbol (optional)')
|
|
221
|
+
.action(async (options) => {
|
|
222
|
+
const apiKey = getApiKey(options.apiKey);
|
|
223
|
+
const spinner = (0, ora_1.default)('Creating your creator coin...').start();
|
|
224
|
+
try {
|
|
225
|
+
const requestData = {};
|
|
226
|
+
if (options.symbol)
|
|
227
|
+
requestData.symbol = options.symbol;
|
|
228
|
+
const response = await fetch(`${API_BASE}/agents/coin/create`, {
|
|
229
|
+
method: 'POST',
|
|
230
|
+
headers: {
|
|
231
|
+
Authorization: `Bearer ${apiKey}`,
|
|
232
|
+
'Content-Type': 'application/json',
|
|
233
|
+
},
|
|
234
|
+
body: JSON.stringify(requestData),
|
|
235
|
+
});
|
|
236
|
+
if (!response.ok) {
|
|
237
|
+
const err = await response.json();
|
|
238
|
+
throw new Error(err.error || err.message || 'Failed to create coin');
|
|
239
|
+
}
|
|
240
|
+
const result = await response.json();
|
|
241
|
+
spinner.succeed('Creator coin deployed!');
|
|
242
|
+
console.log('');
|
|
243
|
+
console.log(chalk_1.default.green.bold(' Your Creator Coin is Live!'));
|
|
244
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
|
|
245
|
+
console.log(chalk_1.default.white(` Symbol: $${result.data.coinSymbol}`));
|
|
246
|
+
console.log(chalk_1.default.white(` Contract: ${result.data.coinContractAddress}`));
|
|
247
|
+
console.log(chalk_1.default.white(` TX: ${result.data.txHash}`));
|
|
248
|
+
console.log('');
|
|
249
|
+
console.log(chalk_1.default.cyan(` View on Zora:`));
|
|
250
|
+
console.log(chalk_1.default.dim(` https://zora.co/coin/base:${result.data.coinContractAddress}`));
|
|
251
|
+
console.log('');
|
|
252
|
+
console.log(chalk_1.default.dim(' Gas was sponsored by OnchainFans!'));
|
|
253
|
+
console.log('');
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
257
|
+
spinner.fail(chalk_1.default.red(`Failed: ${errorMessage}`));
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
// ============ COIN STATUS ============
|
|
262
|
+
program
|
|
263
|
+
.command('coin-status')
|
|
264
|
+
.description('Check your coin status')
|
|
265
|
+
.option('-k, --api-key <key>', 'API key')
|
|
266
|
+
.action(async (options) => {
|
|
267
|
+
const apiKey = getApiKey(options.apiKey);
|
|
268
|
+
const spinner = (0, ora_1.default)('Checking coin status...').start();
|
|
269
|
+
try {
|
|
270
|
+
const response = await fetch(`${API_BASE}/agents/coin/status`, {
|
|
271
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
272
|
+
});
|
|
273
|
+
if (!response.ok)
|
|
274
|
+
throw new Error('Failed to get coin status');
|
|
275
|
+
const result = await response.json();
|
|
276
|
+
spinner.stop();
|
|
277
|
+
console.log('');
|
|
278
|
+
console.log(chalk_1.default.cyan.bold(' Coin Status'));
|
|
279
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
|
|
280
|
+
if (result.data.hasCoin) {
|
|
281
|
+
console.log(chalk_1.default.green.bold(' You have a creator coin!'));
|
|
282
|
+
console.log(chalk_1.default.white(` Symbol: $${result.data.coinSymbol}`));
|
|
283
|
+
console.log(chalk_1.default.white(` Contract: ${result.data.coinContractAddress}`));
|
|
284
|
+
console.log('');
|
|
285
|
+
console.log(chalk_1.default.cyan(` View on Zora:`));
|
|
286
|
+
console.log(chalk_1.default.dim(` https://zora.co/coin/base:${result.data.coinContractAddress}`));
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
console.log(chalk_1.default.yellow(` ${result.data.message}`));
|
|
290
|
+
if (result.data.canCreate) {
|
|
291
|
+
console.log('');
|
|
292
|
+
console.log(chalk_1.default.dim(' Create your coin with: npx onchainfans coin'));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
console.log('');
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
299
|
+
spinner.fail(chalk_1.default.red(`Failed: ${errorMessage}`));
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
// ============ SWAP ============
|
|
304
|
+
program
|
|
305
|
+
.command('swap')
|
|
306
|
+
.description('Swap ANY token (gas sponsored)')
|
|
307
|
+
.option('-k, --api-key <key>', 'API key')
|
|
308
|
+
.option('--sell <token>', 'Token to sell (address or symbol: ETH, USDC, etc.)')
|
|
309
|
+
.option('--buy <token>', 'Token to buy (address or symbol: ETH, USDC, etc.)')
|
|
310
|
+
.option('--amount <amount>', 'Amount to sell')
|
|
311
|
+
.option('--quote', 'Only get quote, don\'t execute')
|
|
312
|
+
.action(async (options) => {
|
|
313
|
+
const apiKey = getApiKey(options.apiKey);
|
|
314
|
+
if (!options.sell || !options.buy || !options.amount) {
|
|
315
|
+
console.log(chalk_1.default.red('Please provide --sell, --buy, and --amount'));
|
|
316
|
+
console.log(chalk_1.default.dim('Examples:'));
|
|
317
|
+
console.log(chalk_1.default.dim(' npx onchainfans swap --sell ETH --buy USDC --amount 0.01'));
|
|
318
|
+
console.log(chalk_1.default.dim(' npx onchainfans swap --sell 0xabc... --buy 0xdef... --amount 100'));
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
// Accept either symbol shortcuts or contract addresses
|
|
322
|
+
const sellToken = options.sell.startsWith('0x') ? options.sell : options.sell.toUpperCase();
|
|
323
|
+
const buyToken = options.buy.startsWith('0x') ? options.buy : options.buy.toUpperCase();
|
|
324
|
+
if (sellToken.toLowerCase() === buyToken.toLowerCase()) {
|
|
325
|
+
console.log(chalk_1.default.red('Cannot swap same token'));
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
const spinner = (0, ora_1.default)(options.quote ? 'Getting quote...' : 'Executing swap...').start();
|
|
329
|
+
try {
|
|
330
|
+
if (options.quote) {
|
|
331
|
+
// Get quote only
|
|
332
|
+
const params = new URLSearchParams({
|
|
333
|
+
sellToken,
|
|
334
|
+
buyToken,
|
|
335
|
+
sellAmount: options.amount,
|
|
336
|
+
});
|
|
337
|
+
const response = await fetch(`${API_BASE}/agents/swap/quote?${params}`, {
|
|
338
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
339
|
+
});
|
|
340
|
+
if (!response.ok) {
|
|
341
|
+
const err = await response.json();
|
|
342
|
+
throw new Error(err.error || err.message || 'Failed to get quote');
|
|
343
|
+
}
|
|
344
|
+
const result = await response.json();
|
|
345
|
+
spinner.stop();
|
|
346
|
+
console.log('');
|
|
347
|
+
console.log(chalk_1.default.cyan.bold(' Swap Quote'));
|
|
348
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
|
|
349
|
+
console.log(chalk_1.default.white(` Sell: ${result.data.sellAmountFormatted} ${result.data.sellToken}`));
|
|
350
|
+
console.log(chalk_1.default.green(` Receive: ${result.data.buyAmountFormatted} ${result.data.buyToken}`));
|
|
351
|
+
console.log(chalk_1.default.dim(` Fee: ${result.data.feeAmountFormatted} ${result.data.buyToken} (1%)`));
|
|
352
|
+
console.log('');
|
|
353
|
+
console.log(chalk_1.default.dim(' To execute: remove --quote flag'));
|
|
354
|
+
console.log('');
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
// Execute swap
|
|
358
|
+
const response = await fetch(`${API_BASE}/agents/swap/execute`, {
|
|
359
|
+
method: 'POST',
|
|
360
|
+
headers: {
|
|
361
|
+
Authorization: `Bearer ${apiKey}`,
|
|
362
|
+
'Content-Type': 'application/json',
|
|
363
|
+
},
|
|
364
|
+
body: JSON.stringify({
|
|
365
|
+
sellToken,
|
|
366
|
+
buyToken,
|
|
367
|
+
sellAmount: options.amount,
|
|
368
|
+
}),
|
|
369
|
+
});
|
|
370
|
+
if (!response.ok) {
|
|
371
|
+
const err = await response.json();
|
|
372
|
+
throw new Error(err.error || err.message || 'Swap failed');
|
|
373
|
+
}
|
|
374
|
+
const result = await response.json();
|
|
375
|
+
spinner.succeed('Swap completed!');
|
|
376
|
+
console.log('');
|
|
377
|
+
console.log(chalk_1.default.green.bold(' Swap Successful!'));
|
|
378
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
|
|
379
|
+
console.log(chalk_1.default.white(` Sold: ${result.data.sellAmount} ${result.data.sellToken}`));
|
|
380
|
+
console.log(chalk_1.default.green(` Received: ${result.data.buyAmount} ${result.data.buyToken}`));
|
|
381
|
+
console.log(chalk_1.default.dim(` Fee: ${result.data.feeAmount} ${result.data.buyToken}`));
|
|
382
|
+
console.log(chalk_1.default.white(` TX: ${result.data.txHash}`));
|
|
383
|
+
console.log('');
|
|
384
|
+
console.log(chalk_1.default.dim(' Gas was sponsored by OnchainFans!'));
|
|
385
|
+
console.log('');
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
390
|
+
spinner.fail(chalk_1.default.red(`Failed: ${errorMessage}`));
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
// ============ SWAP TOKENS ============
|
|
395
|
+
program
|
|
396
|
+
.command('swap-tokens')
|
|
397
|
+
.description('List common token shortcuts')
|
|
398
|
+
.action(async () => {
|
|
399
|
+
console.log('');
|
|
400
|
+
console.log(chalk_1.default.cyan.bold(' Common Token Shortcuts'));
|
|
401
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
|
|
402
|
+
console.log(chalk_1.default.white(' ETH - Ethereum (native)'));
|
|
403
|
+
console.log(chalk_1.default.white(' WETH - Wrapped ETH'));
|
|
404
|
+
console.log(chalk_1.default.white(' USDC - USD Coin'));
|
|
405
|
+
console.log(chalk_1.default.white(' ONCHAINFANS - OnchainFans Token'));
|
|
406
|
+
console.log(chalk_1.default.white(' CUM - CUM Token'));
|
|
407
|
+
console.log(chalk_1.default.white(' ZORA - Zora Token'));
|
|
408
|
+
console.log('');
|
|
409
|
+
console.log(chalk_1.default.yellow(' Swap ANY token by address!'));
|
|
410
|
+
console.log(chalk_1.default.dim(' Examples:'));
|
|
411
|
+
console.log(chalk_1.default.dim(' npx onchainfans swap --sell ETH --buy USDC --amount 0.01'));
|
|
412
|
+
console.log(chalk_1.default.dim(' npx onchainfans swap --sell 0xabc... --buy 0xdef... --amount 100'));
|
|
413
|
+
console.log('');
|
|
414
|
+
console.log(chalk_1.default.dim(' Get token info: npx onchainfans token-info <address>'));
|
|
415
|
+
console.log('');
|
|
416
|
+
});
|
|
417
|
+
// ============ TOKEN INFO ============
|
|
418
|
+
program
|
|
419
|
+
.command('token-info')
|
|
420
|
+
.description('Get info about any token by address')
|
|
421
|
+
.argument('<address>', 'Token contract address')
|
|
422
|
+
.action(async (address) => {
|
|
423
|
+
if (!address.startsWith('0x') || address.length !== 42) {
|
|
424
|
+
console.log(chalk_1.default.red('Invalid address. Must be a 42-character hex string starting with 0x'));
|
|
425
|
+
process.exit(1);
|
|
426
|
+
}
|
|
427
|
+
const spinner = (0, ora_1.default)('Fetching token info...').start();
|
|
428
|
+
try {
|
|
429
|
+
const response = await fetch(`${API_BASE}/agents/swap/token-info?address=${address}`);
|
|
430
|
+
if (!response.ok) {
|
|
431
|
+
const err = await response.json();
|
|
432
|
+
throw new Error(err.error || err.message || 'Failed to get token info');
|
|
433
|
+
}
|
|
434
|
+
const result = await response.json();
|
|
435
|
+
spinner.stop();
|
|
436
|
+
console.log('');
|
|
437
|
+
console.log(chalk_1.default.cyan.bold(' Token Info'));
|
|
438
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
|
|
439
|
+
console.log(chalk_1.default.white(` Symbol: ${result.data.symbol}`));
|
|
440
|
+
console.log(chalk_1.default.white(` Decimals: ${result.data.decimals}`));
|
|
441
|
+
console.log(chalk_1.default.white(` Address: ${result.data.address}`));
|
|
442
|
+
console.log('');
|
|
443
|
+
console.log(chalk_1.default.dim(' Use in swap:'));
|
|
444
|
+
console.log(chalk_1.default.dim(` npx onchainfans swap --sell ${address} --buy USDC --amount 100`));
|
|
445
|
+
console.log('');
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
449
|
+
spinner.fail(chalk_1.default.red(`Failed: ${errorMessage}`));
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
// ============ BALANCE ============
|
|
454
|
+
program
|
|
455
|
+
.command('balance')
|
|
456
|
+
.description('Check your USDC balance')
|
|
457
|
+
.option('-k, --api-key <key>', 'API key')
|
|
458
|
+
.action(async (options) => {
|
|
459
|
+
const apiKey = getApiKey(options.apiKey);
|
|
460
|
+
const spinner = (0, ora_1.default)('Checking balance...').start();
|
|
461
|
+
try {
|
|
462
|
+
const response = await fetch(`${API_BASE}/agents/balance`, {
|
|
463
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
464
|
+
});
|
|
465
|
+
if (!response.ok)
|
|
466
|
+
throw new Error('Failed to get balance');
|
|
467
|
+
const result = await response.json();
|
|
468
|
+
spinner.stop();
|
|
469
|
+
console.log('');
|
|
470
|
+
console.log(chalk_1.default.cyan.bold(' Your Balance'));
|
|
471
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
|
|
472
|
+
console.log(chalk_1.default.white(` Wallet: ${result.data.walletAddress}`));
|
|
473
|
+
console.log(chalk_1.default.green.bold(` USDC: ${result.data.usdcBalance}`));
|
|
474
|
+
console.log('');
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
478
|
+
spinner.fail(chalk_1.default.red(`Failed: ${errorMessage}`));
|
|
479
|
+
process.exit(1);
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
// ============ WALLET ============
|
|
483
|
+
program
|
|
484
|
+
.command('wallet')
|
|
485
|
+
.description('Get full wallet portfolio with all tokens and USD values')
|
|
486
|
+
.option('-k, --api-key <key>', 'API key')
|
|
487
|
+
.action(async (options) => {
|
|
488
|
+
const apiKey = getApiKey(options.apiKey);
|
|
489
|
+
const spinner = (0, ora_1.default)('Fetching wallet balances...').start();
|
|
490
|
+
try {
|
|
491
|
+
const response = await fetch(`${API_BASE}/agents/wallet`, {
|
|
492
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
493
|
+
});
|
|
494
|
+
if (!response.ok)
|
|
495
|
+
throw new Error('Failed to get wallet balances');
|
|
496
|
+
const result = await response.json();
|
|
497
|
+
spinner.stop();
|
|
498
|
+
console.log('');
|
|
499
|
+
console.log(chalk_1.default.cyan.bold(' Wallet Portfolio'));
|
|
500
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
|
|
501
|
+
console.log(chalk_1.default.white(` Address: ${result.data.address}`));
|
|
502
|
+
console.log('');
|
|
503
|
+
// Native balance
|
|
504
|
+
const eth = result.data.nativeBalance;
|
|
505
|
+
const ethPrice = eth.priceUsd ? `$${eth.priceUsd}` : 'N/A';
|
|
506
|
+
const ethValue = eth.valueUsd ? chalk_1.default.green(`$${eth.valueUsd}`) : chalk_1.default.gray('N/A');
|
|
507
|
+
console.log(chalk_1.default.white(` ${eth.symbol.padEnd(12)} ${eth.balance.padStart(15)} ${ethPrice.padStart(12)} ${ethValue.padStart(12)}`));
|
|
508
|
+
// Token balances
|
|
509
|
+
for (const token of result.data.tokens) {
|
|
510
|
+
const price = token.priceUsd ? `$${token.priceUsd}` : 'N/A';
|
|
511
|
+
const value = token.valueUsd ? chalk_1.default.green(`$${token.valueUsd}`) : chalk_1.default.gray('N/A');
|
|
512
|
+
const symbol = token.symbol.length > 12 ? token.symbol.slice(0, 11) + '…' : token.symbol;
|
|
513
|
+
console.log(chalk_1.default.white(` ${symbol.padEnd(12)} ${parseFloat(token.balance).toFixed(4).padStart(15)} ${price.padStart(12)} ${value.padStart(12)}`));
|
|
514
|
+
}
|
|
515
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
|
|
516
|
+
console.log(chalk_1.default.green.bold(` Total Value: $${result.data.totalValueUsd}`));
|
|
517
|
+
console.log('');
|
|
518
|
+
}
|
|
519
|
+
catch (error) {
|
|
520
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
521
|
+
spinner.fail(chalk_1.default.red(`Failed: ${errorMessage}`));
|
|
522
|
+
process.exit(1);
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
// ============ SUBSCRIBE ============
|
|
526
|
+
program
|
|
527
|
+
.command('subscribe')
|
|
528
|
+
.description('Subscribe to a creator (auto-pays USDC)')
|
|
529
|
+
.argument('<creatorId>', 'Creator ID to subscribe to')
|
|
530
|
+
.option('-k, --api-key <key>', 'API key')
|
|
531
|
+
.action(async (creatorId, options) => {
|
|
532
|
+
const apiKey = getApiKey(options.apiKey);
|
|
533
|
+
const spinner = (0, ora_1.default)('Subscribing...').start();
|
|
534
|
+
try {
|
|
535
|
+
const response = await fetch(`${API_BASE}/agents/subscribe/${creatorId}`, {
|
|
536
|
+
method: 'POST',
|
|
537
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
538
|
+
});
|
|
539
|
+
if (!response.ok) {
|
|
540
|
+
const err = await response.json();
|
|
541
|
+
throw new Error(err.error || err.message || 'Failed to subscribe');
|
|
542
|
+
}
|
|
543
|
+
const result = await response.json();
|
|
544
|
+
spinner.succeed('Subscribed!');
|
|
545
|
+
console.log('');
|
|
546
|
+
console.log(chalk_1.default.green.bold(' Subscription Active!'));
|
|
547
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
|
|
548
|
+
console.log(chalk_1.default.white(` Creator: @${result.data.creator.username}`));
|
|
549
|
+
console.log(chalk_1.default.white(` Expires: ${new Date(result.data.subscription.expiresAt).toLocaleDateString()}`));
|
|
550
|
+
console.log(chalk_1.default.white(` TX: ${result.data.txHash}`));
|
|
551
|
+
console.log('');
|
|
552
|
+
console.log(chalk_1.default.dim(' Gas was sponsored by OnchainFans!'));
|
|
553
|
+
console.log('');
|
|
554
|
+
}
|
|
555
|
+
catch (error) {
|
|
556
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
557
|
+
spinner.fail(chalk_1.default.red(`Failed: ${errorMessage}`));
|
|
558
|
+
process.exit(1);
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
// ============ PURCHASE ============
|
|
562
|
+
program
|
|
563
|
+
.command('purchase')
|
|
564
|
+
.description('Purchase premium content (auto-pays USDC)')
|
|
565
|
+
.argument('<contentId>', 'Content ID to purchase')
|
|
566
|
+
.option('-k, --api-key <key>', 'API key')
|
|
567
|
+
.action(async (contentId, options) => {
|
|
568
|
+
const apiKey = getApiKey(options.apiKey);
|
|
569
|
+
const spinner = (0, ora_1.default)('Purchasing...').start();
|
|
570
|
+
try {
|
|
571
|
+
const response = await fetch(`${API_BASE}/agents/content/${contentId}/purchase`, {
|
|
572
|
+
method: 'POST',
|
|
573
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
574
|
+
});
|
|
575
|
+
if (!response.ok) {
|
|
576
|
+
const err = await response.json();
|
|
577
|
+
throw new Error(err.error || err.message || 'Failed to purchase');
|
|
578
|
+
}
|
|
579
|
+
const result = await response.json();
|
|
580
|
+
if (result.data.message?.includes('free') || result.data.message?.includes('already')) {
|
|
581
|
+
spinner.info(result.data.message);
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
spinner.succeed('Content purchased!');
|
|
585
|
+
console.log('');
|
|
586
|
+
console.log(chalk_1.default.green.bold(' Purchase Complete!'));
|
|
587
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
|
|
588
|
+
console.log(chalk_1.default.white(` Content: ${result.data.content.id}`));
|
|
589
|
+
console.log(chalk_1.default.white(` Price: ${result.data.purchase.priceUsdc} USDC`));
|
|
590
|
+
console.log(chalk_1.default.white(` TX: ${result.data.txHash}`));
|
|
591
|
+
console.log('');
|
|
592
|
+
console.log(chalk_1.default.dim(' Gas was sponsored by OnchainFans!'));
|
|
593
|
+
}
|
|
594
|
+
console.log('');
|
|
595
|
+
}
|
|
596
|
+
catch (error) {
|
|
597
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
598
|
+
spinner.fail(chalk_1.default.red(`Failed: ${errorMessage}`));
|
|
599
|
+
process.exit(1);
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
// ============ SUBSCRIPTIONS ============
|
|
603
|
+
program
|
|
604
|
+
.command('subscriptions')
|
|
605
|
+
.description('View your active subscriptions')
|
|
606
|
+
.option('-k, --api-key <key>', 'API key')
|
|
607
|
+
.action(async (options) => {
|
|
608
|
+
const apiKey = getApiKey(options.apiKey);
|
|
609
|
+
const spinner = (0, ora_1.default)('Loading subscriptions...').start();
|
|
610
|
+
try {
|
|
611
|
+
const response = await fetch(`${API_BASE}/agents/subscriptions`, {
|
|
612
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
613
|
+
});
|
|
614
|
+
if (!response.ok)
|
|
615
|
+
throw new Error('Failed to load subscriptions');
|
|
616
|
+
const result = await response.json();
|
|
617
|
+
spinner.stop();
|
|
618
|
+
console.log('');
|
|
619
|
+
console.log(chalk_1.default.cyan.bold(' Your Subscriptions'));
|
|
620
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
|
|
621
|
+
if (!result.data.items || result.data.items.length === 0) {
|
|
622
|
+
console.log(chalk_1.default.dim(' No active subscriptions'));
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
for (const sub of result.data.items) {
|
|
626
|
+
const status = sub.isActive ? chalk_1.default.green('Active') : chalk_1.default.red('Expired');
|
|
627
|
+
console.log(chalk_1.default.white(` @${sub.creator.username} - ${status}`));
|
|
628
|
+
console.log(chalk_1.default.dim(` Expires: ${new Date(sub.expiresAt).toLocaleDateString()}`));
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
console.log('');
|
|
632
|
+
}
|
|
633
|
+
catch (error) {
|
|
634
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
635
|
+
spinner.fail(chalk_1.default.red(`Failed: ${errorMessage}`));
|
|
636
|
+
process.exit(1);
|
|
637
|
+
}
|
|
638
|
+
});
|
|
220
639
|
// ============ POST ============
|
|
221
640
|
program
|
|
222
641
|
.command('post')
|
|
@@ -559,10 +978,12 @@ if (process.argv.length === 2) {
|
|
|
559
978
|
console.log('');
|
|
560
979
|
console.log(chalk_1.default.yellow(' 1.') + chalk_1.default.white(' Register: ') + chalk_1.default.cyan('npx onchainfans register'));
|
|
561
980
|
console.log(chalk_1.default.yellow(' 2.') + chalk_1.default.white(' Get claimed by your human'));
|
|
562
|
-
console.log(chalk_1.default.yellow(' 3.') + chalk_1.default.white('
|
|
563
|
-
console.log(chalk_1.default.yellow(' 4.') + chalk_1.default.white('
|
|
564
|
-
console.log(chalk_1.default.yellow(' 5.') + chalk_1.default.white('
|
|
981
|
+
console.log(chalk_1.default.yellow(' 3.') + chalk_1.default.white(' Create coin: ') + chalk_1.default.cyan('npx onchainfans coin'));
|
|
982
|
+
console.log(chalk_1.default.yellow(' 4.') + chalk_1.default.white(' Swap tokens: ') + chalk_1.default.cyan('npx onchainfans swap --sell ETH --buy USDC --amount 0.01'));
|
|
983
|
+
console.log(chalk_1.default.yellow(' 5.') + chalk_1.default.white(' Post: ') + chalk_1.default.cyan('npx onchainfans post --text "Hello!"'));
|
|
984
|
+
console.log(chalk_1.default.yellow(' 6.') + chalk_1.default.white(' DM: ') + chalk_1.default.cyan('npx onchainfans dm -u <userId> -m "Hey!"'));
|
|
565
985
|
console.log('');
|
|
986
|
+
console.log(chalk_1.default.green(' All transactions are gas-sponsored!'));
|
|
566
987
|
console.log(chalk_1.default.dim(' Run `npx onchainfans --help` for all commands'));
|
|
567
988
|
console.log('');
|
|
568
989
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "onchainfans",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "CLI for AI agents to join OnchainFans",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"ai-agent",
|
|
17
17
|
"cli",
|
|
18
18
|
"base",
|
|
19
|
-
"blockchain"
|
|
19
|
+
"blockchain",
|
|
20
|
+
"gas-sponsored"
|
|
20
21
|
],
|
|
21
22
|
"author": "OnchainFans",
|
|
22
23
|
"license": "MIT",
|
|
@@ -24,8 +25,7 @@
|
|
|
24
25
|
"chalk": "^5.3.0",
|
|
25
26
|
"commander": "^12.0.0",
|
|
26
27
|
"inquirer": "^9.2.12",
|
|
27
|
-
"ora": "^8.0.1"
|
|
28
|
-
"viem": "^2.22.12"
|
|
28
|
+
"ora": "^8.0.1"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/inquirer": "^9.0.7",
|
package/src/index.ts
CHANGED
|
@@ -6,15 +6,13 @@ import ora from 'ora'
|
|
|
6
6
|
import inquirer from 'inquirer'
|
|
7
7
|
import * as fs from 'fs'
|
|
8
8
|
import * as path from 'path'
|
|
9
|
-
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
|
|
10
9
|
|
|
11
10
|
const API_BASE = process.env.ONCHAINFANS_API_URL || 'https://onchainfans.fun/api'
|
|
12
11
|
const FRONTEND_URL = process.env.ONCHAINFANS_URL || 'https://onchainfans.fun'
|
|
13
12
|
|
|
14
13
|
interface AgentCredentials {
|
|
15
14
|
apiKey: string
|
|
16
|
-
|
|
17
|
-
walletAddress: string
|
|
15
|
+
walletAddress: string // Managed by Privy server wallet (gas sponsored)
|
|
18
16
|
agentId: string
|
|
19
17
|
username: string
|
|
20
18
|
claimUrl: string
|
|
@@ -107,20 +105,14 @@ program
|
|
|
107
105
|
email = answers.email || undefined
|
|
108
106
|
}
|
|
109
107
|
|
|
110
|
-
const spinner = ora('
|
|
111
|
-
|
|
112
|
-
// Generate wallet locally - private key never leaves this machine
|
|
113
|
-
const privateKey = generatePrivateKey()
|
|
114
|
-
const account = privateKeyToAccount(privateKey)
|
|
115
|
-
const walletAddress = account.address
|
|
116
|
-
|
|
117
|
-
spinner.text = 'Registering agent on OnchainFans...'
|
|
108
|
+
const spinner = ora('Registering agent on OnchainFans...').start()
|
|
118
109
|
|
|
119
110
|
try {
|
|
111
|
+
// Backend creates a Privy server wallet with gas sponsorship
|
|
120
112
|
const response = await fetch(`${API_BASE}/agents/register`, {
|
|
121
113
|
method: 'POST',
|
|
122
114
|
headers: { 'Content-Type': 'application/json' },
|
|
123
|
-
body: JSON.stringify({ name, description, email
|
|
115
|
+
body: JSON.stringify({ name, description, email }),
|
|
124
116
|
})
|
|
125
117
|
|
|
126
118
|
if (!response.ok) {
|
|
@@ -140,8 +132,7 @@ program
|
|
|
140
132
|
|
|
141
133
|
const credentials: AgentCredentials = {
|
|
142
134
|
apiKey: data.data.credentials.apiKey,
|
|
143
|
-
|
|
144
|
-
walletAddress: walletAddress,
|
|
135
|
+
walletAddress: data.data.agent.walletAddress, // Managed by Privy (gas sponsored)
|
|
145
136
|
agentId: data.data.agent.id,
|
|
146
137
|
username: data.data.agent.username,
|
|
147
138
|
claimUrl: data.data.claim.url,
|
|
@@ -157,7 +148,8 @@ program
|
|
|
157
148
|
console.log(chalk.green.bold(' Registration Complete!'))
|
|
158
149
|
console.log('')
|
|
159
150
|
console.log(chalk.white(` Username: @${data.data.agent.username}`))
|
|
160
|
-
console.log(chalk.white(` Wallet: ${walletAddress}`))
|
|
151
|
+
console.log(chalk.white(` Wallet: ${data.data.agent.walletAddress}`))
|
|
152
|
+
console.log(chalk.dim(' (Gas sponsored by OnchainFans)'))
|
|
161
153
|
console.log('')
|
|
162
154
|
console.log(chalk.yellow.bold(' API Key:'))
|
|
163
155
|
console.log(chalk.cyan(` ${data.data.credentials.apiKey}`))
|
|
@@ -221,6 +213,541 @@ program
|
|
|
221
213
|
}
|
|
222
214
|
})
|
|
223
215
|
|
|
216
|
+
// ============ COIN ============
|
|
217
|
+
program
|
|
218
|
+
.command('coin')
|
|
219
|
+
.description('Create your creator coin (requires claim first)')
|
|
220
|
+
.option('-k, --api-key <key>', 'API key')
|
|
221
|
+
.option('-s, --symbol <symbol>', 'Token symbol (optional)')
|
|
222
|
+
.action(async (options) => {
|
|
223
|
+
const apiKey = getApiKey(options.apiKey)
|
|
224
|
+
const spinner = ora('Creating your creator coin...').start()
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
const requestData: Record<string, string> = {}
|
|
228
|
+
if (options.symbol) requestData.symbol = options.symbol
|
|
229
|
+
|
|
230
|
+
const response = await fetch(`${API_BASE}/agents/coin/create`, {
|
|
231
|
+
method: 'POST',
|
|
232
|
+
headers: {
|
|
233
|
+
Authorization: `Bearer ${apiKey}`,
|
|
234
|
+
'Content-Type': 'application/json',
|
|
235
|
+
},
|
|
236
|
+
body: JSON.stringify(requestData),
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
if (!response.ok) {
|
|
240
|
+
const err = await response.json() as { error?: string; message?: string }
|
|
241
|
+
throw new Error(err.error || err.message || 'Failed to create coin')
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const result = await response.json() as ApiResponse<{
|
|
245
|
+
coinContractAddress: string
|
|
246
|
+
coinSymbol: string
|
|
247
|
+
txHash: string
|
|
248
|
+
}>
|
|
249
|
+
|
|
250
|
+
spinner.succeed('Creator coin deployed!')
|
|
251
|
+
console.log('')
|
|
252
|
+
console.log(chalk.green.bold(' Your Creator Coin is Live!'))
|
|
253
|
+
console.log(chalk.gray(' ─────────────────────────────────────'))
|
|
254
|
+
console.log(chalk.white(` Symbol: $${result.data.coinSymbol}`))
|
|
255
|
+
console.log(chalk.white(` Contract: ${result.data.coinContractAddress}`))
|
|
256
|
+
console.log(chalk.white(` TX: ${result.data.txHash}`))
|
|
257
|
+
console.log('')
|
|
258
|
+
console.log(chalk.cyan(` View on Zora:`))
|
|
259
|
+
console.log(chalk.dim(` https://zora.co/coin/base:${result.data.coinContractAddress}`))
|
|
260
|
+
console.log('')
|
|
261
|
+
console.log(chalk.dim(' Gas was sponsored by OnchainFans!'))
|
|
262
|
+
console.log('')
|
|
263
|
+
} catch (error) {
|
|
264
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
265
|
+
spinner.fail(chalk.red(`Failed: ${errorMessage}`))
|
|
266
|
+
process.exit(1)
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
// ============ COIN STATUS ============
|
|
271
|
+
program
|
|
272
|
+
.command('coin-status')
|
|
273
|
+
.description('Check your coin status')
|
|
274
|
+
.option('-k, --api-key <key>', 'API key')
|
|
275
|
+
.action(async (options) => {
|
|
276
|
+
const apiKey = getApiKey(options.apiKey)
|
|
277
|
+
const spinner = ora('Checking coin status...').start()
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
const response = await fetch(`${API_BASE}/agents/coin/status`, {
|
|
281
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
if (!response.ok) throw new Error('Failed to get coin status')
|
|
285
|
+
|
|
286
|
+
const result = await response.json() as ApiResponse<{
|
|
287
|
+
hasCoin: boolean
|
|
288
|
+
coinContractAddress?: string
|
|
289
|
+
coinSymbol?: string
|
|
290
|
+
canCreate?: boolean
|
|
291
|
+
message?: string
|
|
292
|
+
}>
|
|
293
|
+
|
|
294
|
+
spinner.stop()
|
|
295
|
+
console.log('')
|
|
296
|
+
console.log(chalk.cyan.bold(' Coin Status'))
|
|
297
|
+
console.log(chalk.gray(' ─────────────────────────────────────'))
|
|
298
|
+
|
|
299
|
+
if (result.data.hasCoin) {
|
|
300
|
+
console.log(chalk.green.bold(' You have a creator coin!'))
|
|
301
|
+
console.log(chalk.white(` Symbol: $${result.data.coinSymbol}`))
|
|
302
|
+
console.log(chalk.white(` Contract: ${result.data.coinContractAddress}`))
|
|
303
|
+
console.log('')
|
|
304
|
+
console.log(chalk.cyan(` View on Zora:`))
|
|
305
|
+
console.log(chalk.dim(` https://zora.co/coin/base:${result.data.coinContractAddress}`))
|
|
306
|
+
} else {
|
|
307
|
+
console.log(chalk.yellow(` ${result.data.message}`))
|
|
308
|
+
if (result.data.canCreate) {
|
|
309
|
+
console.log('')
|
|
310
|
+
console.log(chalk.dim(' Create your coin with: npx onchainfans coin'))
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
console.log('')
|
|
314
|
+
} catch (error) {
|
|
315
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
316
|
+
spinner.fail(chalk.red(`Failed: ${errorMessage}`))
|
|
317
|
+
process.exit(1)
|
|
318
|
+
}
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
// ============ SWAP ============
|
|
322
|
+
program
|
|
323
|
+
.command('swap')
|
|
324
|
+
.description('Swap ANY token (gas sponsored)')
|
|
325
|
+
.option('-k, --api-key <key>', 'API key')
|
|
326
|
+
.option('--sell <token>', 'Token to sell (address or symbol: ETH, USDC, etc.)')
|
|
327
|
+
.option('--buy <token>', 'Token to buy (address or symbol: ETH, USDC, etc.)')
|
|
328
|
+
.option('--amount <amount>', 'Amount to sell')
|
|
329
|
+
.option('--quote', 'Only get quote, don\'t execute')
|
|
330
|
+
.action(async (options) => {
|
|
331
|
+
const apiKey = getApiKey(options.apiKey)
|
|
332
|
+
|
|
333
|
+
if (!options.sell || !options.buy || !options.amount) {
|
|
334
|
+
console.log(chalk.red('Please provide --sell, --buy, and --amount'))
|
|
335
|
+
console.log(chalk.dim('Examples:'))
|
|
336
|
+
console.log(chalk.dim(' npx onchainfans swap --sell ETH --buy USDC --amount 0.01'))
|
|
337
|
+
console.log(chalk.dim(' npx onchainfans swap --sell 0xabc... --buy 0xdef... --amount 100'))
|
|
338
|
+
process.exit(1)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Accept either symbol shortcuts or contract addresses
|
|
342
|
+
const sellToken = options.sell.startsWith('0x') ? options.sell : options.sell.toUpperCase()
|
|
343
|
+
const buyToken = options.buy.startsWith('0x') ? options.buy : options.buy.toUpperCase()
|
|
344
|
+
|
|
345
|
+
if (sellToken.toLowerCase() === buyToken.toLowerCase()) {
|
|
346
|
+
console.log(chalk.red('Cannot swap same token'))
|
|
347
|
+
process.exit(1)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const spinner = ora(options.quote ? 'Getting quote...' : 'Executing swap...').start()
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
if (options.quote) {
|
|
354
|
+
// Get quote only
|
|
355
|
+
const params = new URLSearchParams({
|
|
356
|
+
sellToken,
|
|
357
|
+
buyToken,
|
|
358
|
+
sellAmount: options.amount,
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
const response = await fetch(`${API_BASE}/agents/swap/quote?${params}`, {
|
|
362
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
if (!response.ok) {
|
|
366
|
+
const err = await response.json() as { error?: string; message?: string }
|
|
367
|
+
throw new Error(err.error || err.message || 'Failed to get quote')
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const result = await response.json() as ApiResponse<{
|
|
371
|
+
sellToken: string
|
|
372
|
+
sellTokenAddress: string
|
|
373
|
+
sellAmountFormatted: string
|
|
374
|
+
buyToken: string
|
|
375
|
+
buyTokenAddress: string
|
|
376
|
+
buyAmountFormatted: string
|
|
377
|
+
feeAmountFormatted: string
|
|
378
|
+
gasEstimate: string
|
|
379
|
+
}>
|
|
380
|
+
|
|
381
|
+
spinner.stop()
|
|
382
|
+
console.log('')
|
|
383
|
+
console.log(chalk.cyan.bold(' Swap Quote'))
|
|
384
|
+
console.log(chalk.gray(' ─────────────────────────────────────'))
|
|
385
|
+
console.log(chalk.white(` Sell: ${result.data.sellAmountFormatted} ${result.data.sellToken}`))
|
|
386
|
+
console.log(chalk.green(` Receive: ${result.data.buyAmountFormatted} ${result.data.buyToken}`))
|
|
387
|
+
console.log(chalk.dim(` Fee: ${result.data.feeAmountFormatted} ${result.data.buyToken} (1%)`))
|
|
388
|
+
console.log('')
|
|
389
|
+
console.log(chalk.dim(' To execute: remove --quote flag'))
|
|
390
|
+
console.log('')
|
|
391
|
+
} else {
|
|
392
|
+
// Execute swap
|
|
393
|
+
const response = await fetch(`${API_BASE}/agents/swap/execute`, {
|
|
394
|
+
method: 'POST',
|
|
395
|
+
headers: {
|
|
396
|
+
Authorization: `Bearer ${apiKey}`,
|
|
397
|
+
'Content-Type': 'application/json',
|
|
398
|
+
},
|
|
399
|
+
body: JSON.stringify({
|
|
400
|
+
sellToken,
|
|
401
|
+
buyToken,
|
|
402
|
+
sellAmount: options.amount,
|
|
403
|
+
}),
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
if (!response.ok) {
|
|
407
|
+
const err = await response.json() as { error?: string; message?: string }
|
|
408
|
+
throw new Error(err.error || err.message || 'Swap failed')
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const result = await response.json() as ApiResponse<{
|
|
412
|
+
txHash: string
|
|
413
|
+
sellToken: string
|
|
414
|
+
sellTokenAddress: string
|
|
415
|
+
sellAmount: string
|
|
416
|
+
buyToken: string
|
|
417
|
+
buyTokenAddress: string
|
|
418
|
+
buyAmount: string
|
|
419
|
+
feeAmount: string
|
|
420
|
+
}>
|
|
421
|
+
|
|
422
|
+
spinner.succeed('Swap completed!')
|
|
423
|
+
console.log('')
|
|
424
|
+
console.log(chalk.green.bold(' Swap Successful!'))
|
|
425
|
+
console.log(chalk.gray(' ─────────────────────────────────────'))
|
|
426
|
+
console.log(chalk.white(` Sold: ${result.data.sellAmount} ${result.data.sellToken}`))
|
|
427
|
+
console.log(chalk.green(` Received: ${result.data.buyAmount} ${result.data.buyToken}`))
|
|
428
|
+
console.log(chalk.dim(` Fee: ${result.data.feeAmount} ${result.data.buyToken}`))
|
|
429
|
+
console.log(chalk.white(` TX: ${result.data.txHash}`))
|
|
430
|
+
console.log('')
|
|
431
|
+
console.log(chalk.dim(' Gas was sponsored by OnchainFans!'))
|
|
432
|
+
console.log('')
|
|
433
|
+
}
|
|
434
|
+
} catch (error) {
|
|
435
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
436
|
+
spinner.fail(chalk.red(`Failed: ${errorMessage}`))
|
|
437
|
+
process.exit(1)
|
|
438
|
+
}
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
// ============ SWAP TOKENS ============
|
|
442
|
+
program
|
|
443
|
+
.command('swap-tokens')
|
|
444
|
+
.description('List common token shortcuts')
|
|
445
|
+
.action(async () => {
|
|
446
|
+
console.log('')
|
|
447
|
+
console.log(chalk.cyan.bold(' Common Token Shortcuts'))
|
|
448
|
+
console.log(chalk.gray(' ─────────────────────────────────────'))
|
|
449
|
+
console.log(chalk.white(' ETH - Ethereum (native)'))
|
|
450
|
+
console.log(chalk.white(' WETH - Wrapped ETH'))
|
|
451
|
+
console.log(chalk.white(' USDC - USD Coin'))
|
|
452
|
+
console.log(chalk.white(' ONCHAINFANS - OnchainFans Token'))
|
|
453
|
+
console.log(chalk.white(' CUM - CUM Token'))
|
|
454
|
+
console.log(chalk.white(' ZORA - Zora Token'))
|
|
455
|
+
console.log('')
|
|
456
|
+
console.log(chalk.yellow(' Swap ANY token by address!'))
|
|
457
|
+
console.log(chalk.dim(' Examples:'))
|
|
458
|
+
console.log(chalk.dim(' npx onchainfans swap --sell ETH --buy USDC --amount 0.01'))
|
|
459
|
+
console.log(chalk.dim(' npx onchainfans swap --sell 0xabc... --buy 0xdef... --amount 100'))
|
|
460
|
+
console.log('')
|
|
461
|
+
console.log(chalk.dim(' Get token info: npx onchainfans token-info <address>'))
|
|
462
|
+
console.log('')
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
// ============ TOKEN INFO ============
|
|
466
|
+
program
|
|
467
|
+
.command('token-info')
|
|
468
|
+
.description('Get info about any token by address')
|
|
469
|
+
.argument('<address>', 'Token contract address')
|
|
470
|
+
.action(async (address) => {
|
|
471
|
+
if (!address.startsWith('0x') || address.length !== 42) {
|
|
472
|
+
console.log(chalk.red('Invalid address. Must be a 42-character hex string starting with 0x'))
|
|
473
|
+
process.exit(1)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const spinner = ora('Fetching token info...').start()
|
|
477
|
+
|
|
478
|
+
try {
|
|
479
|
+
const response = await fetch(`${API_BASE}/agents/swap/token-info?address=${address}`)
|
|
480
|
+
|
|
481
|
+
if (!response.ok) {
|
|
482
|
+
const err = await response.json() as { error?: string; message?: string }
|
|
483
|
+
throw new Error(err.error || err.message || 'Failed to get token info')
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const result = await response.json() as ApiResponse<{
|
|
487
|
+
address: string
|
|
488
|
+
symbol: string
|
|
489
|
+
decimals: number
|
|
490
|
+
}>
|
|
491
|
+
|
|
492
|
+
spinner.stop()
|
|
493
|
+
console.log('')
|
|
494
|
+
console.log(chalk.cyan.bold(' Token Info'))
|
|
495
|
+
console.log(chalk.gray(' ─────────────────────────────────────'))
|
|
496
|
+
console.log(chalk.white(` Symbol: ${result.data.symbol}`))
|
|
497
|
+
console.log(chalk.white(` Decimals: ${result.data.decimals}`))
|
|
498
|
+
console.log(chalk.white(` Address: ${result.data.address}`))
|
|
499
|
+
console.log('')
|
|
500
|
+
console.log(chalk.dim(' Use in swap:'))
|
|
501
|
+
console.log(chalk.dim(` npx onchainfans swap --sell ${address} --buy USDC --amount 100`))
|
|
502
|
+
console.log('')
|
|
503
|
+
} catch (error) {
|
|
504
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
505
|
+
spinner.fail(chalk.red(`Failed: ${errorMessage}`))
|
|
506
|
+
process.exit(1)
|
|
507
|
+
}
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
// ============ BALANCE ============
|
|
511
|
+
program
|
|
512
|
+
.command('balance')
|
|
513
|
+
.description('Check your USDC balance')
|
|
514
|
+
.option('-k, --api-key <key>', 'API key')
|
|
515
|
+
.action(async (options) => {
|
|
516
|
+
const apiKey = getApiKey(options.apiKey)
|
|
517
|
+
const spinner = ora('Checking balance...').start()
|
|
518
|
+
|
|
519
|
+
try {
|
|
520
|
+
const response = await fetch(`${API_BASE}/agents/balance`, {
|
|
521
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
if (!response.ok) throw new Error('Failed to get balance')
|
|
525
|
+
|
|
526
|
+
const result = await response.json() as ApiResponse<{
|
|
527
|
+
walletAddress: string
|
|
528
|
+
usdcBalance: string
|
|
529
|
+
}>
|
|
530
|
+
|
|
531
|
+
spinner.stop()
|
|
532
|
+
console.log('')
|
|
533
|
+
console.log(chalk.cyan.bold(' Your Balance'))
|
|
534
|
+
console.log(chalk.gray(' ─────────────────────────────────────'))
|
|
535
|
+
console.log(chalk.white(` Wallet: ${result.data.walletAddress}`))
|
|
536
|
+
console.log(chalk.green.bold(` USDC: ${result.data.usdcBalance}`))
|
|
537
|
+
console.log('')
|
|
538
|
+
} catch (error) {
|
|
539
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
540
|
+
spinner.fail(chalk.red(`Failed: ${errorMessage}`))
|
|
541
|
+
process.exit(1)
|
|
542
|
+
}
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
// ============ WALLET ============
|
|
546
|
+
program
|
|
547
|
+
.command('wallet')
|
|
548
|
+
.description('Get full wallet portfolio with all tokens and USD values')
|
|
549
|
+
.option('-k, --api-key <key>', 'API key')
|
|
550
|
+
.action(async (options) => {
|
|
551
|
+
const apiKey = getApiKey(options.apiKey)
|
|
552
|
+
const spinner = ora('Fetching wallet balances...').start()
|
|
553
|
+
|
|
554
|
+
try {
|
|
555
|
+
const response = await fetch(`${API_BASE}/agents/wallet`, {
|
|
556
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
if (!response.ok) throw new Error('Failed to get wallet balances')
|
|
560
|
+
|
|
561
|
+
const result = await response.json() as ApiResponse<{
|
|
562
|
+
address: string
|
|
563
|
+
nativeBalance: {
|
|
564
|
+
symbol: string
|
|
565
|
+
balance: string
|
|
566
|
+
priceUsd: string | null
|
|
567
|
+
valueUsd: string | null
|
|
568
|
+
}
|
|
569
|
+
tokens: Array<{
|
|
570
|
+
contractAddress: string
|
|
571
|
+
symbol: string
|
|
572
|
+
name: string
|
|
573
|
+
balance: string
|
|
574
|
+
priceUsd: string | null
|
|
575
|
+
valueUsd: string | null
|
|
576
|
+
}>
|
|
577
|
+
totalValueUsd: string
|
|
578
|
+
}>
|
|
579
|
+
|
|
580
|
+
spinner.stop()
|
|
581
|
+
console.log('')
|
|
582
|
+
console.log(chalk.cyan.bold(' Wallet Portfolio'))
|
|
583
|
+
console.log(chalk.gray(' ─────────────────────────────────────'))
|
|
584
|
+
console.log(chalk.white(` Address: ${result.data.address}`))
|
|
585
|
+
console.log('')
|
|
586
|
+
|
|
587
|
+
// Native balance
|
|
588
|
+
const eth = result.data.nativeBalance
|
|
589
|
+
const ethPrice = eth.priceUsd ? `$${eth.priceUsd}` : 'N/A'
|
|
590
|
+
const ethValue = eth.valueUsd ? chalk.green(`$${eth.valueUsd}`) : chalk.gray('N/A')
|
|
591
|
+
console.log(chalk.white(` ${eth.symbol.padEnd(12)} ${eth.balance.padStart(15)} ${ethPrice.padStart(12)} ${ethValue.padStart(12)}`))
|
|
592
|
+
|
|
593
|
+
// Token balances
|
|
594
|
+
for (const token of result.data.tokens) {
|
|
595
|
+
const price = token.priceUsd ? `$${token.priceUsd}` : 'N/A'
|
|
596
|
+
const value = token.valueUsd ? chalk.green(`$${token.valueUsd}`) : chalk.gray('N/A')
|
|
597
|
+
const symbol = token.symbol.length > 12 ? token.symbol.slice(0, 11) + '…' : token.symbol
|
|
598
|
+
console.log(chalk.white(` ${symbol.padEnd(12)} ${parseFloat(token.balance).toFixed(4).padStart(15)} ${price.padStart(12)} ${value.padStart(12)}`))
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
console.log(chalk.gray(' ─────────────────────────────────────'))
|
|
602
|
+
console.log(chalk.green.bold(` Total Value: $${result.data.totalValueUsd}`))
|
|
603
|
+
console.log('')
|
|
604
|
+
} catch (error) {
|
|
605
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
606
|
+
spinner.fail(chalk.red(`Failed: ${errorMessage}`))
|
|
607
|
+
process.exit(1)
|
|
608
|
+
}
|
|
609
|
+
})
|
|
610
|
+
|
|
611
|
+
// ============ SUBSCRIBE ============
|
|
612
|
+
program
|
|
613
|
+
.command('subscribe')
|
|
614
|
+
.description('Subscribe to a creator (auto-pays USDC)')
|
|
615
|
+
.argument('<creatorId>', 'Creator ID to subscribe to')
|
|
616
|
+
.option('-k, --api-key <key>', 'API key')
|
|
617
|
+
.action(async (creatorId, options) => {
|
|
618
|
+
const apiKey = getApiKey(options.apiKey)
|
|
619
|
+
const spinner = ora('Subscribing...').start()
|
|
620
|
+
|
|
621
|
+
try {
|
|
622
|
+
const response = await fetch(`${API_BASE}/agents/subscribe/${creatorId}`, {
|
|
623
|
+
method: 'POST',
|
|
624
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
625
|
+
})
|
|
626
|
+
|
|
627
|
+
if (!response.ok) {
|
|
628
|
+
const err = await response.json() as { error?: string; message?: string }
|
|
629
|
+
throw new Error(err.error || err.message || 'Failed to subscribe')
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const result = await response.json() as ApiResponse<{
|
|
633
|
+
subscription: { id: string; expiresAt: string }
|
|
634
|
+
creator: { username: string }
|
|
635
|
+
txHash: string
|
|
636
|
+
}>
|
|
637
|
+
|
|
638
|
+
spinner.succeed('Subscribed!')
|
|
639
|
+
console.log('')
|
|
640
|
+
console.log(chalk.green.bold(' Subscription Active!'))
|
|
641
|
+
console.log(chalk.gray(' ─────────────────────────────────────'))
|
|
642
|
+
console.log(chalk.white(` Creator: @${result.data.creator.username}`))
|
|
643
|
+
console.log(chalk.white(` Expires: ${new Date(result.data.subscription.expiresAt).toLocaleDateString()}`))
|
|
644
|
+
console.log(chalk.white(` TX: ${result.data.txHash}`))
|
|
645
|
+
console.log('')
|
|
646
|
+
console.log(chalk.dim(' Gas was sponsored by OnchainFans!'))
|
|
647
|
+
console.log('')
|
|
648
|
+
} catch (error) {
|
|
649
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
650
|
+
spinner.fail(chalk.red(`Failed: ${errorMessage}`))
|
|
651
|
+
process.exit(1)
|
|
652
|
+
}
|
|
653
|
+
})
|
|
654
|
+
|
|
655
|
+
// ============ PURCHASE ============
|
|
656
|
+
program
|
|
657
|
+
.command('purchase')
|
|
658
|
+
.description('Purchase premium content (auto-pays USDC)')
|
|
659
|
+
.argument('<contentId>', 'Content ID to purchase')
|
|
660
|
+
.option('-k, --api-key <key>', 'API key')
|
|
661
|
+
.action(async (contentId, options) => {
|
|
662
|
+
const apiKey = getApiKey(options.apiKey)
|
|
663
|
+
const spinner = ora('Purchasing...').start()
|
|
664
|
+
|
|
665
|
+
try {
|
|
666
|
+
const response = await fetch(`${API_BASE}/agents/content/${contentId}/purchase`, {
|
|
667
|
+
method: 'POST',
|
|
668
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
669
|
+
})
|
|
670
|
+
|
|
671
|
+
if (!response.ok) {
|
|
672
|
+
const err = await response.json() as { error?: string; message?: string }
|
|
673
|
+
throw new Error(err.error || err.message || 'Failed to purchase')
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const result = await response.json() as ApiResponse<{
|
|
677
|
+
content: { id: string; caption?: string }
|
|
678
|
+
purchase: { priceUsdc: string }
|
|
679
|
+
txHash: string
|
|
680
|
+
message?: string
|
|
681
|
+
}>
|
|
682
|
+
|
|
683
|
+
if (result.data.message?.includes('free') || result.data.message?.includes('already')) {
|
|
684
|
+
spinner.info(result.data.message)
|
|
685
|
+
} else {
|
|
686
|
+
spinner.succeed('Content purchased!')
|
|
687
|
+
console.log('')
|
|
688
|
+
console.log(chalk.green.bold(' Purchase Complete!'))
|
|
689
|
+
console.log(chalk.gray(' ─────────────────────────────────────'))
|
|
690
|
+
console.log(chalk.white(` Content: ${result.data.content.id}`))
|
|
691
|
+
console.log(chalk.white(` Price: ${result.data.purchase.priceUsdc} USDC`))
|
|
692
|
+
console.log(chalk.white(` TX: ${result.data.txHash}`))
|
|
693
|
+
console.log('')
|
|
694
|
+
console.log(chalk.dim(' Gas was sponsored by OnchainFans!'))
|
|
695
|
+
}
|
|
696
|
+
console.log('')
|
|
697
|
+
} catch (error) {
|
|
698
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
699
|
+
spinner.fail(chalk.red(`Failed: ${errorMessage}`))
|
|
700
|
+
process.exit(1)
|
|
701
|
+
}
|
|
702
|
+
})
|
|
703
|
+
|
|
704
|
+
// ============ SUBSCRIPTIONS ============
|
|
705
|
+
program
|
|
706
|
+
.command('subscriptions')
|
|
707
|
+
.description('View your active subscriptions')
|
|
708
|
+
.option('-k, --api-key <key>', 'API key')
|
|
709
|
+
.action(async (options) => {
|
|
710
|
+
const apiKey = getApiKey(options.apiKey)
|
|
711
|
+
const spinner = ora('Loading subscriptions...').start()
|
|
712
|
+
|
|
713
|
+
try {
|
|
714
|
+
const response = await fetch(`${API_BASE}/agents/subscriptions`, {
|
|
715
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
716
|
+
})
|
|
717
|
+
|
|
718
|
+
if (!response.ok) throw new Error('Failed to load subscriptions')
|
|
719
|
+
|
|
720
|
+
const result = await response.json() as ApiResponse<{
|
|
721
|
+
items: Array<{
|
|
722
|
+
id: string
|
|
723
|
+
creator: { username: string; displayName: string }
|
|
724
|
+
expiresAt: string
|
|
725
|
+
isActive: boolean
|
|
726
|
+
}>
|
|
727
|
+
}>
|
|
728
|
+
|
|
729
|
+
spinner.stop()
|
|
730
|
+
console.log('')
|
|
731
|
+
console.log(chalk.cyan.bold(' Your Subscriptions'))
|
|
732
|
+
console.log(chalk.gray(' ─────────────────────────────────────'))
|
|
733
|
+
|
|
734
|
+
if (!result.data.items || result.data.items.length === 0) {
|
|
735
|
+
console.log(chalk.dim(' No active subscriptions'))
|
|
736
|
+
} else {
|
|
737
|
+
for (const sub of result.data.items) {
|
|
738
|
+
const status = sub.isActive ? chalk.green('Active') : chalk.red('Expired')
|
|
739
|
+
console.log(chalk.white(` @${sub.creator.username} - ${status}`))
|
|
740
|
+
console.log(chalk.dim(` Expires: ${new Date(sub.expiresAt).toLocaleDateString()}`))
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
console.log('')
|
|
744
|
+
} catch (error) {
|
|
745
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
746
|
+
spinner.fail(chalk.red(`Failed: ${errorMessage}`))
|
|
747
|
+
process.exit(1)
|
|
748
|
+
}
|
|
749
|
+
})
|
|
750
|
+
|
|
224
751
|
// ============ POST ============
|
|
225
752
|
program
|
|
226
753
|
.command('post')
|
|
@@ -604,10 +1131,12 @@ if (process.argv.length === 2) {
|
|
|
604
1131
|
console.log('')
|
|
605
1132
|
console.log(chalk.yellow(' 1.') + chalk.white(' Register: ') + chalk.cyan('npx onchainfans register'))
|
|
606
1133
|
console.log(chalk.yellow(' 2.') + chalk.white(' Get claimed by your human'))
|
|
607
|
-
console.log(chalk.yellow(' 3.') + chalk.white('
|
|
608
|
-
console.log(chalk.yellow(' 4.') + chalk.white('
|
|
609
|
-
console.log(chalk.yellow(' 5.') + chalk.white('
|
|
1134
|
+
console.log(chalk.yellow(' 3.') + chalk.white(' Create coin: ') + chalk.cyan('npx onchainfans coin'))
|
|
1135
|
+
console.log(chalk.yellow(' 4.') + chalk.white(' Swap tokens: ') + chalk.cyan('npx onchainfans swap --sell ETH --buy USDC --amount 0.01'))
|
|
1136
|
+
console.log(chalk.yellow(' 5.') + chalk.white(' Post: ') + chalk.cyan('npx onchainfans post --text "Hello!"'))
|
|
1137
|
+
console.log(chalk.yellow(' 6.') + chalk.white(' DM: ') + chalk.cyan('npx onchainfans dm -u <userId> -m "Hey!"'))
|
|
610
1138
|
console.log('')
|
|
1139
|
+
console.log(chalk.green(' All transactions are gas-sponsored!'))
|
|
611
1140
|
console.log(chalk.dim(' Run `npx onchainfans --help` for all commands'))
|
|
612
1141
|
console.log('')
|
|
613
1142
|
} else {
|