paytaca-cli 0.6.1 → 0.7.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/README.md +153 -37
- package/dist/ai/autoRefill.d.ts +27 -0
- package/dist/ai/autoRefill.d.ts.map +1 -0
- package/dist/ai/autoRefill.js +75 -0
- package/dist/ai/autoRefill.js.map +1 -0
- package/dist/ai/client.d.ts +73 -0
- package/dist/ai/client.d.ts.map +1 -0
- package/dist/ai/client.js +90 -0
- package/dist/ai/client.js.map +1 -0
- package/dist/ai/config.d.ts +7 -0
- package/dist/ai/config.d.ts.map +1 -0
- package/dist/ai/config.js +16 -0
- package/dist/ai/config.js.map +1 -0
- package/dist/ai/configure.d.ts +61 -0
- package/dist/ai/configure.d.ts.map +1 -0
- package/dist/ai/configure.js +452 -0
- package/dist/ai/configure.js.map +1 -0
- package/dist/ai/credits.d.ts +27 -0
- package/dist/ai/credits.d.ts.map +1 -0
- package/dist/ai/credits.js +99 -0
- package/dist/ai/credits.js.map +1 -0
- package/dist/ai/models.d.ts +13 -0
- package/dist/ai/models.d.ts.map +1 -0
- package/dist/ai/models.js +49 -0
- package/dist/ai/models.js.map +1 -0
- package/dist/ai/oauth.d.ts +45 -0
- package/dist/ai/oauth.d.ts.map +1 -0
- package/dist/ai/oauth.js +137 -0
- package/dist/ai/oauth.js.map +1 -0
- package/dist/ai/purchase.d.ts +30 -0
- package/dist/ai/purchase.d.ts.map +1 -0
- package/dist/ai/purchase.js +456 -0
- package/dist/ai/purchase.js.map +1 -0
- package/dist/commands/address.d.ts.map +1 -1
- package/dist/commands/address.js +88 -36
- package/dist/commands/address.js.map +1 -1
- package/dist/commands/ai.d.ts +9 -0
- package/dist/commands/ai.d.ts.map +1 -0
- package/dist/commands/ai.js +675 -0
- package/dist/commands/ai.js.map +1 -0
- package/dist/commands/balance.d.ts.map +1 -1
- package/dist/commands/balance.js +68 -129
- package/dist/commands/balance.js.map +1 -1
- package/dist/commands/history.d.ts.map +1 -1
- package/dist/commands/history.js +38 -78
- package/dist/commands/history.js.map +1 -1
- package/dist/commands/mcp.d.ts +14 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +68 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/pay.js +1 -1
- package/dist/commands/pay.js.map +1 -1
- package/dist/commands/receive.d.ts +1 -8
- package/dist/commands/receive.d.ts.map +1 -1
- package/dist/commands/receive.js +51 -124
- package/dist/commands/receive.js.map +1 -1
- package/dist/commands/send.d.ts +0 -10
- package/dist/commands/send.d.ts.map +1 -1
- package/dist/commands/send.js +40 -50
- package/dist/commands/send.js.map +1 -1
- package/dist/commands/swap.js +1 -1
- package/dist/commands/swap.js.map +1 -1
- package/dist/commands/token.d.ts +1 -9
- package/dist/commands/token.d.ts.map +1 -1
- package/dist/commands/token.js +192 -289
- package/dist/commands/token.js.map +1 -1
- package/dist/core/context.d.ts +30 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +45 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/wallet.d.ts +130 -0
- package/dist/core/wallet.d.ts.map +1 -0
- package/dist/core/wallet.js +276 -0
- package/dist/core/wallet.js.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts +8 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +24 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +5 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +473 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/utils/format.d.ts +23 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +62 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/x402.js +1 -1
- package/dist/utils/x402.js.map +1 -1
- package/dist/wallet/index.d.ts +13 -0
- package/dist/wallet/index.d.ts.map +1 -1
- package/dist/wallet/index.js +18 -0
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/keys.d.ts.map +1 -1
- package/dist/wallet/keys.js +2 -0
- package/dist/wallet/keys.js.map +1 -1
- package/package.json +7 -3
- package/dist/commands/wallet.test.d.ts +0 -2
- package/dist/commands/wallet.test.d.ts.map +0 -1
- package/dist/commands/wallet.test.js +0 -68
- package/dist/commands/wallet.test.js.map +0 -1
- package/dist/nostr/chat.test.d.ts +0 -2
- package/dist/nostr/chat.test.d.ts.map +0 -1
- package/dist/nostr/chat.test.js +0 -121
- package/dist/nostr/chat.test.js.map +0 -1
- package/dist/nostr/store.test.d.ts +0 -2
- package/dist/nostr/store.test.d.ts.map +0 -1
- package/dist/nostr/store.test.js +0 -456
- package/dist/nostr/store.test.js.map +0 -1
- package/dist/utils/prices.test.d.ts +0 -5
- package/dist/utils/prices.test.d.ts.map +0 -1
- package/dist/utils/prices.test.js +0 -62
- package/dist/utils/prices.test.js.map +0 -1
- package/dist/utils/x402.test.d.ts +0 -2
- package/dist/utils/x402.test.d.ts.map +0 -1
- package/dist/utils/x402.test.js +0 -216
- package/dist/utils/x402.test.js.map +0 -1
- package/dist/wallet/cauldron/pools.test.d.ts +0 -3
- package/dist/wallet/cauldron/pools.test.d.ts.map +0 -1
- package/dist/wallet/cauldron/pools.test.js +0 -149
- package/dist/wallet/cauldron/pools.test.js.map +0 -1
- package/dist/wallet/cauldron/swap.test.d.ts +0 -2
- package/dist/wallet/cauldron/swap.test.d.ts.map +0 -1
- package/dist/wallet/cauldron/swap.test.js +0 -179
- package/dist/wallet/cauldron/swap.test.js.map +0 -1
- package/skills/README.md +0 -68
- package/skills/paytaca-send/SKILL.md +0 -159
- package/skills/paytaca-wallet/SKILL.md +0 -164
- package/skills/paytaca-x402/SKILL.md +0 -130
package/dist/commands/token.js
CHANGED
|
@@ -1,35 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI commands: token list | info | send | send-nft
|
|
2
|
+
* CLI commands: token list | info | price | send | send-nft
|
|
3
3
|
*
|
|
4
4
|
* CashTokens support for the Paytaca CLI.
|
|
5
|
-
*
|
|
6
|
-
* CashTokens are the native token protocol on Bitcoin Cash, supporting
|
|
7
|
-
* both fungible tokens (FTs) and non-fungible tokens (NFTs) at the
|
|
8
|
-
* consensus layer. Tokens are received at token-aware addresses
|
|
9
|
-
* (z-prefix CashAddr format).
|
|
10
|
-
*
|
|
11
|
-
* Token sends are delegated to watchtower-cash-js BCH.send() with
|
|
12
|
-
* the `token` parameter — identical to how paytaca-app handles it.
|
|
13
5
|
*/
|
|
14
6
|
import chalk from 'chalk';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
function
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
minimumFractionDigits: 0,
|
|
31
|
-
maximumFractionDigits: decimals,
|
|
32
|
-
});
|
|
7
|
+
import { WalletNotConfiguredError, requireWallet } from '../core/context.js';
|
|
8
|
+
import { getTokenBalances, getTokenDetail, isValidBchAddress, isTokenAddress, sendToken, } from '../core/wallet.js';
|
|
9
|
+
import { explorerTxUrl, shortHex } from '../utils/format.js';
|
|
10
|
+
import { formatUsd } from '../utils/prices.js';
|
|
11
|
+
function reportError(err, asJson, contextLabel) {
|
|
12
|
+
if (asJson) {
|
|
13
|
+
console.log(JSON.stringify({ error: err.message || String(err) }, null, 2));
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
if (err instanceof WalletNotConfiguredError) {
|
|
17
|
+
console.log(chalk.red(`\n${err.message}\n`));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
console.log(chalk.red(` Error ${contextLabel}: ${err.message || err}`));
|
|
21
|
+
process.exit(1);
|
|
33
22
|
}
|
|
34
23
|
export function registerTokenCommands(program) {
|
|
35
24
|
const token = program
|
|
@@ -40,85 +29,61 @@ export function registerTokenCommands(program) {
|
|
|
40
29
|
.command('list')
|
|
41
30
|
.description('List fungible CashTokens in the wallet')
|
|
42
31
|
.option('--chipnet', 'Use chipnet (testnet) instead of mainnet')
|
|
32
|
+
.option('--json', 'Output as JSON')
|
|
43
33
|
.action(async (opts) => {
|
|
44
34
|
const isChipnet = Boolean(opts.chipnet);
|
|
35
|
+
const asJson = Boolean(opts.json);
|
|
45
36
|
const network = isChipnet ? 'chipnet' : 'mainnet';
|
|
46
|
-
const data = loadMnemonic();
|
|
47
|
-
if (!data) {
|
|
48
|
-
console.log(chalk.red('\nNo wallet found. Run `paytaca wallet create` or `paytaca wallet import` first.\n'));
|
|
49
|
-
process.exit(1);
|
|
50
|
-
}
|
|
51
|
-
const w = loadWallet();
|
|
52
|
-
const bchWallet = w.forNetwork(isChipnet);
|
|
53
|
-
console.log(chalk.bold(`\n CashTokens (${network})\n`));
|
|
54
37
|
try {
|
|
55
|
-
const
|
|
56
|
-
if (
|
|
57
|
-
console.log(
|
|
38
|
+
const result = await getTokenBalances(isChipnet);
|
|
39
|
+
if (asJson) {
|
|
40
|
+
console.log(JSON.stringify(result.tokens, null, 2));
|
|
58
41
|
return;
|
|
59
42
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
prices = new Map();
|
|
65
|
-
for (const p of priceData) {
|
|
66
|
-
if (String(p.currency || '').toLowerCase() !== 'usd')
|
|
67
|
-
continue;
|
|
68
|
-
const catMatch = String(p.asset || '').match(/^ct\/([a-fA-F0-9]+)$/);
|
|
69
|
-
if (!catMatch)
|
|
70
|
-
continue;
|
|
71
|
-
const raw = parseFloat(p.price_value);
|
|
72
|
-
if (!isFinite(raw) || raw === 0)
|
|
73
|
-
continue;
|
|
74
|
-
prices.set(catMatch[1], 1 / raw);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
// Pricing unavailable — proceed without USD values
|
|
43
|
+
console.log(chalk.bold(`\n CashTokens (${network})\n`));
|
|
44
|
+
if (result.tokens.length === 0) {
|
|
45
|
+
console.log(chalk.dim(' No tokens found.\n'));
|
|
46
|
+
return;
|
|
79
47
|
}
|
|
80
48
|
let totalUsd = 0;
|
|
81
49
|
let pricedCount = 0;
|
|
82
|
-
for (const t of tokens) {
|
|
83
|
-
const amount = formatTokenAmount(t.balance, t.decimals);
|
|
50
|
+
for (const t of result.tokens) {
|
|
84
51
|
const symbol = t.symbol ? ` ${t.symbol}` : '';
|
|
85
52
|
const name = t.name !== 'Unknown Token' ? t.name : '';
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (name) {
|
|
53
|
+
console.log(` ${chalk.bold(t.displayBalance + symbol)}`);
|
|
54
|
+
if (name)
|
|
89
55
|
console.log(chalk.dim(` ${name}`));
|
|
90
|
-
}
|
|
91
56
|
console.log(chalk.dim(` ${t.category}`));
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
totalUsd += usdValue;
|
|
57
|
+
if (t.usd !== null) {
|
|
58
|
+
totalUsd += t.usd;
|
|
95
59
|
pricedCount += 1;
|
|
96
|
-
console.log(chalk.green(` ≈ ${formatUsd(
|
|
60
|
+
console.log(chalk.green(` ≈ ${formatUsd(t.usd)}`));
|
|
97
61
|
}
|
|
98
62
|
console.log();
|
|
99
63
|
}
|
|
100
64
|
if (pricedCount > 0) {
|
|
101
|
-
console.log(chalk.dim(` ${tokens.length} token${tokens.length !== 1 ? 's' : ''} total`));
|
|
65
|
+
console.log(chalk.dim(` ${result.tokens.length} token${result.tokens.length !== 1 ? 's' : ''} total`));
|
|
102
66
|
console.log(chalk.dim(` ${pricedCount} priced`));
|
|
103
67
|
console.log(chalk.bold(` Total: ${formatUsd(totalUsd)}`));
|
|
104
68
|
console.log();
|
|
105
69
|
}
|
|
106
70
|
else {
|
|
107
|
-
console.log(chalk.dim(` ${tokens.length} token${tokens.length !== 1 ? 's' : ''} total`));
|
|
71
|
+
console.log(chalk.dim(` ${result.tokens.length} token${result.tokens.length !== 1 ? 's' : ''} total`));
|
|
108
72
|
}
|
|
73
|
+
console.log();
|
|
109
74
|
}
|
|
110
75
|
catch (err) {
|
|
76
|
+
if (err instanceof WalletNotConfiguredError)
|
|
77
|
+
reportError(err, asJson, '');
|
|
111
78
|
const status = err?.response?.status;
|
|
112
|
-
if (status === 404) {
|
|
79
|
+
if (status === 404 && !asJson) {
|
|
113
80
|
console.log(chalk.yellow(' Wallet not yet registered with Watchtower on this network.'));
|
|
114
81
|
console.log(chalk.dim(' Run `paytaca wallet create` or `paytaca wallet import` to register.'));
|
|
82
|
+
console.log();
|
|
83
|
+
return;
|
|
115
84
|
}
|
|
116
|
-
|
|
117
|
-
console.log(chalk.red(` Error fetching tokens: ${err.message || err}`));
|
|
118
|
-
process.exit(1);
|
|
119
|
-
}
|
|
85
|
+
reportError(err, asJson, 'fetching tokens');
|
|
120
86
|
}
|
|
121
|
-
console.log();
|
|
122
87
|
});
|
|
123
88
|
// ── token info ─────────────────────────────────────────────────────
|
|
124
89
|
token
|
|
@@ -126,85 +91,50 @@ export function registerTokenCommands(program) {
|
|
|
126
91
|
.description('Show details for a specific CashToken')
|
|
127
92
|
.argument('<category>', 'Token category ID (64-character hex)')
|
|
128
93
|
.option('--chipnet', 'Use chipnet (testnet) instead of mainnet')
|
|
94
|
+
.option('--json', 'Output as JSON')
|
|
129
95
|
.action(async (category, opts) => {
|
|
130
96
|
const isChipnet = Boolean(opts.chipnet);
|
|
97
|
+
const asJson = Boolean(opts.json);
|
|
131
98
|
const network = isChipnet ? 'chipnet' : 'mainnet';
|
|
132
|
-
// Validate category format
|
|
133
99
|
if (!/^[a-fA-F0-9]{64}$/.test(category)) {
|
|
134
100
|
console.log(chalk.red('\nError: Category must be a 64-character hex string.\n'));
|
|
135
101
|
process.exit(1);
|
|
136
102
|
}
|
|
137
|
-
const data = loadMnemonic();
|
|
138
|
-
if (!data) {
|
|
139
|
-
console.log(chalk.red('\nNo wallet found. Run `paytaca wallet create` or `paytaca wallet import` first.\n'));
|
|
140
|
-
process.exit(1);
|
|
141
|
-
}
|
|
142
|
-
const w = loadWallet();
|
|
143
|
-
const bchWallet = w.forNetwork(isChipnet);
|
|
144
|
-
console.log(chalk.bold(`\n Token Info (${network})\n`));
|
|
145
103
|
try {
|
|
146
|
-
const
|
|
147
|
-
if (
|
|
148
|
-
console.log(
|
|
104
|
+
const detail = await getTokenDetail(category, isChipnet);
|
|
105
|
+
if (asJson) {
|
|
106
|
+
console.log(JSON.stringify(detail, null, 2));
|
|
149
107
|
return;
|
|
150
108
|
}
|
|
151
|
-
console.log(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
console.log(` Decimals: ${
|
|
156
|
-
console.log(` Category: ${
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (p !== null)
|
|
162
|
-
usdPerToken = p;
|
|
163
|
-
}
|
|
164
|
-
catch {
|
|
165
|
-
// Pricing unavailable — omit USD lines
|
|
166
|
-
}
|
|
167
|
-
// Fetch wallet-specific balance
|
|
168
|
-
try {
|
|
169
|
-
const balResult = await bchWallet.getTokenBalance(category);
|
|
170
|
-
const amount = formatTokenAmount(balResult.balance, tokenInfo.decimals);
|
|
171
|
-
const symbol = tokenInfo.symbol ? ` ${tokenInfo.symbol}` : '';
|
|
172
|
-
console.log(` Balance: ${amount}${symbol}`);
|
|
173
|
-
if (usdPerToken !== undefined) {
|
|
174
|
-
const usdValue = tokenAmountToUsd(balResult.balance, tokenInfo.decimals, usdPerToken);
|
|
175
|
-
console.log(chalk.green(` Value: ≈ ${formatUsd(usdValue)}`));
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
179
|
-
// Balance may not be available if wallet doesn't hold this token
|
|
180
|
-
console.log(chalk.dim(' Balance: 0'));
|
|
109
|
+
console.log(chalk.bold(`\n Token Info (${network})\n`));
|
|
110
|
+
console.log(` Name: ${detail.name}`);
|
|
111
|
+
if (detail.symbol)
|
|
112
|
+
console.log(` Symbol: ${detail.symbol}`);
|
|
113
|
+
console.log(` Decimals: ${detail.decimals}`);
|
|
114
|
+
console.log(` Category: ${detail.category}`);
|
|
115
|
+
const symbol = detail.symbol ? ` ${detail.symbol}` : '';
|
|
116
|
+
console.log(` Balance: ${detail.displayBalance}${symbol}`);
|
|
117
|
+
if (detail.usd !== null) {
|
|
118
|
+
console.log(chalk.green(` Value: ≈ ${formatUsd(detail.usd)}`));
|
|
181
119
|
}
|
|
182
|
-
if (usdPerToken !==
|
|
183
|
-
console.log(chalk.dim(` Price: ${formatUsd(usdPerToken)} per ${
|
|
120
|
+
if (detail.usdPerToken !== null) {
|
|
121
|
+
console.log(chalk.dim(` Price: ${formatUsd(detail.usdPerToken)} per ${detail.symbol || 'token'}`));
|
|
184
122
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
console.log(` ${chalk.cyan(commitment)}${cap}`);
|
|
194
|
-
console.log(chalk.dim(` ${nft.txid}:${nft.vout}`));
|
|
195
|
-
console.log();
|
|
196
|
-
}
|
|
123
|
+
if (detail.nfts.length > 0) {
|
|
124
|
+
console.log(`\n ${chalk.bold('NFTs')} (${detail.nfts.length})\n`);
|
|
125
|
+
for (const nft of detail.nfts) {
|
|
126
|
+
const cap = nft.capability === 'none' ? '' : ` [${nft.capability}]`;
|
|
127
|
+
const commitment = nft.commitment ? shortHex(nft.commitment) : '(empty)';
|
|
128
|
+
console.log(` ${chalk.cyan(commitment)}${cap}`);
|
|
129
|
+
console.log(chalk.dim(` ${nft.txid}:${nft.vout}`));
|
|
130
|
+
console.log();
|
|
197
131
|
}
|
|
198
132
|
}
|
|
199
|
-
|
|
200
|
-
// NFT fetch may fail; non-critical
|
|
201
|
-
}
|
|
133
|
+
console.log();
|
|
202
134
|
}
|
|
203
135
|
catch (err) {
|
|
204
|
-
|
|
205
|
-
process.exit(1);
|
|
136
|
+
reportError(err, asJson, '');
|
|
206
137
|
}
|
|
207
|
-
console.log();
|
|
208
138
|
});
|
|
209
139
|
// ── token price ────────────────────────────────────────────────────
|
|
210
140
|
token
|
|
@@ -213,15 +143,15 @@ export function registerTokenCommands(program) {
|
|
|
213
143
|
.argument('<category>', 'Token category ID (64-character hex)')
|
|
214
144
|
.argument('[amount]', 'Token amount in display units (defaults to wallet balance)')
|
|
215
145
|
.option('--chipnet', 'Use chipnet (testnet) instead of mainnet')
|
|
146
|
+
.option('--json', 'Output as JSON')
|
|
216
147
|
.action(async (category, amountStr, opts) => {
|
|
217
148
|
const isChipnet = Boolean(opts.chipnet);
|
|
149
|
+
const asJson = Boolean(opts.json);
|
|
218
150
|
const network = isChipnet ? 'chipnet' : 'mainnet';
|
|
219
|
-
// Validate category format
|
|
220
151
|
if (!/^[a-fA-F0-9]{64}$/.test(category)) {
|
|
221
152
|
console.log(chalk.red('\nError: Category must be a 64-character hex string.\n'));
|
|
222
153
|
process.exit(1);
|
|
223
154
|
}
|
|
224
|
-
// Validate amount if provided
|
|
225
155
|
let requestedAmount = null;
|
|
226
156
|
if (amountStr !== undefined) {
|
|
227
157
|
requestedAmount = Number(amountStr);
|
|
@@ -230,65 +160,44 @@ export function registerTokenCommands(program) {
|
|
|
230
160
|
process.exit(1);
|
|
231
161
|
}
|
|
232
162
|
}
|
|
233
|
-
const data = loadMnemonic();
|
|
234
|
-
if (!data) {
|
|
235
|
-
console.log(chalk.red('\nNo wallet found. Run `paytaca wallet create` or `paytaca wallet import` first.\n'));
|
|
236
|
-
process.exit(1);
|
|
237
|
-
}
|
|
238
|
-
const w = loadWallet();
|
|
239
|
-
const bchWallet = w.forNetwork(isChipnet);
|
|
240
|
-
console.log(chalk.bold(`\n Token Price (${network})\n`));
|
|
241
|
-
// Resolve token metadata (symbol/decimals) — optional, non-fatal
|
|
242
|
-
let symbol = '';
|
|
243
|
-
let decimals = 0;
|
|
244
|
-
let name = '';
|
|
245
163
|
try {
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
displayAmount = balResult.balance / Math.pow(10, decimals);
|
|
164
|
+
const detail = await getTokenDetail(category, isChipnet);
|
|
165
|
+
const displayAmount = requestedAmount !== null
|
|
166
|
+
? requestedAmount
|
|
167
|
+
: detail.rawBalance / Math.pow(10, detail.decimals);
|
|
168
|
+
if (asJson) {
|
|
169
|
+
console.log(JSON.stringify({
|
|
170
|
+
network,
|
|
171
|
+
category,
|
|
172
|
+
symbol: detail.symbol,
|
|
173
|
+
name: detail.name,
|
|
174
|
+
decimals: detail.decimals,
|
|
175
|
+
amount: displayAmount,
|
|
176
|
+
usdPerToken: detail.usdPerToken,
|
|
177
|
+
usd: detail.usdPerToken !== null
|
|
178
|
+
? displayAmount * detail.usdPerToken
|
|
179
|
+
: null,
|
|
180
|
+
}, null, 2));
|
|
181
|
+
return;
|
|
265
182
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
183
|
+
const label = detail.symbol || (detail.name !== 'Unknown Token' ? detail.name : '') || shortHex(category);
|
|
184
|
+
console.log(chalk.bold(`\n Token Price (${network})\n`));
|
|
185
|
+
console.log(` Token: ${label}`);
|
|
186
|
+
if (detail.name !== 'Unknown Token')
|
|
187
|
+
console.log(chalk.dim(` Name: ${detail.name}`));
|
|
188
|
+
console.log(chalk.dim(` Category: ${category}`));
|
|
189
|
+
if (detail.usdPerToken === null) {
|
|
190
|
+
console.log(chalk.yellow('\n No market price available for this token.\n'));
|
|
191
|
+
return;
|
|
269
192
|
}
|
|
193
|
+
const usdValue = displayAmount * detail.usdPerToken;
|
|
194
|
+
console.log(chalk.green(` Price: ${formatUsd(detail.usdPerToken)} per ${detail.symbol || 'token'}`));
|
|
195
|
+
console.log(chalk.bold(` Value: ${formatUsd(usdValue)} for ${displayAmount} ${detail.symbol || 'token(s)'}`));
|
|
196
|
+
console.log();
|
|
270
197
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
try {
|
|
274
|
-
usdPerToken = await getUsdPerToken(category, isChipnet);
|
|
275
|
-
}
|
|
276
|
-
catch {
|
|
277
|
-
// leave null
|
|
278
|
-
}
|
|
279
|
-
const label = symbol || name || shortHex(category);
|
|
280
|
-
console.log(` Token: ${label}`);
|
|
281
|
-
if (name)
|
|
282
|
-
console.log(chalk.dim(` Name: ${name}`));
|
|
283
|
-
console.log(chalk.dim(` Category: ${category}`));
|
|
284
|
-
if (usdPerToken === null) {
|
|
285
|
-
console.log(chalk.yellow('\n No market price available for this token.\n'));
|
|
286
|
-
return;
|
|
198
|
+
catch (err) {
|
|
199
|
+
reportError(err, asJson, '');
|
|
287
200
|
}
|
|
288
|
-
const usdValue = displayAmount * usdPerToken;
|
|
289
|
-
console.log(chalk.green(` Price: ${formatUsd(usdPerToken)} per ${symbol || 'token'}`));
|
|
290
|
-
console.log(chalk.bold(` Value: ${formatUsd(usdValue)} for ${displayAmount} ${symbol || 'token(s)'}`));
|
|
291
|
-
console.log();
|
|
292
201
|
});
|
|
293
202
|
// ── token send ─────────────────────────────────────────────────────
|
|
294
203
|
token
|
|
@@ -298,22 +207,16 @@ export function registerTokenCommands(program) {
|
|
|
298
207
|
.argument('<amount>', 'Token amount to send (in base units, before decimal scaling)')
|
|
299
208
|
.requiredOption('--token <id>', 'Token category ID (64-character hex)')
|
|
300
209
|
.option('--chipnet', 'Use chipnet (testnet) instead of mainnet')
|
|
210
|
+
.option('--json', 'Output as JSON')
|
|
301
211
|
.action(async (address, amountStr, opts) => {
|
|
302
212
|
const isChipnet = Boolean(opts.chipnet);
|
|
213
|
+
const asJson = Boolean(opts.json);
|
|
303
214
|
const category = opts.token;
|
|
304
215
|
const network = isChipnet ? 'chipnet' : 'mainnet';
|
|
305
|
-
// Validate wallet
|
|
306
|
-
const data = loadMnemonic();
|
|
307
|
-
if (!data) {
|
|
308
|
-
console.log(chalk.red('\nNo wallet found. Run `paytaca wallet create` or `paytaca wallet import` first.\n'));
|
|
309
|
-
process.exit(1);
|
|
310
|
-
}
|
|
311
|
-
// Validate category
|
|
312
216
|
if (!/^[a-fA-F0-9]{64}$/.test(category)) {
|
|
313
217
|
console.log(chalk.red('\nError: Category must be a 64-character hex string.\n'));
|
|
314
218
|
process.exit(1);
|
|
315
219
|
}
|
|
316
|
-
// Parse amount as bigint
|
|
317
220
|
let tokenAmount;
|
|
318
221
|
try {
|
|
319
222
|
tokenAmount = BigInt(amountStr);
|
|
@@ -321,70 +224,69 @@ export function registerTokenCommands(program) {
|
|
|
321
224
|
catch {
|
|
322
225
|
console.log(chalk.red('\nError: Amount must be a valid integer.\n'));
|
|
323
226
|
process.exit(1);
|
|
324
|
-
return;
|
|
227
|
+
return;
|
|
325
228
|
}
|
|
326
229
|
if (tokenAmount <= 0n) {
|
|
327
230
|
console.log(chalk.red('\nError: Amount must be positive.\n'));
|
|
328
231
|
process.exit(1);
|
|
329
232
|
}
|
|
330
|
-
|
|
331
|
-
const addressValidator = new Address(address);
|
|
332
|
-
if (!addressValidator.isValidBCHAddress(isChipnet) &&
|
|
333
|
-
!addressValidator.isP2SH?.() &&
|
|
334
|
-
!addressValidator.isTokenAddress?.()) {
|
|
233
|
+
if (!isValidBchAddress(address, isChipnet)) {
|
|
335
234
|
console.log(chalk.red('\nError: Invalid BCH address.\n'));
|
|
336
235
|
process.exit(1);
|
|
337
236
|
}
|
|
338
|
-
|
|
339
|
-
if (!addressValidator.isTokenAddress?.()) {
|
|
237
|
+
if (!isTokenAddress(address) && !asJson) {
|
|
340
238
|
console.log(chalk.yellow('\n Warning: Address is not a token-aware (z-prefix) address.'));
|
|
341
239
|
console.log(chalk.yellow(' Tokens should be sent to token-aware addresses to avoid loss.\n'));
|
|
342
240
|
}
|
|
343
|
-
const w = loadWallet();
|
|
344
|
-
const bchWallet = w.forNetwork(isChipnet);
|
|
345
|
-
// Token change goes to our token address
|
|
346
|
-
const tokenChangeAddress = bchWallet.getTokenAddressSetAt(0).change;
|
|
347
|
-
// Fetch token info for display
|
|
348
|
-
let tokenLabel = shortHex(category);
|
|
349
241
|
try {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
242
|
+
let tokenLabel = shortHex(category);
|
|
243
|
+
try {
|
|
244
|
+
const detail = await getTokenDetail(category, isChipnet);
|
|
245
|
+
if (detail.symbol)
|
|
246
|
+
tokenLabel = detail.symbol;
|
|
247
|
+
else if (detail.name !== 'Unknown Token')
|
|
248
|
+
tokenLabel = detail.name;
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// non-critical
|
|
252
|
+
}
|
|
253
|
+
if (!asJson) {
|
|
254
|
+
console.log(`\n Sending ${chalk.bold(amountStr + ' ' + tokenLabel)} on ${chalk.cyan(network)}`);
|
|
255
|
+
console.log(chalk.dim(` Category: ${category}`));
|
|
256
|
+
console.log(chalk.dim(` To: ${address}`));
|
|
257
|
+
console.log();
|
|
258
|
+
}
|
|
259
|
+
const outcome = await sendToken({ category, amount: tokenAmount, address }, isChipnet);
|
|
260
|
+
if (asJson) {
|
|
261
|
+
console.log(JSON.stringify({
|
|
262
|
+
...outcome,
|
|
263
|
+
address,
|
|
264
|
+
category,
|
|
265
|
+
amount: amountStr,
|
|
266
|
+
}, null, 2));
|
|
267
|
+
if (!outcome.success)
|
|
268
|
+
process.exit(1);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (outcome.success) {
|
|
367
272
|
console.log(chalk.green(' Transaction sent successfully!\n'));
|
|
368
|
-
if (
|
|
369
|
-
console.log(` txid: ${
|
|
370
|
-
|
|
371
|
-
? 'https://chipnet.chaingraph.cash/tx/'
|
|
372
|
-
: 'https://bchexplorer.info/tx/';
|
|
373
|
-
console.log(chalk.dim(` ${explorer}${result.txid}`));
|
|
273
|
+
if (outcome.txid) {
|
|
274
|
+
console.log(` txid: ${outcome.txid}`);
|
|
275
|
+
console.log(chalk.dim(` ${outcome.explorerUrl}`));
|
|
374
276
|
}
|
|
375
277
|
}
|
|
376
278
|
else {
|
|
377
|
-
console.log(chalk.red(` Transaction failed: ${
|
|
378
|
-
if (
|
|
379
|
-
console.log(chalk.yellow(` Insufficient BCH for transaction fees. Short by ${
|
|
279
|
+
console.log(chalk.red(` Transaction failed: ${outcome.error || 'Unknown error'}`));
|
|
280
|
+
if (outcome.lackingSats) {
|
|
281
|
+
console.log(chalk.yellow(` Insufficient BCH for transaction fees. Short by ${outcome.lackingSats} satoshis.`));
|
|
380
282
|
}
|
|
283
|
+
process.exit(1);
|
|
381
284
|
}
|
|
285
|
+
console.log();
|
|
382
286
|
}
|
|
383
287
|
catch (err) {
|
|
384
|
-
|
|
385
|
-
process.exit(1);
|
|
288
|
+
reportError(err, asJson, '');
|
|
386
289
|
}
|
|
387
|
-
console.log();
|
|
388
290
|
});
|
|
389
291
|
// ── token send-nft ─────────────────────────────────────────────────
|
|
390
292
|
token
|
|
@@ -397,48 +299,40 @@ export function registerTokenCommands(program) {
|
|
|
397
299
|
.option('--txid <txid>', 'UTXO txid containing the NFT (auto-detected if omitted)')
|
|
398
300
|
.option('--vout <n>', 'UTXO output index (auto-detected if omitted)')
|
|
399
301
|
.option('--chipnet', 'Use chipnet (testnet) instead of mainnet')
|
|
302
|
+
.option('--json', 'Output as JSON')
|
|
400
303
|
.action(async (address, opts) => {
|
|
401
304
|
const isChipnet = Boolean(opts.chipnet);
|
|
305
|
+
const asJson = Boolean(opts.json);
|
|
402
306
|
const category = opts.token;
|
|
403
307
|
const commitment = opts.commitment;
|
|
404
308
|
const capability = opts.capability || 'none';
|
|
405
309
|
const network = isChipnet ? 'chipnet' : 'mainnet';
|
|
406
|
-
// Validate wallet
|
|
407
|
-
const data = loadMnemonic();
|
|
408
|
-
if (!data) {
|
|
409
|
-
console.log(chalk.red('\nNo wallet found. Run `paytaca wallet create` or `paytaca wallet import` first.\n'));
|
|
410
|
-
process.exit(1);
|
|
411
|
-
}
|
|
412
|
-
// Validate category
|
|
413
310
|
if (!/^[a-fA-F0-9]{64}$/.test(category)) {
|
|
414
311
|
console.log(chalk.red('\nError: Category must be a 64-character hex string.\n'));
|
|
415
312
|
process.exit(1);
|
|
416
313
|
}
|
|
417
|
-
// Validate capability
|
|
418
314
|
if (!['none', 'minting', 'mutable'].includes(capability)) {
|
|
419
315
|
console.log(chalk.red('\nError: Capability must be "none", "minting", or "mutable".\n'));
|
|
420
316
|
process.exit(1);
|
|
421
317
|
}
|
|
422
|
-
|
|
423
|
-
const addressValidator = new Address(address);
|
|
424
|
-
if (!addressValidator.isValidBCHAddress(isChipnet) &&
|
|
425
|
-
!addressValidator.isP2SH?.() &&
|
|
426
|
-
!addressValidator.isTokenAddress?.()) {
|
|
318
|
+
if (!isValidBchAddress(address, isChipnet)) {
|
|
427
319
|
console.log(chalk.red('\nError: Invalid BCH address.\n'));
|
|
428
320
|
process.exit(1);
|
|
429
321
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
try {
|
|
439
|
-
const nfts = await bchWallet.getNftUtxos(category);
|
|
322
|
+
try {
|
|
323
|
+
const ctx = requireWallet(isChipnet);
|
|
324
|
+
let txid = opts.txid || '';
|
|
325
|
+
let vout = opts.vout !== undefined ? parseInt(opts.vout, 10) : -1;
|
|
326
|
+
if (!txid || vout < 0) {
|
|
327
|
+
if (!asJson)
|
|
328
|
+
console.log(chalk.dim('\n Searching for NFT UTXO...'));
|
|
329
|
+
const nfts = await ctx.bch.getNftUtxos(category);
|
|
440
330
|
const match = nfts.find((n) => n.commitment === commitment && n.capability === capability);
|
|
441
331
|
if (!match) {
|
|
332
|
+
if (asJson) {
|
|
333
|
+
console.log(JSON.stringify({ error: `No NFT found matching category ${category}` }, null, 2));
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
442
336
|
console.log(chalk.red(`\n Error: No NFT found matching category ${shortHex(category)} ` +
|
|
443
337
|
`with commitment "${commitment}" and capability "${capability}".\n`));
|
|
444
338
|
console.log(chalk.dim(' Use `paytaca token info <category>` to list available NFTs.\n'));
|
|
@@ -447,30 +341,39 @@ export function registerTokenCommands(program) {
|
|
|
447
341
|
txid = match.txid;
|
|
448
342
|
vout = match.vout;
|
|
449
343
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
344
|
+
const changeAddress = ctx.bch.getTokenAddressSetAt(0).change;
|
|
345
|
+
if (!asJson) {
|
|
346
|
+
console.log(`\n Sending NFT on ${chalk.cyan(network)}`);
|
|
347
|
+
console.log(chalk.dim(` Category: ${category}`));
|
|
348
|
+
console.log(chalk.dim(` Commitment: ${commitment || '(empty)'}`));
|
|
349
|
+
console.log(chalk.dim(` Capability: ${capability}`));
|
|
350
|
+
console.log(chalk.dim(` UTXO: ${shortHex(txid)}:${vout}`));
|
|
351
|
+
console.log(chalk.dim(` To: ${address}`));
|
|
352
|
+
console.log();
|
|
353
|
+
}
|
|
354
|
+
const result = await ctx.bch.sendNft(category, commitment, capability, txid, vout, address, changeAddress);
|
|
355
|
+
const jsonResult = {
|
|
356
|
+
network,
|
|
357
|
+
success: result.success,
|
|
358
|
+
txid: result.txid,
|
|
359
|
+
explorerUrl: result.txid ? explorerTxUrl(result.txid, isChipnet) : undefined,
|
|
360
|
+
error: result.error,
|
|
361
|
+
category,
|
|
362
|
+
commitment,
|
|
363
|
+
capability,
|
|
364
|
+
address,
|
|
365
|
+
};
|
|
366
|
+
if (asJson) {
|
|
367
|
+
console.log(JSON.stringify(jsonResult, null, 2));
|
|
368
|
+
if (!result.success)
|
|
369
|
+
process.exit(1);
|
|
370
|
+
return;
|
|
453
371
|
}
|
|
454
|
-
}
|
|
455
|
-
// Change address for leftover BCH
|
|
456
|
-
const changeAddress = bchWallet.getTokenAddressSetAt(0).change;
|
|
457
|
-
console.log(`\n Sending NFT on ${chalk.cyan(network)}`);
|
|
458
|
-
console.log(chalk.dim(` Category: ${category}`));
|
|
459
|
-
console.log(chalk.dim(` Commitment: ${commitment || '(empty)'}`));
|
|
460
|
-
console.log(chalk.dim(` Capability: ${capability}`));
|
|
461
|
-
console.log(chalk.dim(` UTXO: ${shortHex(txid)}:${vout}`));
|
|
462
|
-
console.log(chalk.dim(` To: ${address}`));
|
|
463
|
-
console.log();
|
|
464
|
-
try {
|
|
465
|
-
const result = await bchWallet.sendNft(category, commitment, capability, txid, vout, address, changeAddress);
|
|
466
372
|
if (result.success) {
|
|
467
373
|
console.log(chalk.green(' Transaction sent successfully!\n'));
|
|
468
374
|
if (result.txid) {
|
|
469
375
|
console.log(` txid: ${result.txid}`);
|
|
470
|
-
|
|
471
|
-
? 'https://chipnet.chaingraph.cash/tx/'
|
|
472
|
-
: 'https://bchexplorer.info/tx/';
|
|
473
|
-
console.log(chalk.dim(` ${explorer}${result.txid}`));
|
|
376
|
+
console.log(chalk.dim(` ${explorerTxUrl(result.txid, isChipnet)}`));
|
|
474
377
|
}
|
|
475
378
|
}
|
|
476
379
|
else {
|
|
@@ -478,13 +381,13 @@ export function registerTokenCommands(program) {
|
|
|
478
381
|
if (result.lackingSats) {
|
|
479
382
|
console.log(chalk.yellow(` Insufficient BCH for transaction fees. Short by ${result.lackingSats} satoshis.`));
|
|
480
383
|
}
|
|
384
|
+
process.exit(1);
|
|
481
385
|
}
|
|
386
|
+
console.log();
|
|
482
387
|
}
|
|
483
388
|
catch (err) {
|
|
484
|
-
|
|
485
|
-
process.exit(1);
|
|
389
|
+
reportError(err, asJson, '');
|
|
486
390
|
}
|
|
487
|
-
console.log();
|
|
488
391
|
});
|
|
489
392
|
}
|
|
490
393
|
//# sourceMappingURL=token.js.map
|