nansen-cli 1.22.0 → 1.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/README.md +2 -0
- package/package.json +1 -1
- package/skills/nansen-agent-guide/SKILL.md +58 -0
- package/skills/nansen-smart-alerts/SKILL.md +4 -1
- package/src/api.js +15 -1
- package/src/cli.js +6 -5
- package/src/commands/alerts.js +18 -4
- package/src/schema.json +21 -0
- package/src/trade-validation.js +54 -0
- package/src/trading.js +13 -9
- package/src/wallet.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.23.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#361](https://github.com/nansen-ai/nansen-cli/pull/361) [`ff22da3`](https://github.com/nansen-ai/nansen-cli/commit/ff22da376c1072ef06e84054f6ee31c058e6e08c) Thanks [@TimNooren](https://github.com/TimNooren)! - fix(alerts): error when --webhook-secret is passed without --webhook
|
|
8
|
+
|
|
9
|
+
Previously, passing --webhook-secret with a non-webhook channel (e.g. --telegram)
|
|
10
|
+
silently discarded the secret with no warning. The alert was created successfully
|
|
11
|
+
but without any signing, giving the false impression that the secret was active.
|
|
12
|
+
|
|
13
|
+
Now throws an actionable error: "--webhook-secret requires --webhook".
|
|
14
|
+
|
|
15
|
+
- [#358](https://github.com/nansen-ai/nansen-cli/pull/358) [`70ee712`](https://github.com/nansen-ai/nansen-cli/commit/70ee71205343fb2d003eb5f50144e266bdc6109e) Thanks [@TimNooren](https://github.com/TimNooren)! - Add pre-quote trade input validation: rejects same-token swaps, invalid address formats, and non-positive amounts before any network call.
|
|
16
|
+
|
|
17
|
+
## 1.23.0
|
|
18
|
+
|
|
19
|
+
### Minor Changes
|
|
20
|
+
|
|
21
|
+
- [#341](https://github.com/nansen-ai/nansen-cli/pull/341) [`4b60056`](https://github.com/nansen-ai/nansen-cli/commit/4b6005697d52b5d432b9b32bcd1d36422f9166cc) Thanks [@gulshngill](https://github.com/gulshngill)! - Add `--webhook <url>` and `--webhook-secret <secret>` flags to `alerts create` and `alerts update`.
|
|
22
|
+
|
|
23
|
+
Allows alerts to be delivered to any HTTP/HTTPS endpoint via POST, alongside
|
|
24
|
+
the existing `--telegram`, `--slack`, and `--discord` channels. The optional
|
|
25
|
+
`--webhook-secret` enables HMAC payload signing for verification.
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- [#344](https://github.com/nansen-ai/nansen-cli/pull/344) [`3dc09cc`](https://github.com/nansen-ai/nansen-cli/commit/3dc09cc8aa38cd4da4ae305f83e9599efb3b9ff9) Thanks [@0xlaveen](https://github.com/0xlaveen)! - Add nansen-agent-guide skill — routing guide for when to use `nansen agent` vs direct CLI data commands
|
|
30
|
+
|
|
31
|
+
- [#347](https://github.com/nansen-ai/nansen-cli/pull/347) [`a243c7a`](https://github.com/nansen-ai/nansen-cli/commit/a243c7a33f28057949d2060c732083642422eb18) Thanks [@kome12](https://github.com/kome12)! - Add --buy-or-sell option to `token who-bought-sold` command — allows filtering by buy or sell side (BUY | SELL, defaults to BUY)
|
|
32
|
+
|
|
3
33
|
## 1.22.0
|
|
4
34
|
|
|
5
35
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -27,6 +27,8 @@ Get your API key at [app.nansen.ai/auth/agent-setup](https://app.nansen.ai/auth/
|
|
|
27
27
|
|
|
28
28
|
```
|
|
29
29
|
nansen research <category> <subcommand> [options]
|
|
30
|
+
nansen agent "<question>" # AI research agent (200 credits, Pro)
|
|
31
|
+
nansen agent "<question>" --expert # deeper analysis (750 credits, Pro)
|
|
30
32
|
nansen trade <subcommand> [options]
|
|
31
33
|
nansen wallet <subcommand> [options]
|
|
32
34
|
nansen schema [command] [--pretty] # full command reference (no API key needed)
|
package/package.json
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nansen-agent-guide
|
|
3
|
+
description: Routing guide -- when to use `nansen agent` (AI research) vs direct CLI data commands. Use when deciding how to answer a user's research question with Nansen tools.
|
|
4
|
+
metadata:
|
|
5
|
+
openclaw:
|
|
6
|
+
requires:
|
|
7
|
+
env:
|
|
8
|
+
- NANSEN_API_KEY
|
|
9
|
+
bins:
|
|
10
|
+
- nansen
|
|
11
|
+
primaryEnv: NANSEN_API_KEY
|
|
12
|
+
install:
|
|
13
|
+
- kind: node
|
|
14
|
+
package: nansen-cli
|
|
15
|
+
bins: [nansen]
|
|
16
|
+
allowed-tools: Bash(nansen:*)
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Agent vs CLI Routing
|
|
20
|
+
|
|
21
|
+
| Need a... | Use |
|
|
22
|
+
|-----------|-----|
|
|
23
|
+
| **take** (analysis, interpretation) | `nansen agent` |
|
|
24
|
+
| **table** (raw data, specific metrics) | Direct CLI commands |
|
|
25
|
+
| **report** (both) | Agent for narrative + CLI for data |
|
|
26
|
+
|
|
27
|
+
## Use `nansen agent` when
|
|
28
|
+
|
|
29
|
+
- Question requires interpretation or synthesis across multiple data sources
|
|
30
|
+
- Open-ended research: "analyse this wallet", "what's happening with ETH smart money?"
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
nansen agent "What are top smart money tokens on Solana today and why?"
|
|
34
|
+
nansen agent "Analyse wallet 0x123... -- is this a smart trader?"
|
|
35
|
+
nansen agent "..." --expert # deeper analysis, 750 credits
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Cost: 200 credits (fast) / 750 credits (expert)
|
|
39
|
+
|
|
40
|
+
## Use direct CLI commands when
|
|
41
|
+
|
|
42
|
+
- You need specific structured data -- prices, volumes, holders, flows
|
|
43
|
+
- Deterministic question: "top 10 tokens by netflow on ethereum"
|
|
44
|
+
- Piping output or building a data table
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
nansen research token screener --chain ethereum --smart-money --limit 10
|
|
48
|
+
nansen research smart-money netflow --chain solana
|
|
49
|
+
nansen research profiler balance --address 0x123... --chain ethereum
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Cost: 5-50 credits per call
|
|
53
|
+
|
|
54
|
+
## Anti-patterns
|
|
55
|
+
|
|
56
|
+
- Don't use `nansen agent` for simple data fetches -- 40x more expensive
|
|
57
|
+
- Don't use raw CLI for open-ended analysis -- returns data, not interpretation
|
|
58
|
+
- Don't chain 3+ agent calls -- get raw data via CLI, call agent once for synthesis
|
|
@@ -41,6 +41,8 @@ nansen alerts delete <id>
|
|
|
41
41
|
| `--telegram` | chat ID | optional | | |
|
|
42
42
|
| `--slack` | webhook URL | optional | | |
|
|
43
43
|
| `--discord` | webhook URL | optional | | |
|
|
44
|
+
| `--webhook` | endpoint URL | optional | optional | |
|
|
45
|
+
| `--webhook-secret` | optional (webhook only) | optional | | |
|
|
44
46
|
| `--description` | optional | optional | | |
|
|
45
47
|
| `--enabled` | | flag | flag | |
|
|
46
48
|
| `--disabled` | flag | flag | flag | |
|
|
@@ -131,7 +133,8 @@ nansen alerts create \
|
|
|
131
133
|
## Notes
|
|
132
134
|
|
|
133
135
|
- Chain aliases: Hyperliquid = `hyperevm`, BSC = `bnb`.
|
|
134
|
-
- Multiple channels can be combined: `--telegram 123 --slack https://...`
|
|
136
|
+
- Multiple channels can be combined: `--telegram 123 --slack https://... --webhook https://...`
|
|
137
|
+
- `--webhook <url>` sends a POST request with the alert payload to any HTTP/HTTPS endpoint. Useful for server deployments, Zapier, n8n, or custom integrations. The endpoint must be publicly reachable and return a 2xx response.
|
|
135
138
|
- `--data '<json>'` merges raw JSON on top of named flags (escape hatch for fields without named flags).
|
|
136
139
|
- Alert endpoints are internal-only. Non-internal users receive 404.
|
|
137
140
|
- Use single quotes for names with `$` or special characters: `--name 'SM >$1M'`
|
package/src/api.js
CHANGED
|
@@ -293,6 +293,19 @@ export function validateAddress(address, chain = 'ethereum') {
|
|
|
293
293
|
return { valid: true };
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Normalize EVM address to lowercase for API compatibility.
|
|
298
|
+
* The API should handle case-insensitive addresses server-side, but this is
|
|
299
|
+
* a defensive client-side measure since checksummed addresses currently
|
|
300
|
+
* return empty results.
|
|
301
|
+
*/
|
|
302
|
+
export function normalizeAddress(address, chain = 'ethereum') {
|
|
303
|
+
if (address && typeof address === 'string' && address.startsWith('0x') && EVM_CHAINS.includes(chain)) {
|
|
304
|
+
return address.toLowerCase();
|
|
305
|
+
}
|
|
306
|
+
return address;
|
|
307
|
+
}
|
|
308
|
+
|
|
296
309
|
/**
|
|
297
310
|
* Validate token address (same rules as wallet address)
|
|
298
311
|
*/
|
|
@@ -1030,7 +1043,7 @@ export class NansenAPI {
|
|
|
1030
1043
|
}
|
|
1031
1044
|
|
|
1032
1045
|
async tokenWhoBoughtSold(params = {}) {
|
|
1033
|
-
const { tokenAddress, chain = 'solana', filters = {}, orderBy, pagination, days = 30, date } = params;
|
|
1046
|
+
const { tokenAddress, chain = 'solana', buyOrSell = 'BUY', filters = {}, orderBy, pagination, days = 30, date } = params;
|
|
1034
1047
|
if (tokenAddress) {
|
|
1035
1048
|
const validation = validateTokenAddress(tokenAddress, chain);
|
|
1036
1049
|
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
@@ -1039,6 +1052,7 @@ export class NansenAPI {
|
|
|
1039
1052
|
return this.request('/api/v1/tgm/who-bought-sold', {
|
|
1040
1053
|
token_address: tokenAddress,
|
|
1041
1054
|
chain,
|
|
1055
|
+
buy_or_sell: buyOrSell,
|
|
1042
1056
|
date: dateRange,
|
|
1043
1057
|
filters,
|
|
1044
1058
|
order_by: orderBy,
|
package/src/cli.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Extracted from index.js for coverage
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { NansenAPI, NansenError, ErrorCode, saveConfig, deleteConfig, getConfigFile, clearCache, getCacheDir, validateAddress, sleep } from './api.js';
|
|
6
|
+
import { NansenAPI, NansenError, ErrorCode, saveConfig, deleteConfig, getConfigFile, clearCache, getCacheDir, validateAddress, normalizeAddress, sleep } from './api.js';
|
|
7
7
|
import { buildWalletCommands } from './wallet.js';
|
|
8
8
|
import { buildTradingCommands } from './trading.js';
|
|
9
9
|
import { formatAlertsTable, buildAlertsCommands } from './commands/alerts.js';
|
|
@@ -870,7 +870,7 @@ export function buildCommands(deps = {}) {
|
|
|
870
870
|
return;
|
|
871
871
|
}
|
|
872
872
|
|
|
873
|
-
let apiKey = options['api-key']
|
|
873
|
+
let apiKey = options['api-key'];
|
|
874
874
|
|
|
875
875
|
if (!apiKey) {
|
|
876
876
|
apiKey = process.env.NANSEN_API_KEY;
|
|
@@ -1187,9 +1187,9 @@ export function buildCommands(deps = {}) {
|
|
|
1187
1187
|
|
|
1188
1188
|
'token': async (args, apiInstance, flags, options) => {
|
|
1189
1189
|
const subcommand = args[0] || 'help';
|
|
1190
|
-
const tokenAddress = options.token || options['token-address'];
|
|
1191
|
-
const tokenSymbol = options.symbol || options['token-symbol'];
|
|
1192
1190
|
const chain = options.chain || 'solana';
|
|
1191
|
+
const tokenAddress = normalizeAddress(options.token || options['token-address'], chain);
|
|
1192
|
+
const tokenSymbol = options.symbol || options['token-symbol'];
|
|
1193
1193
|
const chains = options.chains || [chain];
|
|
1194
1194
|
const timeframe = options.timeframe || '24h';
|
|
1195
1195
|
const filters = options.filters || {};
|
|
@@ -1247,7 +1247,8 @@ export function buildCommands(deps = {}) {
|
|
|
1247
1247
|
'pnl': () => apiInstance.tokenPnlLeaderboard({ tokenAddress, chain, filters, orderBy, pagination, days }),
|
|
1248
1248
|
'who-bought-sold': () => {
|
|
1249
1249
|
const date = parseDateOption(options.date, days);
|
|
1250
|
-
|
|
1250
|
+
const buyOrSell = (options['buy-or-sell'] || 'BUY').toUpperCase();
|
|
1251
|
+
return apiInstance.tokenWhoBoughtSold({ tokenAddress, chain, buyOrSell, filters, orderBy, pagination, days, date });
|
|
1251
1252
|
},
|
|
1252
1253
|
'flow-intelligence': () => apiInstance.tokenFlowIntelligence({ tokenAddress, chain, days }),
|
|
1253
1254
|
'transfers': () => {
|
package/src/commands/alerts.js
CHANGED
|
@@ -484,10 +484,11 @@ USAGE:
|
|
|
484
484
|
REQUIRED:
|
|
485
485
|
--name <name> Alert name
|
|
486
486
|
--type <type> sm-token-flows | common-token-transfer | smart-contract-call
|
|
487
|
-
At least one channel: --telegram <chatId> | --slack <url> | --discord <url>
|
|
487
|
+
At least one channel: --telegram <chatId> | --slack <url> | --discord <url> | --webhook <url>
|
|
488
488
|
|
|
489
489
|
OPTIONS (all types):
|
|
490
490
|
--chains <chains> Comma-separated chains (e.g. ethereum,solana)
|
|
491
|
+
--webhook-secret <secret> Signing secret for webhook payload verification (webhook only)
|
|
491
492
|
--token <address:chain> Include token (repeatable)
|
|
492
493
|
--exclude-token <addr:chain> Exclude token (repeatable)
|
|
493
494
|
--description '<text>' Alert description
|
|
@@ -559,12 +560,23 @@ USAGE:
|
|
|
559
560
|
return;
|
|
560
561
|
}
|
|
561
562
|
|
|
562
|
-
// Build channels array from --telegram/--slack/--discord flags
|
|
563
|
+
// Build channels array from --telegram/--slack/--discord/--webhook flags
|
|
563
564
|
function buildChannels() {
|
|
565
|
+
if (options["webhook-secret"] && !options.webhook) {
|
|
566
|
+
throw new NansenError('--webhook-secret requires --webhook', ErrorCode.INVALID_PARAMS);
|
|
567
|
+
}
|
|
564
568
|
const channels = [];
|
|
565
569
|
if (options.telegram) channels.push({ type: 'telegram', data: { chatId: String(options.telegram) } });
|
|
566
570
|
if (options.slack) channels.push({ type: 'slack', data: { webhookUrl: options.slack } });
|
|
567
571
|
if (options.discord) channels.push({ type: 'discord', data: { webhookUrl: options.discord } });
|
|
572
|
+
if (options.webhook) {
|
|
573
|
+
const webhookData = { webhookUrl: options.webhook };
|
|
574
|
+
if (options["webhook-secret"]) {
|
|
575
|
+
if (options["webhook-secret"].length < 16) throw new NansenError('--webhook-secret must be at least 16 characters', ErrorCode.INVALID_PARAMS);
|
|
576
|
+
webhookData.secret = options["webhook-secret"];
|
|
577
|
+
}
|
|
578
|
+
channels.push({ type: 'webhook', data: webhookData });
|
|
579
|
+
}
|
|
568
580
|
return channels.length > 0 ? channels : null;
|
|
569
581
|
}
|
|
570
582
|
|
|
@@ -608,7 +620,7 @@ USAGE:
|
|
|
608
620
|
if (!name) missing.push('--name');
|
|
609
621
|
if (!type) missing.push('--type');
|
|
610
622
|
if (!options.chains) missing.push('--chains');
|
|
611
|
-
if (!channels) missing.push('a channel (--telegram, --slack, or --
|
|
623
|
+
if (!channels) missing.push('a channel (--telegram, --slack, --discord, or --webhook)');
|
|
612
624
|
if (missing.length > 0) {
|
|
613
625
|
throw new NansenError(`Required: ${missing.join(', ')}`, ErrorCode.MISSING_PARAM);
|
|
614
626
|
}
|
|
@@ -707,7 +719,9 @@ USAGE:
|
|
|
707
719
|
? `Invalid Slack webhook URL. Check the URL and try again.`
|
|
708
720
|
: ch?.type === 'discord'
|
|
709
721
|
? `Invalid Discord webhook URL. Check the URL and try again.`
|
|
710
|
-
:
|
|
722
|
+
: ch?.type === 'webhook'
|
|
723
|
+
? `Invalid webhook URL (${ch.data.webhookUrl}). Ensure the endpoint is reachable and returns 2xx.`
|
|
724
|
+
: err.message;
|
|
711
725
|
throw new NansenError(hint, err.code ?? ErrorCode.INVALID_PARAMS, err.status);
|
|
712
726
|
}
|
|
713
727
|
throw err;
|
package/src/schema.json
CHANGED
|
@@ -300,6 +300,11 @@
|
|
|
300
300
|
},
|
|
301
301
|
"days": {
|
|
302
302
|
"default": 30
|
|
303
|
+
},
|
|
304
|
+
"buy-or-sell": {
|
|
305
|
+
"description": "Filter by buy or sell side",
|
|
306
|
+
"enum": ["BUY", "SELL"],
|
|
307
|
+
"default": "BUY"
|
|
303
308
|
}
|
|
304
309
|
}
|
|
305
310
|
},
|
|
@@ -669,6 +674,14 @@
|
|
|
669
674
|
"type": "string",
|
|
670
675
|
"description": "Discord webhook URL for notifications"
|
|
671
676
|
},
|
|
677
|
+
"webhook": {
|
|
678
|
+
"type": "string",
|
|
679
|
+
"description": "HTTP/HTTPS endpoint URL to POST alert payloads to"
|
|
680
|
+
},
|
|
681
|
+
"webhook-secret": {
|
|
682
|
+
"type": "string",
|
|
683
|
+
"description": "Signing secret for webhook payload verification (optional, webhook only)"
|
|
684
|
+
},
|
|
672
685
|
"data": {
|
|
673
686
|
"type": "string",
|
|
674
687
|
"description": "Alert config JSON. --chains is merged on top."
|
|
@@ -710,6 +723,14 @@
|
|
|
710
723
|
"type": "string",
|
|
711
724
|
"description": "Discord webhook URL"
|
|
712
725
|
},
|
|
726
|
+
"webhook": {
|
|
727
|
+
"type": "string",
|
|
728
|
+
"description": "HTTP/HTTPS endpoint URL to POST alert payloads to"
|
|
729
|
+
},
|
|
730
|
+
"webhook-secret": {
|
|
731
|
+
"type": "string",
|
|
732
|
+
"description": "Signing secret for webhook payload verification (optional, webhook only)"
|
|
733
|
+
},
|
|
713
734
|
"data": {
|
|
714
735
|
"type": "string",
|
|
715
736
|
"description": "Alert config JSON. --chains merged on top."
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trade input validation for the Nansen CLI.
|
|
3
|
+
* Catches common agent errors (wrong addresses, same-token swaps,
|
|
4
|
+
* bad amounts) before any network call.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { validateAddress } from './api.js';
|
|
8
|
+
|
|
9
|
+
const SUPPORTED_CHAINS = ['solana', 'base'];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validate quote inputs before any network call.
|
|
13
|
+
* Throws on validation failure with an actionable error message.
|
|
14
|
+
*/
|
|
15
|
+
export function validateQuoteInput({ chain, from, to, amount }) {
|
|
16
|
+
// 1. Chain must be supported
|
|
17
|
+
const normalizedChain = chain?.toLowerCase();
|
|
18
|
+
if (!SUPPORTED_CHAINS.includes(normalizedChain)) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`Unsupported chain "${chain}". Supported chains: ${SUPPORTED_CHAINS.join(', ')}.`
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 2. Amount must be a positive finite number
|
|
25
|
+
const numAmount = Number(amount);
|
|
26
|
+
if (!Number.isFinite(numAmount) || numAmount <= 0) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`Invalid amount "${amount}". Must be a positive number.`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 3. Token address format must match the chain (reuses api.js validateAddress)
|
|
33
|
+
const fromResult = validateAddress(from, normalizedChain);
|
|
34
|
+
if (!fromResult.valid) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Invalid sell token address for ${normalizedChain}. ${fromResult.error}`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
const toResult = validateAddress(to, normalizedChain);
|
|
40
|
+
if (!toResult.valid) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`Invalid buy token address for ${normalizedChain}. ${toResult.error}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 4. Sell and buy tokens must be different
|
|
47
|
+
const fromNorm = normalizedChain === 'solana' ? from : from.toLowerCase();
|
|
48
|
+
const toNorm = normalizedChain === 'solana' ? to : to.toLowerCase();
|
|
49
|
+
if (fromNorm === toNorm) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Cannot swap ${from} for itself. Sell and buy tokens must be different.`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/trading.js
CHANGED
|
@@ -13,6 +13,7 @@ import { base58Decode } from './transfer.js';
|
|
|
13
13
|
import { keccak256, signSecp256k1, rlpEncode } from './crypto.js';
|
|
14
14
|
import { getWalletConnectAddress, sendTransactionViaWalletConnect, sendSolanaTransactionViaWalletConnect, sendApprovalViaWalletConnect } from './walletconnect-trading.js';
|
|
15
15
|
import { retrievePassword } from './keychain.js';
|
|
16
|
+
import { validateQuoteInput } from './trade-validation.js';
|
|
16
17
|
import { CHAIN_RPCS } from './rpc-urls.js';
|
|
17
18
|
|
|
18
19
|
// ============= Constants =============
|
|
@@ -115,9 +116,6 @@ export async function getQuote(params) {
|
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
const headers = { 'Accept': 'application/json' };
|
|
118
|
-
if (process.env.NANSEN_API_KEY) {
|
|
119
|
-
headers['Authorization'] = `Bearer ${process.env.NANSEN_API_KEY}`;
|
|
120
|
-
}
|
|
121
119
|
|
|
122
120
|
const res = await fetch(url.toString(), { headers });
|
|
123
121
|
|
|
@@ -156,10 +154,6 @@ export async function executeTransaction(params, { retries = 2, retryDelayMs = 1
|
|
|
156
154
|
'Content-Type': 'application/json',
|
|
157
155
|
'Accept': 'application/json',
|
|
158
156
|
};
|
|
159
|
-
if (process.env.NANSEN_API_KEY) {
|
|
160
|
-
headers['Authorization'] = `Bearer ${process.env.NANSEN_API_KEY}`;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
157
|
let lastError;
|
|
164
158
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
165
159
|
if (attempt > 0) {
|
|
@@ -841,7 +835,7 @@ export function buildTradingCommands(deps = {}) {
|
|
|
841
835
|
const amount = options.amount || args[3];
|
|
842
836
|
const walletName = options.wallet;
|
|
843
837
|
const slippage = options.slippage;
|
|
844
|
-
const autoSlippage = flags['auto-slippage']
|
|
838
|
+
const autoSlippage = flags['auto-slippage'];
|
|
845
839
|
const maxAutoSlippage = options['max-auto-slippage'];
|
|
846
840
|
const swapMode = options['swap-mode'] || 'exactIn';
|
|
847
841
|
const amountUnit = options['amount-unit'];
|
|
@@ -884,6 +878,16 @@ EXAMPLES:
|
|
|
884
878
|
return;
|
|
885
879
|
}
|
|
886
880
|
|
|
881
|
+
// Static input validation — catches common agent errors (wrong addresses,
|
|
882
|
+
// same-token swaps, bad amounts) before any network or wallet call.
|
|
883
|
+
try {
|
|
884
|
+
validateQuoteInput({ chain, from, to, amount });
|
|
885
|
+
} catch (validationErr) {
|
|
886
|
+
log(`Error: ${validationErr.message}`);
|
|
887
|
+
exit(1);
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
|
|
887
891
|
// When --amount-unit token is used, resolve decimals and convert to base units.
|
|
888
892
|
// Otherwise, validate that the amount is already in base units (integer).
|
|
889
893
|
let resolvedAmount = amount;
|
|
@@ -1013,7 +1017,7 @@ EXAMPLES:
|
|
|
1013
1017
|
'execute': async (args, apiInstance, flags, options) => {
|
|
1014
1018
|
const quoteId = options.quote || options['quote-id'] || args[0];
|
|
1015
1019
|
const walletName = options.wallet;
|
|
1016
|
-
const noSimulate = flags['no-simulate']
|
|
1020
|
+
const noSimulate = flags['no-simulate'];
|
|
1017
1021
|
|
|
1018
1022
|
if (!quoteId) {
|
|
1019
1023
|
log(`
|