outsmart 2.0.0-alpha.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/LICENSE +21 -0
- package/README.md +568 -0
- package/dist/cli.d.ts +44 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1251 -0
- package/dist/cli.js.map +1 -0
- package/dist/dex/byreal-clmm.d.ts +16 -0
- package/dist/dex/byreal-clmm.d.ts.map +1 -0
- package/dist/dex/byreal-clmm.js +39 -0
- package/dist/dex/byreal-clmm.js.map +1 -0
- package/dist/dex/dflow.d.ts +27 -0
- package/dist/dex/dflow.d.ts.map +1 -0
- package/dist/dex/dflow.js +200 -0
- package/dist/dex/dflow.js.map +1 -0
- package/dist/dex/fusion-amm.d.ts +44 -0
- package/dist/dex/fusion-amm.d.ts.map +1 -0
- package/dist/dex/fusion-amm.js +546 -0
- package/dist/dex/fusion-amm.js.map +1 -0
- package/dist/dex/futarchy-amm.d.ts +32 -0
- package/dist/dex/futarchy-amm.d.ts.map +1 -0
- package/dist/dex/futarchy-amm.js +443 -0
- package/dist/dex/futarchy-amm.js.map +1 -0
- package/dist/dex/futarchy-idl.d.ts +2568 -0
- package/dist/dex/futarchy-idl.d.ts.map +1 -0
- package/dist/dex/futarchy-idl.js +2570 -0
- package/dist/dex/futarchy-idl.js.map +1 -0
- package/dist/dex/futarchy-launchpad.d.ts +68 -0
- package/dist/dex/futarchy-launchpad.d.ts.map +1 -0
- package/dist/dex/futarchy-launchpad.js +377 -0
- package/dist/dex/futarchy-launchpad.js.map +1 -0
- package/dist/dex/index.d.ts +88 -0
- package/dist/dex/index.d.ts.map +1 -0
- package/dist/dex/index.js +159 -0
- package/dist/dex/index.js.map +1 -0
- package/dist/dex/jupiter-ultra.d.ts +27 -0
- package/dist/dex/jupiter-ultra.d.ts.map +1 -0
- package/dist/dex/jupiter-ultra.js +369 -0
- package/dist/dex/jupiter-ultra.js.map +1 -0
- package/dist/dex/meteora-damm-v1.d.ts +36 -0
- package/dist/dex/meteora-damm-v1.d.ts.map +1 -0
- package/dist/dex/meteora-damm-v1.js +314 -0
- package/dist/dex/meteora-damm-v1.js.map +1 -0
- package/dist/dex/meteora-damm-v2.d.ts +103 -0
- package/dist/dex/meteora-damm-v2.d.ts.map +1 -0
- package/dist/dex/meteora-damm-v2.js +1146 -0
- package/dist/dex/meteora-damm-v2.js.map +1 -0
- package/dist/dex/meteora-dbc.d.ts +38 -0
- package/dist/dex/meteora-dbc.d.ts.map +1 -0
- package/dist/dex/meteora-dbc.js +374 -0
- package/dist/dex/meteora-dbc.js.map +1 -0
- package/dist/dex/meteora-dlmm.d.ts +79 -0
- package/dist/dex/meteora-dlmm.d.ts.map +1 -0
- package/dist/dex/meteora-dlmm.js +735 -0
- package/dist/dex/meteora-dlmm.js.map +1 -0
- package/dist/dex/orca.d.ts +31 -0
- package/dist/dex/orca.d.ts.map +1 -0
- package/dist/dex/orca.js +536 -0
- package/dist/dex/orca.js.map +1 -0
- package/dist/dex/pancakeswap-clmm.d.ts +16 -0
- package/dist/dex/pancakeswap-clmm.d.ts.map +1 -0
- package/dist/dex/pancakeswap-clmm.js +39 -0
- package/dist/dex/pancakeswap-clmm.js.map +1 -0
- package/dist/dex/pumpfun-amm.d.ts +46 -0
- package/dist/dex/pumpfun-amm.d.ts.map +1 -0
- package/dist/dex/pumpfun-amm.js +692 -0
- package/dist/dex/pumpfun-amm.js.map +1 -0
- package/dist/dex/pumpfun.d.ts +41 -0
- package/dist/dex/pumpfun.d.ts.map +1 -0
- package/dist/dex/pumpfun.js +555 -0
- package/dist/dex/pumpfun.js.map +1 -0
- package/dist/dex/raydium-amm-v4.d.ts +11 -0
- package/dist/dex/raydium-amm-v4.d.ts.map +1 -0
- package/dist/dex/raydium-amm-v4.js +649 -0
- package/dist/dex/raydium-amm-v4.js.map +1 -0
- package/dist/dex/raydium-clmm.d.ts +12 -0
- package/dist/dex/raydium-clmm.d.ts.map +1 -0
- package/dist/dex/raydium-clmm.js +675 -0
- package/dist/dex/raydium-clmm.js.map +1 -0
- package/dist/dex/raydium-cpmm.d.ts +10 -0
- package/dist/dex/raydium-cpmm.d.ts.map +1 -0
- package/dist/dex/raydium-cpmm.js +613 -0
- package/dist/dex/raydium-cpmm.js.map +1 -0
- package/dist/dex/raydium-launchlab.d.ts +12 -0
- package/dist/dex/raydium-launchlab.d.ts.map +1 -0
- package/dist/dex/raydium-launchlab.js +530 -0
- package/dist/dex/raydium-launchlab.js.map +1 -0
- package/dist/dex/shared/clmm-base.d.ts +58 -0
- package/dist/dex/shared/clmm-base.d.ts.map +1 -0
- package/dist/dex/shared/clmm-base.js +891 -0
- package/dist/dex/shared/clmm-base.js.map +1 -0
- package/dist/dex/types.d.ts +601 -0
- package/dist/dex/types.d.ts.map +1 -0
- package/dist/dex/types.js +137 -0
- package/dist/dex/types.js.map +1 -0
- package/dist/dexscreener/index.d.ts +2 -0
- package/dist/dexscreener/index.d.ts.map +1 -0
- package/dist/dexscreener/index.js +18 -0
- package/dist/dexscreener/index.js.map +1 -0
- package/dist/dexscreener/info.d.ts +22 -0
- package/dist/dexscreener/info.d.ts.map +1 -0
- package/dist/dexscreener/info.js +104 -0
- package/dist/dexscreener/info.js.map +1 -0
- package/dist/helpers/check_balance.d.ts +10 -0
- package/dist/helpers/check_balance.d.ts.map +1 -0
- package/dist/helpers/check_balance.js +34 -0
- package/dist/helpers/check_balance.js.map +1 -0
- package/dist/helpers/config.d.ts +51 -0
- package/dist/helpers/config.d.ts.map +1 -0
- package/dist/helpers/config.js +118 -0
- package/dist/helpers/config.js.map +1 -0
- package/dist/helpers/index.d.ts +8 -0
- package/dist/helpers/index.d.ts.map +1 -0
- package/dist/helpers/index.js +29 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/helpers/logger.d.ts +27 -0
- package/dist/helpers/logger.d.ts.map +1 -0
- package/dist/helpers/logger.js +39 -0
- package/dist/helpers/logger.js.map +1 -0
- package/dist/helpers/token-2022.d.ts +32 -0
- package/dist/helpers/token-2022.d.ts.map +1 -0
- package/dist/helpers/token-2022.js +48 -0
- package/dist/helpers/token-2022.js.map +1 -0
- package/dist/helpers/unwrap_sol.d.ts +2 -0
- package/dist/helpers/unwrap_sol.d.ts.map +1 -0
- package/dist/helpers/unwrap_sol.js +67 -0
- package/dist/helpers/unwrap_sol.js.map +1 -0
- package/dist/helpers/util.d.ts +698 -0
- package/dist/helpers/util.d.ts.map +1 -0
- package/dist/helpers/util.js +181 -0
- package/dist/helpers/util.js.map +1 -0
- package/dist/helpers/utils.d.ts +10 -0
- package/dist/helpers/utils.d.ts.map +1 -0
- package/dist/helpers/utils.js +97 -0
- package/dist/helpers/utils.js.map +1 -0
- package/dist/helpers/wrap_sol.d.ts +3 -0
- package/dist/helpers/wrap_sol.d.ts.map +1 -0
- package/dist/helpers/wrap_sol.js +88 -0
- package/dist/helpers/wrap_sol.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/transactions/bloXroute_tips_tx_executor.d.ts +4 -0
- package/dist/transactions/bloXroute_tips_tx_executor.d.ts.map +1 -0
- package/dist/transactions/bloXroute_tips_tx_executor.js +70 -0
- package/dist/transactions/bloXroute_tips_tx_executor.js.map +1 -0
- package/dist/transactions/index.d.ts +6 -0
- package/dist/transactions/index.d.ts.map +1 -0
- package/dist/transactions/index.js +30 -0
- package/dist/transactions/index.js.map +1 -0
- package/dist/transactions/jito_tips_tx_executor.d.ts +15 -0
- package/dist/transactions/jito_tips_tx_executor.d.ts.map +1 -0
- package/dist/transactions/jito_tips_tx_executor.js +99 -0
- package/dist/transactions/jito_tips_tx_executor.js.map +1 -0
- package/dist/transactions/landing/index.d.ts +30 -0
- package/dist/transactions/landing/index.d.ts.map +1 -0
- package/dist/transactions/landing/index.js +60 -0
- package/dist/transactions/landing/index.js.map +1 -0
- package/dist/transactions/landing/nonce-manager.d.ts +116 -0
- package/dist/transactions/landing/nonce-manager.d.ts.map +1 -0
- package/dist/transactions/landing/nonce-manager.js +393 -0
- package/dist/transactions/landing/nonce-manager.js.map +1 -0
- package/dist/transactions/landing/orchestrator.d.ts +104 -0
- package/dist/transactions/landing/orchestrator.d.ts.map +1 -0
- package/dist/transactions/landing/orchestrator.js +329 -0
- package/dist/transactions/landing/orchestrator.js.map +1 -0
- package/dist/transactions/landing/providers/astralane.d.ts +12 -0
- package/dist/transactions/landing/providers/astralane.d.ts.map +1 -0
- package/dist/transactions/landing/providers/astralane.js +132 -0
- package/dist/transactions/landing/providers/astralane.js.map +1 -0
- package/dist/transactions/landing/providers/blockrazor.d.ts +11 -0
- package/dist/transactions/landing/providers/blockrazor.d.ts.map +1 -0
- package/dist/transactions/landing/providers/blockrazor.js +134 -0
- package/dist/transactions/landing/providers/blockrazor.js.map +1 -0
- package/dist/transactions/landing/providers/bloxroute.d.ts +12 -0
- package/dist/transactions/landing/providers/bloxroute.d.ts.map +1 -0
- package/dist/transactions/landing/providers/bloxroute.js +102 -0
- package/dist/transactions/landing/providers/bloxroute.js.map +1 -0
- package/dist/transactions/landing/providers/flashblock.d.ts +10 -0
- package/dist/transactions/landing/providers/flashblock.d.ts.map +1 -0
- package/dist/transactions/landing/providers/flashblock.js +102 -0
- package/dist/transactions/landing/providers/flashblock.js.map +1 -0
- package/dist/transactions/landing/providers/helius-sender.d.ts +11 -0
- package/dist/transactions/landing/providers/helius-sender.d.ts.map +1 -0
- package/dist/transactions/landing/providers/helius-sender.js +101 -0
- package/dist/transactions/landing/providers/helius-sender.js.map +1 -0
- package/dist/transactions/landing/providers/jito.d.ts +16 -0
- package/dist/transactions/landing/providers/jito.d.ts.map +1 -0
- package/dist/transactions/landing/providers/jito.js +110 -0
- package/dist/transactions/landing/providers/jito.js.map +1 -0
- package/dist/transactions/landing/providers/nextblock.d.ts +11 -0
- package/dist/transactions/landing/providers/nextblock.d.ts.map +1 -0
- package/dist/transactions/landing/providers/nextblock.js +109 -0
- package/dist/transactions/landing/providers/nextblock.js.map +1 -0
- package/dist/transactions/landing/providers/node1.d.ts +11 -0
- package/dist/transactions/landing/providers/node1.d.ts.map +1 -0
- package/dist/transactions/landing/providers/node1.js +101 -0
- package/dist/transactions/landing/providers/node1.js.map +1 -0
- package/dist/transactions/landing/providers/nozomi.d.ts +11 -0
- package/dist/transactions/landing/providers/nozomi.d.ts.map +1 -0
- package/dist/transactions/landing/providers/nozomi.js +124 -0
- package/dist/transactions/landing/providers/nozomi.js.map +1 -0
- package/dist/transactions/landing/providers/soyas.d.ts +16 -0
- package/dist/transactions/landing/providers/soyas.d.ts.map +1 -0
- package/dist/transactions/landing/providers/soyas.js +192 -0
- package/dist/transactions/landing/providers/soyas.js.map +1 -0
- package/dist/transactions/landing/providers/stellium.d.ts +11 -0
- package/dist/transactions/landing/providers/stellium.d.ts.map +1 -0
- package/dist/transactions/landing/providers/stellium.js +102 -0
- package/dist/transactions/landing/providers/stellium.js.map +1 -0
- package/dist/transactions/landing/providers/zero-slot.d.ts +10 -0
- package/dist/transactions/landing/providers/zero-slot.d.ts.map +1 -0
- package/dist/transactions/landing/providers/zero-slot.js +92 -0
- package/dist/transactions/landing/providers/zero-slot.js.map +1 -0
- package/dist/transactions/landing/tip-accounts.d.ts +22 -0
- package/dist/transactions/landing/tip-accounts.d.ts.map +1 -0
- package/dist/transactions/landing/tip-accounts.js +140 -0
- package/dist/transactions/landing/tip-accounts.js.map +1 -0
- package/dist/transactions/landing/types.d.ts +98 -0
- package/dist/transactions/landing/types.d.ts.map +1 -0
- package/dist/transactions/landing/types.js +30 -0
- package/dist/transactions/landing/types.js.map +1 -0
- package/dist/transactions/nozomi/tx-submission.d.ts +14 -0
- package/dist/transactions/nozomi/tx-submission.d.ts.map +1 -0
- package/dist/transactions/nozomi/tx-submission.js +107 -0
- package/dist/transactions/nozomi/tx-submission.js.map +1 -0
- package/dist/transactions/send-rpc.d.ts +54 -0
- package/dist/transactions/send-rpc.d.ts.map +1 -0
- package/dist/transactions/send-rpc.js +126 -0
- package/dist/transactions/send-rpc.js.map +1 -0
- package/dist/transactions/simple_tx_executor.d.ts +10 -0
- package/dist/transactions/simple_tx_executor.d.ts.map +1 -0
- package/dist/transactions/simple_tx_executor.js +33 -0
- package/dist/transactions/simple_tx_executor.js.map +1 -0
- package/package.json +112 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1251 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
// Suppress bigint-buffer native binding warning (pure JS fallback works fine)
|
|
38
|
+
// bigint-buffer@1.1.5 doesn't have Node 22+ bindings and prints to stderr on load
|
|
39
|
+
const _origWarn = process.stderr.write.bind(process.stderr);
|
|
40
|
+
process.stderr.write = (chunk, ...args) => {
|
|
41
|
+
if (typeof chunk === "string" && chunk.includes("bigint: Failed to load bindings"))
|
|
42
|
+
return true;
|
|
43
|
+
return _origWarn(chunk, ...args);
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* outsmart CLI — The Solana trading command-line interface.
|
|
47
|
+
*
|
|
48
|
+
* 18 DEX adapters, 12 TX landing providers, one unified interface.
|
|
49
|
+
*
|
|
50
|
+
* Usage:
|
|
51
|
+
* # On-chain DEX — pool address required, token auto-detected from pool
|
|
52
|
+
* outsmart buy --dex meteora-dlmm --pool <POOL> --amount 0.1
|
|
53
|
+
* outsmart sell --dex meteora-dlmm --pool <POOL> --pct 100
|
|
54
|
+
*
|
|
55
|
+
* # Stablecoin pool — auto-swaps SOL→USD1 then buys, no extra steps needed
|
|
56
|
+
* outsmart buy --dex raydium-launchlab --pool <POOL> --amount 0.1
|
|
57
|
+
*
|
|
58
|
+
* # Swap aggregator — requires token mint only (finds best route automatically)
|
|
59
|
+
* outsmart buy --dex jupiter-ultra --token <MINT> --amount 0.1
|
|
60
|
+
* outsmart sell --dex jupiter-ultra --token <MINT> --pct 100
|
|
61
|
+
*
|
|
62
|
+
* outsmart quote --dex meteora-dlmm --pool <POOL>
|
|
63
|
+
* outsmart list-dex
|
|
64
|
+
* outsmart list-dex --cap canSell
|
|
65
|
+
* outsmart config show
|
|
66
|
+
* outsmart init
|
|
67
|
+
*/
|
|
68
|
+
require("dotenv/config");
|
|
69
|
+
const commander_1 = require("commander");
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Side-effect imports — trigger adapter self-registration
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
require("./dex/raydium-amm-v4");
|
|
74
|
+
require("./dex/raydium-cpmm");
|
|
75
|
+
require("./dex/raydium-clmm");
|
|
76
|
+
require("./dex/raydium-launchlab");
|
|
77
|
+
require("./dex/meteora-damm-v1");
|
|
78
|
+
require("./dex/meteora-damm-v2");
|
|
79
|
+
require("./dex/meteora-dlmm");
|
|
80
|
+
require("./dex/meteora-dbc");
|
|
81
|
+
require("./dex/orca");
|
|
82
|
+
require("./dex/byreal-clmm");
|
|
83
|
+
require("./dex/pancakeswap-clmm");
|
|
84
|
+
require("./dex/fusion-amm");
|
|
85
|
+
require("./dex/futarchy-amm");
|
|
86
|
+
require("./dex/futarchy-launchpad");
|
|
87
|
+
require("./dex/pumpfun");
|
|
88
|
+
require("./dex/pumpfun-amm");
|
|
89
|
+
require("./dex/jupiter-ultra");
|
|
90
|
+
require("./dex/dflow");
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Internal imports
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
const dex_1 = require("./dex");
|
|
95
|
+
const types_1 = require("./dex/types");
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Version from package.json
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
100
|
+
const pkg = require("../package.json");
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Helpers
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
function die(msg) {
|
|
105
|
+
console.error(`\n error: ${msg}\n`);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
/** Base58 character set (no 0, O, I, l) */
|
|
109
|
+
const BASE58_CHARS = /^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$/;
|
|
110
|
+
/**
|
|
111
|
+
* Validate that a string is a valid Solana base58 address.
|
|
112
|
+
* Checks length (32-44 chars), character set, and PublicKey construction.
|
|
113
|
+
*/
|
|
114
|
+
function validateBase58(value, flag) {
|
|
115
|
+
if (value.length < 32 || value.length > 44) {
|
|
116
|
+
die(`Invalid ${flag}: "${value}" is not a valid Solana address (must be 32-44 characters, got ${value.length})`);
|
|
117
|
+
}
|
|
118
|
+
if (!BASE58_CHARS.test(value)) {
|
|
119
|
+
die(`Invalid ${flag}: "${value}" is not a valid Solana address (contains invalid base58 characters)`);
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
// Dynamic import would be async; use require for synchronous validation
|
|
123
|
+
// PublicKey is already used elsewhere in this file via dynamic import,
|
|
124
|
+
// but for a sync helper we use require.
|
|
125
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
126
|
+
const { PublicKey } = require("@solana/web3.js");
|
|
127
|
+
new PublicKey(value);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
die(`Invalid ${flag}: "${value}" is not a valid Solana address`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Validate that a DEX name is registered in the adapter registry.
|
|
135
|
+
* Shows available adapters if the name is not found.
|
|
136
|
+
*/
|
|
137
|
+
function validateDex(dexName) {
|
|
138
|
+
const registry = (0, dex_1.getRegistry)();
|
|
139
|
+
if (!registry.has(dexName)) {
|
|
140
|
+
const available = registry.getNames();
|
|
141
|
+
die(`Unknown --dex "${dexName}". Available adapters:\n` +
|
|
142
|
+
available.map((n) => ` ${n}`).join("\n"));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function printResult(result) {
|
|
146
|
+
console.log();
|
|
147
|
+
console.log(` dex: ${result.dex}`);
|
|
148
|
+
console.log(` tx: ${result.txSignature}`);
|
|
149
|
+
console.log(` confirmed: ${result.confirmed}`);
|
|
150
|
+
console.log(` in: ${result.amountIn} ${result.amountInToken}`);
|
|
151
|
+
if (result.amountOut != null) {
|
|
152
|
+
console.log(` out: ${result.amountOut} ${result.amountOutToken ?? ""}`);
|
|
153
|
+
}
|
|
154
|
+
if (result.poolAddress) {
|
|
155
|
+
console.log(` pool: ${result.poolAddress}`);
|
|
156
|
+
}
|
|
157
|
+
if (result.priceImpactPct != null) {
|
|
158
|
+
console.log(` impact: ${result.priceImpactPct.toFixed(2)}%`);
|
|
159
|
+
}
|
|
160
|
+
console.log();
|
|
161
|
+
}
|
|
162
|
+
/** Friendly label for a stablecoin mint */
|
|
163
|
+
function stablecoinLabel(mint) {
|
|
164
|
+
if (mint === types_1.USDC_MINT)
|
|
165
|
+
return "USDC";
|
|
166
|
+
if (mint === types_1.USDT_MINT)
|
|
167
|
+
return "USDT";
|
|
168
|
+
if (mint === types_1.USD1_MINT)
|
|
169
|
+
return "USD1";
|
|
170
|
+
return mint.slice(0, 8) + "...";
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Resolve the token mint and quote mint from pool state.
|
|
174
|
+
*
|
|
175
|
+
* Calls adapter.getPrice(pool) which decodes the pool account and returns
|
|
176
|
+
* baseMint + quoteMint. We determine which is the "token" (what the user
|
|
177
|
+
* wants to buy/sell) and which is the "quote" (SOL or stablecoin).
|
|
178
|
+
*
|
|
179
|
+
* Priority: SOL > USDC/USDT/USD1 > error (ambiguous).
|
|
180
|
+
*/
|
|
181
|
+
async function resolvePool(adapter, poolAddress) {
|
|
182
|
+
if (!adapter.capabilities.canGetPrice || !adapter.getPrice) {
|
|
183
|
+
die(`${adapter.name} cannot auto-detect token from pool — please provide --token <mint>`);
|
|
184
|
+
}
|
|
185
|
+
const price = await adapter.getPrice(poolAddress);
|
|
186
|
+
const { baseMint, quoteMint } = price;
|
|
187
|
+
// SOL as quote — most common
|
|
188
|
+
if (quoteMint === dex_1.WSOL_MINT)
|
|
189
|
+
return { tokenMint: baseMint, quoteMint, price };
|
|
190
|
+
if (baseMint === dex_1.WSOL_MINT)
|
|
191
|
+
return { tokenMint: quoteMint, quoteMint: baseMint, price };
|
|
192
|
+
// Stablecoin as quote
|
|
193
|
+
if (types_1.STABLECOIN_MINTS.has(quoteMint))
|
|
194
|
+
return { tokenMint: baseMint, quoteMint, price };
|
|
195
|
+
if (types_1.STABLECOIN_MINTS.has(baseMint))
|
|
196
|
+
return { tokenMint: quoteMint, quoteMint: baseMint, price };
|
|
197
|
+
// Neither side is SOL or stablecoin — ambiguous
|
|
198
|
+
die(`Pool ${poolAddress} has no SOL or stablecoin side (${baseMint} / ${quoteMint}).\n`
|
|
199
|
+
+ ` Please specify --token <mint> to indicate which token to trade.`);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Get the SPL token balance for a mint in the user's wallet.
|
|
203
|
+
*/
|
|
204
|
+
async function getTokenBalance(mint) {
|
|
205
|
+
const { getConnection, getWallet } = await Promise.resolve().then(() => __importStar(require("./helpers/config")));
|
|
206
|
+
const { PublicKey } = await Promise.resolve().then(() => __importStar(require("@solana/web3.js")));
|
|
207
|
+
const { getAssociatedTokenAddress } = await Promise.resolve().then(() => __importStar(require("@solana/spl-token")));
|
|
208
|
+
const connection = getConnection();
|
|
209
|
+
const wallet = getWallet();
|
|
210
|
+
const mintPk = new PublicKey(mint);
|
|
211
|
+
try {
|
|
212
|
+
const ata = await getAssociatedTokenAddress(mintPk, wallet.publicKey);
|
|
213
|
+
const res = await connection.getTokenAccountBalance(ata);
|
|
214
|
+
return {
|
|
215
|
+
amount: Number(res.value.uiAmount ?? 0),
|
|
216
|
+
raw: BigInt(res.value.amount),
|
|
217
|
+
decimals: res.value.decimals,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return { amount: 0, raw: 0n, decimals: 0 };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Check if Jupiter Ultra API is available (JUPITER_API_KEY is set).
|
|
226
|
+
*/
|
|
227
|
+
function hasJupiterApiKey() {
|
|
228
|
+
return !!process.env.JUPITER_API_KEY;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Auto-swap SOL → stablecoin. Uses jupiter-ultra if JUPITER_API_KEY is set,
|
|
232
|
+
* otherwise falls back to on-chain DEX adapters from SOL_STABLECOIN_POOLS.
|
|
233
|
+
* Returns the stablecoin amount received.
|
|
234
|
+
*/
|
|
235
|
+
async function autoSwapSolToStablecoin(stablecoinMint, amountSol) {
|
|
236
|
+
const label = stablecoinLabel(stablecoinMint);
|
|
237
|
+
// Snapshot balance BEFORE swap so we return only the delta
|
|
238
|
+
const balanceBefore = await getTokenBalance(stablecoinMint);
|
|
239
|
+
if (hasJupiterApiKey()) {
|
|
240
|
+
// --- Jupiter Ultra path ---
|
|
241
|
+
console.log(`\n step 1: swapping ${amountSol} SOL → ${label} via jupiter-ultra...`);
|
|
242
|
+
const jupAdapter = (0, dex_1.getDexAdapter)("jupiter-ultra");
|
|
243
|
+
const result = await jupAdapter.buy({
|
|
244
|
+
tokenMint: stablecoinMint,
|
|
245
|
+
amountSol,
|
|
246
|
+
});
|
|
247
|
+
if (!result.txSignature) {
|
|
248
|
+
die(`Failed to swap SOL → ${label}: no transaction signature returned`);
|
|
249
|
+
}
|
|
250
|
+
console.log(` ✓ tx: ${result.txSignature}`);
|
|
251
|
+
if (result.confirmed)
|
|
252
|
+
console.log(` ✓ confirmed`);
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
// --- On-chain fallback path ---
|
|
256
|
+
const pools = types_1.SOL_STABLECOIN_POOLS[stablecoinMint];
|
|
257
|
+
if (!pools || pools.length === 0) {
|
|
258
|
+
die(`No on-chain SOL/${label} pools configured and JUPITER_API_KEY is not set.`);
|
|
259
|
+
}
|
|
260
|
+
let swapped = false;
|
|
261
|
+
for (const entry of pools) {
|
|
262
|
+
try {
|
|
263
|
+
const adapter = (0, dex_1.getDexAdapter)(entry.dex);
|
|
264
|
+
console.log(`\n step 1: swapping ${amountSol} SOL → ${label} via ${entry.dex} (pool ${entry.pool.slice(0, 8)}...)...`);
|
|
265
|
+
const result = await adapter.buy({
|
|
266
|
+
tokenMint: stablecoinMint,
|
|
267
|
+
amountSol,
|
|
268
|
+
poolAddress: entry.pool,
|
|
269
|
+
quoteMint: dex_1.WSOL_MINT,
|
|
270
|
+
});
|
|
271
|
+
if (!result.txSignature) {
|
|
272
|
+
console.log(` ✗ no tx signature, trying next pool...`);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
console.log(` ✓ tx: ${result.txSignature}`);
|
|
276
|
+
if (result.confirmed)
|
|
277
|
+
console.log(` ✓ confirmed`);
|
|
278
|
+
swapped = true;
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
console.log(` ✗ ${entry.dex} failed: ${err.message ?? err}. Trying next pool...`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (!swapped) {
|
|
286
|
+
die(`All on-chain SOL/${label} pools failed. Set JUPITER_API_KEY for jupiter-ultra fallback,\n`
|
|
287
|
+
+ ` or check your SOL balance and RPC connection.`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// Wait for balance to settle, then compute delta (only the swapped amount)
|
|
291
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
292
|
+
const balanceAfter = await getTokenBalance(stablecoinMint);
|
|
293
|
+
const received = balanceAfter.amount - balanceBefore.amount;
|
|
294
|
+
console.log(` ✓ received: ${received.toFixed(6)} ${label} (wallet total: ${balanceAfter.amount} ${label})`);
|
|
295
|
+
if (received <= 0) {
|
|
296
|
+
die(`SOL → ${label} swap TX landed but received 0 ${label}. TX may have failed on-chain.`);
|
|
297
|
+
}
|
|
298
|
+
return received;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Auto-swap stablecoin → SOL after a sell. Uses jupiter-ultra if JUPITER_API_KEY
|
|
302
|
+
* is set, otherwise falls back to on-chain DEX adapters.
|
|
303
|
+
*/
|
|
304
|
+
async function autoSwapStablecoinToSol(stablecoinMint) {
|
|
305
|
+
const balance = await getTokenBalance(stablecoinMint);
|
|
306
|
+
if (balance.amount === 0)
|
|
307
|
+
return;
|
|
308
|
+
const label = stablecoinLabel(stablecoinMint);
|
|
309
|
+
if (hasJupiterApiKey()) {
|
|
310
|
+
// --- Jupiter Ultra path ---
|
|
311
|
+
console.log(`\n step 2: swapping ${balance.amount} ${label} → SOL via jupiter-ultra...`);
|
|
312
|
+
const jupAdapter = (0, dex_1.getDexAdapter)("jupiter-ultra");
|
|
313
|
+
const result = await jupAdapter.sell({
|
|
314
|
+
tokenMint: stablecoinMint,
|
|
315
|
+
percentage: 100,
|
|
316
|
+
});
|
|
317
|
+
if (result.txSignature) {
|
|
318
|
+
console.log(` ✓ tx: ${result.txSignature}`);
|
|
319
|
+
if (result.confirmed)
|
|
320
|
+
console.log(` ✓ confirmed`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
// --- On-chain fallback path ---
|
|
325
|
+
const pools = types_1.SOL_STABLECOIN_POOLS[stablecoinMint];
|
|
326
|
+
if (!pools || pools.length === 0) {
|
|
327
|
+
console.log(`\n ⚠ No on-chain ${label}/SOL pools configured and JUPITER_API_KEY is not set. ${label} remains in wallet.`);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
for (const entry of pools) {
|
|
331
|
+
try {
|
|
332
|
+
const adapter = (0, dex_1.getDexAdapter)(entry.dex);
|
|
333
|
+
console.log(`\n step 2: swapping ${balance.amount} ${label} → SOL via ${entry.dex} (pool ${entry.pool.slice(0, 8)}...)...`);
|
|
334
|
+
const result = await adapter.sell({
|
|
335
|
+
tokenMint: stablecoinMint,
|
|
336
|
+
percentage: 100,
|
|
337
|
+
poolAddress: entry.pool,
|
|
338
|
+
quoteMint: dex_1.WSOL_MINT,
|
|
339
|
+
});
|
|
340
|
+
if (result.txSignature) {
|
|
341
|
+
console.log(` ✓ tx: ${result.txSignature}`);
|
|
342
|
+
if (result.confirmed)
|
|
343
|
+
console.log(` ✓ confirmed`);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
console.log(` ✗ no tx signature, trying next pool...`);
|
|
347
|
+
}
|
|
348
|
+
catch (err) {
|
|
349
|
+
console.log(` ✗ ${entry.dex} failed: ${err.message ?? err}. Trying next pool...`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
console.log(`\n ⚠ All on-chain ${label}/SOL pools failed. ${label} remains in wallet.`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function buildSwapOpts(cmd) {
|
|
356
|
+
const opts = {};
|
|
357
|
+
if (cmd.slippage != null)
|
|
358
|
+
opts.slippageBps = Number(cmd.slippage);
|
|
359
|
+
if (cmd.priority != null)
|
|
360
|
+
opts.priorityFeeMicroLamports = Number(cmd.priority);
|
|
361
|
+
if (cmd.tip != null)
|
|
362
|
+
opts.tipSol = Number(cmd.tip);
|
|
363
|
+
if (cmd.cu != null)
|
|
364
|
+
opts.computeUnitLimit = Number(cmd.cu);
|
|
365
|
+
if (cmd.jito)
|
|
366
|
+
opts.useJito = true;
|
|
367
|
+
if (cmd.strategy != null) {
|
|
368
|
+
opts.landingStrategy = cmd.strategy;
|
|
369
|
+
}
|
|
370
|
+
return opts;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Shared option definitions for swap commands.
|
|
374
|
+
* @param includeTip - whether to add --tip (false for snipe, which has it as required)
|
|
375
|
+
*/
|
|
376
|
+
function addSwapOptions(cmd, includeTip = true) {
|
|
377
|
+
cmd
|
|
378
|
+
.option("--slippage <bps>", `slippage tolerance in basis points (default: ${dex_1.DEFAULT_SLIPPAGE_BPS})`)
|
|
379
|
+
.option("--priority <microLamports>", "priority fee in microLamports per CU")
|
|
380
|
+
.option("--cu <units>", "compute unit limit")
|
|
381
|
+
.option("--jito", "use Jito bundle submission")
|
|
382
|
+
.option("--strategy <mode>", "TX landing strategy: concurrent|race|random|sequential")
|
|
383
|
+
.option("--quote <mint>", "quote token mint (default: WSOL)");
|
|
384
|
+
if (includeTip) {
|
|
385
|
+
cmd.option("--tip <sol>", "MEV tip in SOL");
|
|
386
|
+
}
|
|
387
|
+
return cmd;
|
|
388
|
+
}
|
|
389
|
+
// ---------------------------------------------------------------------------
|
|
390
|
+
// Program
|
|
391
|
+
// ---------------------------------------------------------------------------
|
|
392
|
+
const program = new commander_1.Command()
|
|
393
|
+
.name("outsmart")
|
|
394
|
+
.description("The Solana trading CLI — 18 DEX adapters, 12 TX landing providers.")
|
|
395
|
+
.version(pkg.version);
|
|
396
|
+
// ---------------------------------------------------------------------------
|
|
397
|
+
// outsmart buy
|
|
398
|
+
// ---------------------------------------------------------------------------
|
|
399
|
+
const buyCmd = new commander_1.Command("buy")
|
|
400
|
+
.description("Buy tokens with SOL (or quote token)")
|
|
401
|
+
.requiredOption("-d, --dex <name>", "DEX adapter name (e.g. raydium-cpmm, jupiter-ultra)")
|
|
402
|
+
.requiredOption("-a, --amount <sol>", "amount of SOL to spend")
|
|
403
|
+
.option("-p, --pool <address>", "pool address (required for on-chain DEXes)")
|
|
404
|
+
.option("-t, --token <mint>", "token mint address to buy")
|
|
405
|
+
.action(async (cmdOpts) => {
|
|
406
|
+
// --- Input sanitization ---
|
|
407
|
+
validateDex(cmdOpts.dex);
|
|
408
|
+
if (cmdOpts.pool)
|
|
409
|
+
validateBase58(cmdOpts.pool, "--pool");
|
|
410
|
+
if (cmdOpts.token)
|
|
411
|
+
validateBase58(cmdOpts.token, "--token");
|
|
412
|
+
if (cmdOpts.quote)
|
|
413
|
+
validateBase58(cmdOpts.quote, "--quote");
|
|
414
|
+
const adapter = (0, dex_1.getDexAdapter)(cmdOpts.dex);
|
|
415
|
+
if (!adapter.capabilities.canBuy) {
|
|
416
|
+
die(`${adapter.name} does not support buy`);
|
|
417
|
+
}
|
|
418
|
+
// Validate inputs based on adapter type
|
|
419
|
+
if (adapter.capabilities.isAggregator) {
|
|
420
|
+
// Aggregators (jupiter-ultra, dflow): need --token, no --pool needed
|
|
421
|
+
if (!cmdOpts.token) {
|
|
422
|
+
die(`${adapter.name} is a swap aggregator — --token <mint> is required.\n Example: outsmart buy --dex ${adapter.name} --token <MINT> --amount 0.1`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
// On-chain DEXes: need --pool (--token is optional, auto-resolved from pool)
|
|
427
|
+
if (!cmdOpts.pool) {
|
|
428
|
+
die(`${adapter.name} is an on-chain DEX — --pool <address> is required.\n Example: outsmart buy --dex ${adapter.name} --pool <POOL> --amount 0.1`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
// Auto-resolve token + quote from pool state
|
|
432
|
+
let tokenMint = cmdOpts.token;
|
|
433
|
+
let quoteMint = cmdOpts.quote;
|
|
434
|
+
let amountToSpend = Number(cmdOpts.amount);
|
|
435
|
+
if (isNaN(amountToSpend) || amountToSpend <= 0) {
|
|
436
|
+
die(`Invalid --amount: ${cmdOpts.amount}. Must be a positive number.`);
|
|
437
|
+
}
|
|
438
|
+
if (!tokenMint && cmdOpts.pool) {
|
|
439
|
+
const resolved = await resolvePool(adapter, cmdOpts.pool);
|
|
440
|
+
tokenMint = resolved.tokenMint;
|
|
441
|
+
quoteMint = quoteMint ?? resolved.quoteMint;
|
|
442
|
+
console.log(` auto-detected token: ${tokenMint}`);
|
|
443
|
+
// If the quote is a stablecoin (not SOL), auto-swap SOL → stablecoin first
|
|
444
|
+
if (resolved.quoteMint !== dex_1.WSOL_MINT && types_1.STABLECOIN_MINTS.has(resolved.quoteMint)) {
|
|
445
|
+
const label = stablecoinLabel(resolved.quoteMint);
|
|
446
|
+
console.log(` pool quote: ${label} (not SOL)`);
|
|
447
|
+
quoteMint = resolved.quoteMint;
|
|
448
|
+
// Check existing balance
|
|
449
|
+
const existingBalance = await getTokenBalance(resolved.quoteMint);
|
|
450
|
+
if (existingBalance.amount > 0) {
|
|
451
|
+
console.log(` wallet has ${existingBalance.amount} ${label}`);
|
|
452
|
+
}
|
|
453
|
+
// Swap SOL → stablecoin, then use full stablecoin balance for the buy
|
|
454
|
+
amountToSpend = await autoSwapSolToStablecoin(resolved.quoteMint, amountToSpend);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const params = {
|
|
458
|
+
tokenMint,
|
|
459
|
+
amountSol: amountToSpend,
|
|
460
|
+
poolAddress: cmdOpts.pool,
|
|
461
|
+
quoteMint,
|
|
462
|
+
opts: buildSwapOpts(cmdOpts),
|
|
463
|
+
};
|
|
464
|
+
const isStablecoinQuote = quoteMint && types_1.STABLECOIN_MINTS.has(quoteMint);
|
|
465
|
+
const stepLabel = isStablecoinQuote ? "step 2: " : "";
|
|
466
|
+
console.log(`\n ${stepLabel}buying on ${adapter.name}...`);
|
|
467
|
+
const result = await adapter.buy(params);
|
|
468
|
+
printResult(result);
|
|
469
|
+
});
|
|
470
|
+
addSwapOptions(buyCmd);
|
|
471
|
+
program.addCommand(buyCmd);
|
|
472
|
+
// ---------------------------------------------------------------------------
|
|
473
|
+
// outsmart sell
|
|
474
|
+
// ---------------------------------------------------------------------------
|
|
475
|
+
const sellCmd = new commander_1.Command("sell")
|
|
476
|
+
.description("Sell tokens for SOL (or quote token)")
|
|
477
|
+
.requiredOption("-d, --dex <name>", "DEX adapter name")
|
|
478
|
+
.requiredOption("--pct <percentage>", "percentage of held balance to sell (0-100)")
|
|
479
|
+
.option("-p, --pool <address>", "pool address (required for on-chain DEXes)")
|
|
480
|
+
.option("-t, --token <mint>", "token mint address to sell")
|
|
481
|
+
.action(async (cmdOpts) => {
|
|
482
|
+
// --- Input sanitization ---
|
|
483
|
+
validateDex(cmdOpts.dex);
|
|
484
|
+
if (cmdOpts.pool)
|
|
485
|
+
validateBase58(cmdOpts.pool, "--pool");
|
|
486
|
+
if (cmdOpts.token)
|
|
487
|
+
validateBase58(cmdOpts.token, "--token");
|
|
488
|
+
if (cmdOpts.quote)
|
|
489
|
+
validateBase58(cmdOpts.quote, "--quote");
|
|
490
|
+
const adapter = (0, dex_1.getDexAdapter)(cmdOpts.dex);
|
|
491
|
+
if (!adapter.capabilities.canSell) {
|
|
492
|
+
die(`${adapter.name} does not support sell`);
|
|
493
|
+
}
|
|
494
|
+
// Validate inputs based on adapter type
|
|
495
|
+
if (adapter.capabilities.isAggregator) {
|
|
496
|
+
// Aggregators (jupiter-ultra, dflow): need --token, no --pool needed
|
|
497
|
+
if (!cmdOpts.token) {
|
|
498
|
+
die(`${adapter.name} is a swap aggregator — --token <mint> is required.\n Example: outsmart sell --dex ${adapter.name} --token <MINT> --pct 100`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
// On-chain DEXes: need --pool (--token is optional, auto-resolved from pool)
|
|
503
|
+
if (!cmdOpts.pool) {
|
|
504
|
+
die(`${adapter.name} is an on-chain DEX — --pool <address> is required.\n Example: outsmart sell --dex ${adapter.name} --pool <POOL> --pct 100`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
const pct = Number(cmdOpts.pct);
|
|
508
|
+
if (isNaN(pct) || pct <= 0 || pct > 100) {
|
|
509
|
+
die(`Invalid --pct: ${cmdOpts.pct}. Must be between 1 and 100.`);
|
|
510
|
+
}
|
|
511
|
+
// Auto-resolve token + quote from pool state
|
|
512
|
+
let tokenMint = cmdOpts.token;
|
|
513
|
+
let quoteMint = cmdOpts.quote;
|
|
514
|
+
let isStablecoinQuote = false;
|
|
515
|
+
if (!tokenMint && cmdOpts.pool) {
|
|
516
|
+
const resolved = await resolvePool(adapter, cmdOpts.pool);
|
|
517
|
+
tokenMint = resolved.tokenMint;
|
|
518
|
+
quoteMint = quoteMint ?? resolved.quoteMint;
|
|
519
|
+
console.log(` auto-detected token: ${tokenMint}`);
|
|
520
|
+
if (resolved.quoteMint !== dex_1.WSOL_MINT && types_1.STABLECOIN_MINTS.has(resolved.quoteMint)) {
|
|
521
|
+
isStablecoinQuote = true;
|
|
522
|
+
const label = stablecoinLabel(resolved.quoteMint);
|
|
523
|
+
console.log(` pool quote: ${label} (will auto-convert to SOL after sell)`);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
const params = {
|
|
527
|
+
tokenMint,
|
|
528
|
+
percentage: pct,
|
|
529
|
+
poolAddress: cmdOpts.pool,
|
|
530
|
+
quoteMint,
|
|
531
|
+
opts: buildSwapOpts(cmdOpts),
|
|
532
|
+
};
|
|
533
|
+
const stepLabel = isStablecoinQuote ? "step 1: " : "";
|
|
534
|
+
console.log(`\n ${stepLabel}selling ${params.percentage}% on ${adapter.name}...`);
|
|
535
|
+
const result = await adapter.sell(params);
|
|
536
|
+
printResult(result);
|
|
537
|
+
// Auto-swap stablecoin proceeds → SOL
|
|
538
|
+
if (isStablecoinQuote && quoteMint && result.txSignature) {
|
|
539
|
+
await autoSwapStablecoinToSol(quoteMint);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
addSwapOptions(sellCmd);
|
|
543
|
+
program.addCommand(sellCmd);
|
|
544
|
+
// ---------------------------------------------------------------------------
|
|
545
|
+
// outsmart snipe — NOT YET IMPLEMENTED
|
|
546
|
+
//
|
|
547
|
+
// Real sniping requires a gRPC (Geyser/Yellowstone) listener that monitors
|
|
548
|
+
// pool creation events in real time. When a new pool is created where the
|
|
549
|
+
// base or quote token matches the target, it fires an instant buy through
|
|
550
|
+
// concurrent multi-provider TX landing.
|
|
551
|
+
//
|
|
552
|
+
// This needs the user's own Geyser gRPC key and runs as a background
|
|
553
|
+
// process (cronjob/tmux). Will be added when gRPC integration is built.
|
|
554
|
+
//
|
|
555
|
+
// For now, use `outsmart buy --pool <POOL> --tip <SOL>` to execute a
|
|
556
|
+
// competitive buy on a known pool.
|
|
557
|
+
// ---------------------------------------------------------------------------
|
|
558
|
+
// ---------------------------------------------------------------------------
|
|
559
|
+
// outsmart quote
|
|
560
|
+
// ---------------------------------------------------------------------------
|
|
561
|
+
program
|
|
562
|
+
.command("quote")
|
|
563
|
+
.description("Get the current on-chain price from a pool")
|
|
564
|
+
.requiredOption("-d, --dex <name>", "DEX adapter name")
|
|
565
|
+
.requiredOption("-p, --pool <address>", "pool address")
|
|
566
|
+
.action(async (cmdOpts) => {
|
|
567
|
+
const adapter = (0, dex_1.getDexAdapter)(cmdOpts.dex);
|
|
568
|
+
if (!adapter.capabilities.canGetPrice) {
|
|
569
|
+
die(`${adapter.name} does not support getPrice`);
|
|
570
|
+
}
|
|
571
|
+
if (!adapter.getPrice) {
|
|
572
|
+
die(`${adapter.name} declares canGetPrice but has no getPrice() implementation`);
|
|
573
|
+
}
|
|
574
|
+
const price = await adapter.getPrice(cmdOpts.pool);
|
|
575
|
+
console.log();
|
|
576
|
+
console.log(` dex: ${adapter.name}`);
|
|
577
|
+
console.log(` pool: ${price.poolAddress}`);
|
|
578
|
+
console.log(` price: ${price.price}`);
|
|
579
|
+
console.log(` base: ${price.baseMint}`);
|
|
580
|
+
console.log(` quote: ${price.quoteMint}`);
|
|
581
|
+
console.log(` source: ${price.source}`);
|
|
582
|
+
console.log(` time: ${new Date(price.timestamp).toISOString()}`);
|
|
583
|
+
console.log();
|
|
584
|
+
});
|
|
585
|
+
// ---------------------------------------------------------------------------
|
|
586
|
+
// outsmart find-pool
|
|
587
|
+
// ---------------------------------------------------------------------------
|
|
588
|
+
program
|
|
589
|
+
.command("find-pool")
|
|
590
|
+
.description("Discover a pool for a token pair on a specific DEX")
|
|
591
|
+
.requiredOption("-d, --dex <name>", "DEX adapter name")
|
|
592
|
+
.requiredOption("-t, --token <mint>", "base token mint address")
|
|
593
|
+
.option("--quote <mint>", "quote token mint (default: WSOL)")
|
|
594
|
+
.action(async (cmdOpts) => {
|
|
595
|
+
const adapter = (0, dex_1.getDexAdapter)(cmdOpts.dex);
|
|
596
|
+
if (!adapter.capabilities.canFindPool) {
|
|
597
|
+
die(`${adapter.name} does not support findPool`);
|
|
598
|
+
}
|
|
599
|
+
if (!adapter.findPool) {
|
|
600
|
+
die(`${adapter.name} declares canFindPool but has no findPool() implementation`);
|
|
601
|
+
}
|
|
602
|
+
const pool = await adapter.findPool(cmdOpts.token, cmdOpts.quote);
|
|
603
|
+
if (!pool) {
|
|
604
|
+
console.log(`\n No pool found for ${cmdOpts.token} on ${adapter.name}\n`);
|
|
605
|
+
process.exit(1);
|
|
606
|
+
}
|
|
607
|
+
console.log();
|
|
608
|
+
console.log(` dex: ${pool.dex}`);
|
|
609
|
+
console.log(` protocol: ${pool.protocol}`);
|
|
610
|
+
console.log(` pool: ${pool.address}`);
|
|
611
|
+
console.log(` base: ${pool.baseMint} (${pool.baseDecimals} decimals)`);
|
|
612
|
+
console.log(` quote: ${pool.quoteMint} (${pool.quoteDecimals} decimals)`);
|
|
613
|
+
if (pool.liquidity != null) {
|
|
614
|
+
console.log(` liquidity: $${pool.liquidity.toLocaleString()}`);
|
|
615
|
+
}
|
|
616
|
+
if (pool.price != null) {
|
|
617
|
+
console.log(` price: ${pool.price}`);
|
|
618
|
+
}
|
|
619
|
+
console.log();
|
|
620
|
+
});
|
|
621
|
+
// ---------------------------------------------------------------------------
|
|
622
|
+
// outsmart add-liq
|
|
623
|
+
// ---------------------------------------------------------------------------
|
|
624
|
+
program
|
|
625
|
+
.command("add-liq")
|
|
626
|
+
.description("Add liquidity to a pool")
|
|
627
|
+
.requiredOption("-d, --dex <name>", "DEX adapter name (e.g. meteora-dlmm)")
|
|
628
|
+
.requiredOption("-p, --pool <address>", "pool address")
|
|
629
|
+
.option("--amount-sol <amount>", "amount of SOL to deposit")
|
|
630
|
+
.option("--amount-token <amount>", "amount of non-SOL token to deposit")
|
|
631
|
+
.option("-t, --token <mint>", "token mint (for single-sided token deposits)")
|
|
632
|
+
.option("--strategy <type>", "distribution strategy: spot|curve|bid-ask (default: spot)")
|
|
633
|
+
.option("--bins <count>", "number of bins to spread across (default: 50, max: 70)")
|
|
634
|
+
.option("--amount-a <amount>", "amount of token A (legacy, use --amount-sol instead)")
|
|
635
|
+
.option("--amount-b <amount>", "amount of token B (legacy, use --amount-token instead)")
|
|
636
|
+
.option("--slippage <bps>", "slippage tolerance in basis points")
|
|
637
|
+
.option("--priority <microLamports>", "priority fee in microLamports per CU")
|
|
638
|
+
.option("--tip <sol>", "MEV tip in SOL")
|
|
639
|
+
.action(async (cmdOpts) => {
|
|
640
|
+
const adapter = (0, dex_1.getDexAdapter)(cmdOpts.dex);
|
|
641
|
+
if (!adapter.capabilities.canAddLiquidity) {
|
|
642
|
+
die(`${adapter.name} does not support addLiquidity`);
|
|
643
|
+
}
|
|
644
|
+
if (!adapter.addLiquidity) {
|
|
645
|
+
die(`${adapter.name} declares canAddLiquidity but has no addLiquidity() implementation`);
|
|
646
|
+
}
|
|
647
|
+
// Validate: at least one amount must be provided
|
|
648
|
+
const hasAmountSol = cmdOpts.amountSol != null;
|
|
649
|
+
const hasAmountToken = cmdOpts.amountToken != null;
|
|
650
|
+
const hasLegacyA = cmdOpts.amountA != null;
|
|
651
|
+
const hasLegacyB = cmdOpts.amountB != null;
|
|
652
|
+
if (!hasAmountSol && !hasAmountToken && !hasLegacyA && !hasLegacyB) {
|
|
653
|
+
die("At least one of --amount-sol or --amount-token must be provided.\n"
|
|
654
|
+
+ " Examples:\n"
|
|
655
|
+
+ ` outsmart add-liq --dex ${adapter.name} --pool <POOL> --amount-sol 0.5\n`
|
|
656
|
+
+ ` outsmart add-liq --dex ${adapter.name} --pool <POOL> --amount-token 1000 --token <MINT>\n`
|
|
657
|
+
+ ` outsmart add-liq --dex ${adapter.name} --pool <POOL> --amount-sol 0.5 --amount-token 1000`);
|
|
658
|
+
}
|
|
659
|
+
// Validate strategy
|
|
660
|
+
const validStrategies = ["spot", "curve", "bid-ask"];
|
|
661
|
+
if (cmdOpts.strategy && !validStrategies.includes(cmdOpts.strategy)) {
|
|
662
|
+
die(`Invalid strategy "${cmdOpts.strategy}". Must be one of: ${validStrategies.join(", ")}`);
|
|
663
|
+
}
|
|
664
|
+
const params = {
|
|
665
|
+
poolAddress: cmdOpts.pool,
|
|
666
|
+
amountSol: hasAmountSol ? Number(cmdOpts.amountSol) : undefined,
|
|
667
|
+
amountToken: hasAmountToken ? Number(cmdOpts.amountToken) : undefined,
|
|
668
|
+
tokenMint: cmdOpts.token,
|
|
669
|
+
strategy: cmdOpts.strategy,
|
|
670
|
+
bins: cmdOpts.bins != null ? Number(cmdOpts.bins) : undefined,
|
|
671
|
+
amountA: hasLegacyA ? Number(cmdOpts.amountA) : undefined,
|
|
672
|
+
amountB: hasLegacyB ? Number(cmdOpts.amountB) : undefined,
|
|
673
|
+
opts: buildSwapOpts(cmdOpts),
|
|
674
|
+
};
|
|
675
|
+
const mode = params.amountSol && params.amountToken
|
|
676
|
+
? "balanced" : params.amountSol ? "one-sided SOL" : "one-sided token";
|
|
677
|
+
console.log(`\n adding ${mode} liquidity on ${adapter.name} (pool: ${params.poolAddress})...`);
|
|
678
|
+
if (cmdOpts.strategy)
|
|
679
|
+
console.log(` strategy: ${cmdOpts.strategy}`);
|
|
680
|
+
if (cmdOpts.bins)
|
|
681
|
+
console.log(` bins: ${cmdOpts.bins}`);
|
|
682
|
+
const result = await adapter.addLiquidity(params);
|
|
683
|
+
console.log();
|
|
684
|
+
console.log(` tx: ${result.txSignature}`);
|
|
685
|
+
console.log(` confirmed: ${result.confirmed}`);
|
|
686
|
+
if (result.positionAddress) {
|
|
687
|
+
console.log(` position: ${result.positionAddress}`);
|
|
688
|
+
}
|
|
689
|
+
if (result.error) {
|
|
690
|
+
console.log(` error: ${result.error}`);
|
|
691
|
+
}
|
|
692
|
+
console.log();
|
|
693
|
+
});
|
|
694
|
+
// ---------------------------------------------------------------------------
|
|
695
|
+
// outsmart remove-liq
|
|
696
|
+
// ---------------------------------------------------------------------------
|
|
697
|
+
program
|
|
698
|
+
.command("remove-liq")
|
|
699
|
+
.description("Remove liquidity from a pool")
|
|
700
|
+
.requiredOption("-d, --dex <name>", "DEX adapter name (e.g. meteora-dlmm)")
|
|
701
|
+
.requiredOption("-p, --pool <address>", "pool address")
|
|
702
|
+
.requiredOption("--pct <percentage>", "percentage of LP position to remove (0-100)")
|
|
703
|
+
.option("--position <address>", "specific position address to remove from (default: first found)")
|
|
704
|
+
.option("--slippage <bps>", "slippage tolerance in basis points")
|
|
705
|
+
.option("--priority <microLamports>", "priority fee in microLamports per CU")
|
|
706
|
+
.option("--tip <sol>", "MEV tip in SOL")
|
|
707
|
+
.action(async (cmdOpts) => {
|
|
708
|
+
const adapter = (0, dex_1.getDexAdapter)(cmdOpts.dex);
|
|
709
|
+
if (!adapter.capabilities.canRemoveLiquidity) {
|
|
710
|
+
die(`${adapter.name} does not support removeLiquidity`);
|
|
711
|
+
}
|
|
712
|
+
if (!adapter.removeLiquidity) {
|
|
713
|
+
die(`${adapter.name} declares canRemoveLiquidity but has no removeLiquidity() implementation`);
|
|
714
|
+
}
|
|
715
|
+
const params = {
|
|
716
|
+
poolAddress: cmdOpts.pool,
|
|
717
|
+
percentage: Number(cmdOpts.pct),
|
|
718
|
+
positionAddress: cmdOpts.position,
|
|
719
|
+
opts: buildSwapOpts(cmdOpts),
|
|
720
|
+
};
|
|
721
|
+
console.log(`\n removing ${params.percentage}% liquidity on ${adapter.name} (pool: ${params.poolAddress})...`);
|
|
722
|
+
if (params.positionAddress) {
|
|
723
|
+
console.log(` position: ${params.positionAddress}`);
|
|
724
|
+
}
|
|
725
|
+
const result = await adapter.removeLiquidity(params);
|
|
726
|
+
console.log();
|
|
727
|
+
console.log(` tx: ${result.txSignature}`);
|
|
728
|
+
console.log(` confirmed: ${result.confirmed}`);
|
|
729
|
+
if (result.positionAddress) {
|
|
730
|
+
console.log(` position: ${result.positionAddress}`);
|
|
731
|
+
}
|
|
732
|
+
if (result.error) {
|
|
733
|
+
console.log(` error: ${result.error}`);
|
|
734
|
+
}
|
|
735
|
+
console.log();
|
|
736
|
+
});
|
|
737
|
+
// ---------------------------------------------------------------------------
|
|
738
|
+
// outsmart claim-fees
|
|
739
|
+
// ---------------------------------------------------------------------------
|
|
740
|
+
program
|
|
741
|
+
.command("claim-fees")
|
|
742
|
+
.description("Claim accumulated swap fees from LP positions")
|
|
743
|
+
.requiredOption("-d, --dex <name>", "DEX adapter name (e.g. meteora-dlmm)")
|
|
744
|
+
.requiredOption("-p, --pool <address>", "pool address")
|
|
745
|
+
.option("--position <address>", "specific position address to claim from (default: first found)")
|
|
746
|
+
.action(async (cmdOpts) => {
|
|
747
|
+
const adapter = (0, dex_1.getDexAdapter)(cmdOpts.dex);
|
|
748
|
+
if (!adapter.capabilities.canClaimFees) {
|
|
749
|
+
die(`${adapter.name} does not support claimFees`);
|
|
750
|
+
}
|
|
751
|
+
if (!adapter.claimFees) {
|
|
752
|
+
die(`${adapter.name} declares canClaimFees but has no claimFees() implementation`);
|
|
753
|
+
}
|
|
754
|
+
console.log(`\n claiming fees on ${adapter.name} (pool: ${cmdOpts.pool})...`);
|
|
755
|
+
const result = await adapter.claimFees(cmdOpts.pool, cmdOpts.position);
|
|
756
|
+
console.log();
|
|
757
|
+
console.log(` tx: ${result.txSignature}`);
|
|
758
|
+
console.log(` confirmed: ${result.confirmed}`);
|
|
759
|
+
if (result.positionAddress) {
|
|
760
|
+
console.log(` position: ${result.positionAddress}`);
|
|
761
|
+
}
|
|
762
|
+
if (result.error) {
|
|
763
|
+
console.log(` error: ${result.error}`);
|
|
764
|
+
}
|
|
765
|
+
console.log();
|
|
766
|
+
});
|
|
767
|
+
// ---------------------------------------------------------------------------
|
|
768
|
+
// outsmart positions
|
|
769
|
+
// ---------------------------------------------------------------------------
|
|
770
|
+
program
|
|
771
|
+
.command("positions")
|
|
772
|
+
.description("List LP positions in a pool")
|
|
773
|
+
.requiredOption("-d, --dex <name>", "DEX adapter name (e.g. meteora-dlmm)")
|
|
774
|
+
.requiredOption("-p, --pool <address>", "pool address")
|
|
775
|
+
.option("--json", "output as JSON")
|
|
776
|
+
.action(async (cmdOpts) => {
|
|
777
|
+
const adapter = (0, dex_1.getDexAdapter)(cmdOpts.dex);
|
|
778
|
+
if (!adapter.capabilities.canListPositions) {
|
|
779
|
+
die(`${adapter.name} does not support listPositions`);
|
|
780
|
+
}
|
|
781
|
+
if (!adapter.listPositions) {
|
|
782
|
+
die(`${adapter.name} declares canListPositions but has no listPositions() implementation`);
|
|
783
|
+
}
|
|
784
|
+
const positions = await adapter.listPositions(cmdOpts.pool);
|
|
785
|
+
if (cmdOpts.json) {
|
|
786
|
+
console.log(JSON.stringify(positions, null, 2));
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
if (positions.length === 0) {
|
|
790
|
+
console.log(`\n No positions found in pool ${cmdOpts.pool}\n`);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
console.log(`\n ${positions.length} position(s) in pool ${cmdOpts.pool}:\n`);
|
|
794
|
+
for (const pos of positions) {
|
|
795
|
+
console.log(` position: ${pos.positionAddress}`);
|
|
796
|
+
console.log(` bins: ${pos.lowerBinId} → ${pos.upperBinId}`);
|
|
797
|
+
console.log(` in-range: ${pos.inRange}`);
|
|
798
|
+
console.log(` tokenX: ${pos.amountX} (${pos.tokenXMint})`);
|
|
799
|
+
console.log(` tokenY: ${pos.amountY} (${pos.tokenYMint})`);
|
|
800
|
+
console.log(` feeX: ${pos.feeX}`);
|
|
801
|
+
console.log(` feeY: ${pos.feeY}`);
|
|
802
|
+
console.log();
|
|
803
|
+
}
|
|
804
|
+
});
|
|
805
|
+
// ---------------------------------------------------------------------------
|
|
806
|
+
// outsmart create-pump-coin (PumpFun bonding curve)
|
|
807
|
+
// ---------------------------------------------------------------------------
|
|
808
|
+
program
|
|
809
|
+
.command("create-pump-coin")
|
|
810
|
+
.description("Create a new PumpFun token with a bonding curve")
|
|
811
|
+
.requiredOption("--name <name>", "token name")
|
|
812
|
+
.requiredOption("--symbol <symbol>", "token symbol")
|
|
813
|
+
.requiredOption("--uri <uri>", "metadata URI (IPFS link to JSON metadata)")
|
|
814
|
+
.action(async (cmdOpts) => {
|
|
815
|
+
const adapter = (0, dex_1.getDexAdapter)("pumpfun");
|
|
816
|
+
console.log(`\n creating token "${cmdOpts.name}" (${cmdOpts.symbol}) on pump.fun...`);
|
|
817
|
+
const result = await adapter.create(cmdOpts.name, cmdOpts.symbol, cmdOpts.uri);
|
|
818
|
+
console.log();
|
|
819
|
+
console.log(` tx: ${result.txSignature}`);
|
|
820
|
+
console.log(` confirmed: ${result.confirmed}`);
|
|
821
|
+
if (result.positionAddress) {
|
|
822
|
+
console.log(` mint: ${result.positionAddress}`);
|
|
823
|
+
}
|
|
824
|
+
if (result.poolAddress) {
|
|
825
|
+
console.log(` curve: ${result.poolAddress}`);
|
|
826
|
+
}
|
|
827
|
+
if (result.error) {
|
|
828
|
+
console.log(` error: ${result.error}`);
|
|
829
|
+
}
|
|
830
|
+
console.log();
|
|
831
|
+
});
|
|
832
|
+
// ---------------------------------------------------------------------------
|
|
833
|
+
// outsmart create-pool (PumpSwap AMM)
|
|
834
|
+
// ---------------------------------------------------------------------------
|
|
835
|
+
program
|
|
836
|
+
.command("create-pool")
|
|
837
|
+
.description("Create a new PumpSwap AMM pool with initial liquidity")
|
|
838
|
+
.requiredOption("--base <mint>", "base token mint address")
|
|
839
|
+
.requiredOption("--quote <mint>", "quote token mint address (usually WSOL)")
|
|
840
|
+
.requiredOption("--base-amount <amount>", "initial base token deposit (human-readable)")
|
|
841
|
+
.requiredOption("--quote-amount <amount>", "initial quote token deposit (human-readable)")
|
|
842
|
+
.option("--index <number>", "pool index (default: 1; 0 is reserved for canonical pump pools)", "1")
|
|
843
|
+
.action(async (cmdOpts) => {
|
|
844
|
+
const adapter = (0, dex_1.getDexAdapter)("pumpfun-amm");
|
|
845
|
+
console.log(`\n creating pool on PumpSwap AMM...`);
|
|
846
|
+
console.log(` base: ${cmdOpts.base}`);
|
|
847
|
+
console.log(` quote: ${cmdOpts.quote}`);
|
|
848
|
+
console.log(` amounts: ${cmdOpts.baseAmount} base + ${cmdOpts.quoteAmount} quote`);
|
|
849
|
+
console.log(` index: ${cmdOpts.index}`);
|
|
850
|
+
const result = await adapter.createPool(cmdOpts.base, cmdOpts.quote, Number(cmdOpts.baseAmount), Number(cmdOpts.quoteAmount), Number(cmdOpts.index));
|
|
851
|
+
console.log();
|
|
852
|
+
console.log(` tx: ${result.txSignature}`);
|
|
853
|
+
console.log(` confirmed: ${result.confirmed}`);
|
|
854
|
+
if (result.poolAddress) {
|
|
855
|
+
console.log(` pool: ${result.poolAddress}`);
|
|
856
|
+
}
|
|
857
|
+
if (result.error) {
|
|
858
|
+
console.log(` error: ${result.error}`);
|
|
859
|
+
}
|
|
860
|
+
console.log();
|
|
861
|
+
});
|
|
862
|
+
// ---------------------------------------------------------------------------
|
|
863
|
+
// outsmart create-damm-pool (DAMM v2 custom pool — full fee config)
|
|
864
|
+
// ---------------------------------------------------------------------------
|
|
865
|
+
program
|
|
866
|
+
.command("create-damm-pool")
|
|
867
|
+
.description("Create a Meteora DAMM v2 custom pool with full fee configuration")
|
|
868
|
+
.requiredOption("--base <mint>", "base token mint address")
|
|
869
|
+
.requiredOption("--base-amount <amount>", "initial base token deposit (human-readable)")
|
|
870
|
+
.requiredOption("--quote-amount <amount>", "initial quote token deposit (human-readable)")
|
|
871
|
+
.option("--quote <mint>", "quote token mint (default: WSOL)")
|
|
872
|
+
.option("--price <number>", "initial price in quote/base units (default: quoteAmount / baseAmount)")
|
|
873
|
+
.option("--max-fee <bps>", "max base fee in bps, charged at activation (default: 9900)", "9900")
|
|
874
|
+
.option("--min-fee <bps>", "min base fee in bps, reached after decay (default: 200)", "200")
|
|
875
|
+
.option("--periods <n>", "number of fee decay periods (default: 1440)", "1440")
|
|
876
|
+
.option("--duration <secs>", "total fee decay duration in seconds (default: 86400)", "86400")
|
|
877
|
+
.option("--fee-mode <0|1>", "fee scheduler: 0=linear, 1=exponential (default: 0)", "0")
|
|
878
|
+
.option("--dynamic-fee", "enable dynamic fee on top of base fee")
|
|
879
|
+
.option("--collect-mode <0|1>", "fee collection: 0=both tokens, 1=quote only (default: 1)", "1")
|
|
880
|
+
.option("--activation <timestamp>", "activation unix timestamp (default: immediate)")
|
|
881
|
+
.option("--alpha-vault", "create alpha vault after pool")
|
|
882
|
+
.option("--priority <microLamports>", "priority fee in microLamports per CU")
|
|
883
|
+
.option("--cu <units>", "compute unit limit")
|
|
884
|
+
.action(async (cmdOpts) => {
|
|
885
|
+
const adapter = (0, dex_1.getDexAdapter)("meteora-damm-v2");
|
|
886
|
+
const params = {
|
|
887
|
+
baseMint: cmdOpts.base,
|
|
888
|
+
quoteMint: cmdOpts.quote,
|
|
889
|
+
baseAmount: Number(cmdOpts.baseAmount),
|
|
890
|
+
quoteAmount: Number(cmdOpts.quoteAmount),
|
|
891
|
+
initPrice: cmdOpts.price ? Number(cmdOpts.price) : undefined,
|
|
892
|
+
poolFees: {
|
|
893
|
+
maxBaseFeeBps: Number(cmdOpts.maxFee),
|
|
894
|
+
minBaseFeeBps: Number(cmdOpts.minFee),
|
|
895
|
+
numberOfPeriod: Number(cmdOpts.periods),
|
|
896
|
+
totalDuration: Number(cmdOpts.duration),
|
|
897
|
+
feeSchedulerMode: Number(cmdOpts.feeMode),
|
|
898
|
+
useDynamicFee: !!cmdOpts.dynamicFee,
|
|
899
|
+
dynamicFeeConfig: null,
|
|
900
|
+
},
|
|
901
|
+
collectFeeMode: Number(cmdOpts.collectMode),
|
|
902
|
+
activationType: 1, // timestamp
|
|
903
|
+
activationPoint: cmdOpts.activation ? Number(cmdOpts.activation) : null,
|
|
904
|
+
hasAlphaVault: !!cmdOpts.alphaVault,
|
|
905
|
+
opts: buildSwapOpts(cmdOpts),
|
|
906
|
+
};
|
|
907
|
+
console.log(`\n creating DAMM v2 custom pool...`);
|
|
908
|
+
console.log(` base: ${cmdOpts.base}`);
|
|
909
|
+
console.log(` quote: ${cmdOpts.quote ?? "WSOL"}`);
|
|
910
|
+
console.log(` amounts: ${cmdOpts.baseAmount} base + ${cmdOpts.quoteAmount} quote`);
|
|
911
|
+
console.log(` fees: ${cmdOpts.maxFee} → ${cmdOpts.minFee} bps (${cmdOpts.feeMode === "1" ? "exponential" : "linear"})`);
|
|
912
|
+
const result = await adapter.createCustomPool(params);
|
|
913
|
+
console.log();
|
|
914
|
+
console.log(` tx: ${result.txSignature}`);
|
|
915
|
+
console.log(` confirmed: ${result.confirmed}`);
|
|
916
|
+
if (result.poolAddress)
|
|
917
|
+
console.log(` pool: ${result.poolAddress}`);
|
|
918
|
+
if (result.positionAddress)
|
|
919
|
+
console.log(` position: ${result.positionAddress}`);
|
|
920
|
+
if (result.error)
|
|
921
|
+
console.log(` error: ${result.error}`);
|
|
922
|
+
console.log();
|
|
923
|
+
});
|
|
924
|
+
// ---------------------------------------------------------------------------
|
|
925
|
+
// outsmart create-damm-config-pool (DAMM v2 config-based pool)
|
|
926
|
+
// ---------------------------------------------------------------------------
|
|
927
|
+
program
|
|
928
|
+
.command("create-damm-config-pool")
|
|
929
|
+
.description("Create a Meteora DAMM v2 pool using an existing config")
|
|
930
|
+
.requiredOption("--base <mint>", "base token mint address")
|
|
931
|
+
.requiredOption("--base-amount <amount>", "initial base token deposit (human-readable)")
|
|
932
|
+
.requiredOption("--quote-amount <amount>", "initial quote token deposit (human-readable)")
|
|
933
|
+
.requiredOption("--config <address>", "on-chain config address")
|
|
934
|
+
.option("--quote <mint>", "quote token mint (default: WSOL)")
|
|
935
|
+
.option("--price <number>", "initial price in quote/base units (default: quoteAmount / baseAmount)")
|
|
936
|
+
.option("--activation <timestamp>", "activation unix timestamp (default: immediate)")
|
|
937
|
+
.option("--lock", "permanently lock the initial liquidity")
|
|
938
|
+
.option("--priority <microLamports>", "priority fee in microLamports per CU")
|
|
939
|
+
.option("--cu <units>", "compute unit limit")
|
|
940
|
+
.action(async (cmdOpts) => {
|
|
941
|
+
const adapter = (0, dex_1.getDexAdapter)("meteora-damm-v2");
|
|
942
|
+
const params = {
|
|
943
|
+
baseMint: cmdOpts.base,
|
|
944
|
+
quoteMint: cmdOpts.quote,
|
|
945
|
+
baseAmount: Number(cmdOpts.baseAmount),
|
|
946
|
+
quoteAmount: Number(cmdOpts.quoteAmount),
|
|
947
|
+
initPrice: cmdOpts.price ? Number(cmdOpts.price) : undefined,
|
|
948
|
+
configAddress: cmdOpts.config,
|
|
949
|
+
activationPoint: cmdOpts.activation ? Number(cmdOpts.activation) : null,
|
|
950
|
+
lockLiquidity: !!cmdOpts.lock,
|
|
951
|
+
opts: buildSwapOpts(cmdOpts),
|
|
952
|
+
};
|
|
953
|
+
console.log(`\n creating DAMM v2 config-based pool...`);
|
|
954
|
+
console.log(` base: ${cmdOpts.base}`);
|
|
955
|
+
console.log(` quote: ${cmdOpts.quote ?? "WSOL"}`);
|
|
956
|
+
console.log(` config: ${cmdOpts.config}`);
|
|
957
|
+
console.log(` amounts: ${cmdOpts.baseAmount} base + ${cmdOpts.quoteAmount} quote`);
|
|
958
|
+
const result = await adapter.createConfigPool(params);
|
|
959
|
+
console.log();
|
|
960
|
+
console.log(` tx: ${result.txSignature}`);
|
|
961
|
+
console.log(` confirmed: ${result.confirmed}`);
|
|
962
|
+
if (result.poolAddress)
|
|
963
|
+
console.log(` pool: ${result.poolAddress}`);
|
|
964
|
+
if (result.positionAddress)
|
|
965
|
+
console.log(` position: ${result.positionAddress}`);
|
|
966
|
+
if (result.error)
|
|
967
|
+
console.log(` error: ${result.error}`);
|
|
968
|
+
console.log();
|
|
969
|
+
});
|
|
970
|
+
// ---------------------------------------------------------------------------
|
|
971
|
+
// outsmart list-dex
|
|
972
|
+
// ---------------------------------------------------------------------------
|
|
973
|
+
program
|
|
974
|
+
.command("list-dex")
|
|
975
|
+
.description("List all registered DEX adapters")
|
|
976
|
+
.option("--cap <capability>", "filter by capability (e.g. canBuy, canSell, canSnipe)")
|
|
977
|
+
.option("--json", "output as JSON")
|
|
978
|
+
.action((cmdOpts) => {
|
|
979
|
+
let adapters = (0, dex_1.listDexAdapters)();
|
|
980
|
+
if (cmdOpts.cap) {
|
|
981
|
+
const cap = cmdOpts.cap;
|
|
982
|
+
adapters = adapters.filter((a) => a.capabilities[cap]);
|
|
983
|
+
}
|
|
984
|
+
if (cmdOpts.json) {
|
|
985
|
+
console.log(JSON.stringify(adapters, null, 2));
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
console.log();
|
|
989
|
+
console.log(` ${adapters.length} DEX adapter(s) registered:\n`);
|
|
990
|
+
// Table header
|
|
991
|
+
const nameWidth = 22;
|
|
992
|
+
const protoWidth = 14;
|
|
993
|
+
console.log(` ${"NAME".padEnd(nameWidth)}${"PROTOCOL".padEnd(protoWidth)}CAPABILITIES`);
|
|
994
|
+
console.log(` ${"─".repeat(nameWidth)}${"─".repeat(protoWidth)}${"─".repeat(40)}`);
|
|
995
|
+
for (const a of adapters) {
|
|
996
|
+
const caps = Object.entries(a.capabilities)
|
|
997
|
+
.filter(([, v]) => v)
|
|
998
|
+
.map(([k]) => k.replace("can", "").toLowerCase())
|
|
999
|
+
.join(", ");
|
|
1000
|
+
console.log(` ${a.name.padEnd(nameWidth)}${a.protocol.padEnd(protoWidth)}${caps}`);
|
|
1001
|
+
}
|
|
1002
|
+
console.log();
|
|
1003
|
+
});
|
|
1004
|
+
// ---------------------------------------------------------------------------
|
|
1005
|
+
// outsmart config
|
|
1006
|
+
// ---------------------------------------------------------------------------
|
|
1007
|
+
const configCmd = new commander_1.Command("config")
|
|
1008
|
+
.description("View or update outsmart configuration");
|
|
1009
|
+
configCmd
|
|
1010
|
+
.command("show")
|
|
1011
|
+
.description("Show current configuration from environment")
|
|
1012
|
+
.action(() => {
|
|
1013
|
+
const envVars = [
|
|
1014
|
+
"SOLANA_RPC_URL",
|
|
1015
|
+
"RPC_URL",
|
|
1016
|
+
"HELIUS_API_KEY",
|
|
1017
|
+
"JITO_API_KEY",
|
|
1018
|
+
"BLOXROUTE_AUTH_HEADER",
|
|
1019
|
+
"NOZOMI_API_KEY",
|
|
1020
|
+
"BLOCKRAZOR_API_KEY",
|
|
1021
|
+
"NEXTBLOCK_API_KEY",
|
|
1022
|
+
"ZERO_SLOT_API_KEY",
|
|
1023
|
+
"SOYAS_API_KEY",
|
|
1024
|
+
"ASTRALANE_API_KEY",
|
|
1025
|
+
"STELLIUM_API_KEY",
|
|
1026
|
+
"FLASHBLOCK_API_KEY",
|
|
1027
|
+
"NODE1_API_KEY",
|
|
1028
|
+
"TX_LANDING_MODE",
|
|
1029
|
+
"TX_LANDING_PROVIDERS",
|
|
1030
|
+
"DEFAULT_TIP_SOL",
|
|
1031
|
+
"DEFAULT_SLIPPAGE_BPS",
|
|
1032
|
+
"DEFAULT_PRIORITY_FEE",
|
|
1033
|
+
];
|
|
1034
|
+
console.log();
|
|
1035
|
+
console.log(" outsmart configuration (from environment):\n");
|
|
1036
|
+
for (const key of envVars) {
|
|
1037
|
+
const val = process.env[key];
|
|
1038
|
+
if (val) {
|
|
1039
|
+
// Mask sensitive values
|
|
1040
|
+
const isSensitive = key.includes("KEY") || key.includes("AUTH") || key.includes("PRIVATE");
|
|
1041
|
+
const display = isSensitive ? val.slice(0, 6) + "..." + val.slice(-4) : val;
|
|
1042
|
+
console.log(` ${key.padEnd(28)} ${display}`);
|
|
1043
|
+
}
|
|
1044
|
+
else {
|
|
1045
|
+
console.log(` ${key.padEnd(28)} (not set)`);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
console.log();
|
|
1049
|
+
});
|
|
1050
|
+
configCmd
|
|
1051
|
+
.command("env")
|
|
1052
|
+
.description("Print a .env template with all supported variables")
|
|
1053
|
+
.action(() => {
|
|
1054
|
+
console.log(`# outsmart .env configuration
|
|
1055
|
+
# Copy this to .env in your project root
|
|
1056
|
+
|
|
1057
|
+
# ─── Solana RPC ───
|
|
1058
|
+
SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
|
|
1059
|
+
# RPC_URL= # fallback alias
|
|
1060
|
+
|
|
1061
|
+
# ─── Wallet ───
|
|
1062
|
+
# WALLET_PRIVATE_KEY= # base58 private key (or set WALLET_PATH)
|
|
1063
|
+
# WALLET_PATH= # path to keypair JSON file
|
|
1064
|
+
|
|
1065
|
+
# ─── TX Landing Providers (API keys) ───
|
|
1066
|
+
# HELIUS_API_KEY=
|
|
1067
|
+
# JITO_API_KEY=
|
|
1068
|
+
# BLOXROUTE_AUTH_HEADER=
|
|
1069
|
+
# NOZOMI_API_KEY=
|
|
1070
|
+
# BLOCKRAZOR_API_KEY=
|
|
1071
|
+
# NEXTBLOCK_API_KEY=
|
|
1072
|
+
# ZERO_SLOT_API_KEY=
|
|
1073
|
+
# SOYAS_API_KEY=
|
|
1074
|
+
# ASTRALANE_API_KEY=
|
|
1075
|
+
# STELLIUM_API_KEY=
|
|
1076
|
+
# FLASHBLOCK_API_KEY=
|
|
1077
|
+
# NODE1_API_KEY=
|
|
1078
|
+
|
|
1079
|
+
# ─── TX Landing Strategy ───
|
|
1080
|
+
# TX_LANDING_MODE=race # concurrent|race|random|sequential
|
|
1081
|
+
# TX_LANDING_PROVIDERS= # comma-separated provider names (empty = all enabled)
|
|
1082
|
+
# DEFAULT_TIP_SOL=0.001
|
|
1083
|
+
|
|
1084
|
+
# ─── Defaults ───
|
|
1085
|
+
# DEFAULT_SLIPPAGE_BPS=300 # 3%
|
|
1086
|
+
# DEFAULT_PRIORITY_FEE=4000 # microLamports per CU
|
|
1087
|
+
`);
|
|
1088
|
+
});
|
|
1089
|
+
program.addCommand(configCmd);
|
|
1090
|
+
// ---------------------------------------------------------------------------
|
|
1091
|
+
// outsmart init
|
|
1092
|
+
// ---------------------------------------------------------------------------
|
|
1093
|
+
program
|
|
1094
|
+
.command("init")
|
|
1095
|
+
.description("Set up outsmart — prompts for wallet key and RPC endpoint")
|
|
1096
|
+
.action(async () => {
|
|
1097
|
+
const readline = await Promise.resolve().then(() => __importStar(require("readline")));
|
|
1098
|
+
const fs = await Promise.resolve().then(() => __importStar(require("fs")));
|
|
1099
|
+
const path = await Promise.resolve().then(() => __importStar(require("path")));
|
|
1100
|
+
const rl = readline.createInterface({
|
|
1101
|
+
input: process.stdin,
|
|
1102
|
+
output: process.stdout,
|
|
1103
|
+
});
|
|
1104
|
+
const ask = (question) => new Promise((resolve) => rl.question(question, (answer) => resolve(answer.trim())));
|
|
1105
|
+
console.log();
|
|
1106
|
+
console.log(" outsmart init");
|
|
1107
|
+
console.log(" ─────────────");
|
|
1108
|
+
console.log();
|
|
1109
|
+
// Determine config location
|
|
1110
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "~";
|
|
1111
|
+
const globalDir = path.join(homeDir, ".outsmart");
|
|
1112
|
+
const globalConfig = path.join(globalDir, "config.env");
|
|
1113
|
+
const localConfig = path.join(process.cwd(), ".env");
|
|
1114
|
+
// Check if local .env already exists
|
|
1115
|
+
const hasLocalEnv = fs.existsSync(localConfig);
|
|
1116
|
+
const hasGlobalConfig = fs.existsSync(globalConfig);
|
|
1117
|
+
if (hasLocalEnv) {
|
|
1118
|
+
console.log(` Found existing .env at ${localConfig}`);
|
|
1119
|
+
}
|
|
1120
|
+
if (hasGlobalConfig) {
|
|
1121
|
+
console.log(` Found existing config at ${globalConfig}`);
|
|
1122
|
+
}
|
|
1123
|
+
// Ask for required values
|
|
1124
|
+
const privateKey = await ask(" Wallet private key (base58): ");
|
|
1125
|
+
if (!privateKey) {
|
|
1126
|
+
console.log("\n Aborted — no private key provided.\n");
|
|
1127
|
+
rl.close();
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
// Validate the key
|
|
1131
|
+
try {
|
|
1132
|
+
const bs58Module = await Promise.resolve().then(() => __importStar(require("bs58")));
|
|
1133
|
+
const { Keypair } = await Promise.resolve().then(() => __importStar(require("@solana/web3.js")));
|
|
1134
|
+
const decoded = bs58Module.default.decode(privateKey);
|
|
1135
|
+
const kp = Keypair.fromSecretKey(decoded);
|
|
1136
|
+
console.log(` Wallet: ${kp.publicKey.toBase58()}`);
|
|
1137
|
+
}
|
|
1138
|
+
catch {
|
|
1139
|
+
console.log("\n Error: invalid private key. Must be base58-encoded.\n");
|
|
1140
|
+
rl.close();
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
const rpcUrl = await ask(" RPC endpoint (e.g. https://mainnet.helius-rpc.com/?api-key=...): ");
|
|
1144
|
+
if (!rpcUrl) {
|
|
1145
|
+
console.log("\n Aborted — no RPC endpoint provided.\n");
|
|
1146
|
+
rl.close();
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
// Build config content
|
|
1150
|
+
const configContent = [
|
|
1151
|
+
"# outsmart configuration",
|
|
1152
|
+
`# Generated by outsmart init on ${new Date().toISOString()}`,
|
|
1153
|
+
"",
|
|
1154
|
+
"# ─── Required ───",
|
|
1155
|
+
`PRIVATE_KEY=${privateKey}`,
|
|
1156
|
+
`MAINNET_ENDPOINT=${rpcUrl}`,
|
|
1157
|
+
"",
|
|
1158
|
+
"# ─── TX Landing Providers (optional) ───",
|
|
1159
|
+
"# HELIUS_API_KEY=",
|
|
1160
|
+
"# JITO_API_KEY=",
|
|
1161
|
+
"# BLOXROUTE_AUTH_HEADER=",
|
|
1162
|
+
"# NOZOMI_API_KEY=",
|
|
1163
|
+
"# BLOCKRAZOR_API_KEY=",
|
|
1164
|
+
"# NEXTBLOCK_API_KEY=",
|
|
1165
|
+
"# ZERO_SLOT_API_KEY=",
|
|
1166
|
+
"# SOYAS_API_KEY=",
|
|
1167
|
+
"# ASTRALANE_API_KEY=",
|
|
1168
|
+
"# STELLIUM_API_KEY=",
|
|
1169
|
+
"# FLASHBLOCK_API_KEY=",
|
|
1170
|
+
"# NODE1_API_KEY=",
|
|
1171
|
+
"",
|
|
1172
|
+
"# ─── Trading Defaults (optional) ───",
|
|
1173
|
+
"# TX_LANDING_MODE=concurrent",
|
|
1174
|
+
"# DEFAULT_TIP_SOL=0.001",
|
|
1175
|
+
"# DEFAULT_SLIPPAGE_BPS=300",
|
|
1176
|
+
"# DEFAULT_PRIORITY_FEE=4000",
|
|
1177
|
+
"",
|
|
1178
|
+
].join("\n");
|
|
1179
|
+
// Write to global config (~/.outsmart/config.env)
|
|
1180
|
+
if (!fs.existsSync(globalDir)) {
|
|
1181
|
+
fs.mkdirSync(globalDir, { recursive: true });
|
|
1182
|
+
}
|
|
1183
|
+
fs.writeFileSync(globalConfig, configContent, { mode: 0o600 });
|
|
1184
|
+
console.log(`\n Config written to ${globalConfig}`);
|
|
1185
|
+
console.log(" (file permissions set to owner-only read/write)");
|
|
1186
|
+
// Also write local .env if we're in a project directory
|
|
1187
|
+
const hasPkgJson = fs.existsSync(path.join(process.cwd(), "package.json"));
|
|
1188
|
+
if (hasPkgJson && !hasLocalEnv) {
|
|
1189
|
+
fs.writeFileSync(localConfig, configContent, { mode: 0o600 });
|
|
1190
|
+
console.log(` Also written to ${localConfig}`);
|
|
1191
|
+
}
|
|
1192
|
+
console.log();
|
|
1193
|
+
console.log(" You're ready to trade:");
|
|
1194
|
+
console.log(" outsmart buy --dex raydium-cpmm --pool <POOL> --amount 0.1");
|
|
1195
|
+
console.log(" outsmart buy --dex jupiter-ultra --token <MINT> --amount 0.1");
|
|
1196
|
+
console.log(" outsmart list-dex");
|
|
1197
|
+
console.log();
|
|
1198
|
+
rl.close();
|
|
1199
|
+
});
|
|
1200
|
+
// ---------------------------------------------------------------------------
|
|
1201
|
+
// outsmart info
|
|
1202
|
+
// ---------------------------------------------------------------------------
|
|
1203
|
+
program
|
|
1204
|
+
.command("info")
|
|
1205
|
+
.description("Show token info from DexScreener")
|
|
1206
|
+
.requiredOption("-t, --token <mint>", "token mint address")
|
|
1207
|
+
.action(async (cmdOpts) => {
|
|
1208
|
+
const { getInfoFromDexscreener } = await Promise.resolve().then(() => __importStar(require("./dexscreener/info")));
|
|
1209
|
+
const info = await getInfoFromDexscreener(cmdOpts.token);
|
|
1210
|
+
console.log();
|
|
1211
|
+
console.log(` name: ${info.name}`);
|
|
1212
|
+
console.log(` address: ${info.address}`);
|
|
1213
|
+
console.log(` price: $${info.priceInUSD}`);
|
|
1214
|
+
console.log(` mcap: $${Number(info.marketCap).toLocaleString()}`);
|
|
1215
|
+
console.log(` age: ${info.pairAge}`);
|
|
1216
|
+
console.log(` liq (SOL): ${info.liquidityInSOL}`);
|
|
1217
|
+
console.log(` pool: ${info.poolId}`);
|
|
1218
|
+
console.log();
|
|
1219
|
+
console.log(` vol 5m/1h/6h/24h: ${info.volume5m} / ${info.volume1h} / ${info.volume6h} / ${info.volume24h}`);
|
|
1220
|
+
console.log(` buyers 5m/1h/6h/24h: ${info.buyers5m} / ${info.buyers1h} / ${info.buyers6h} / ${info.buyers24h}`);
|
|
1221
|
+
console.log();
|
|
1222
|
+
if (info.dexscreenerURL)
|
|
1223
|
+
console.log(` dexscreener: ${info.dexscreenerURL}`);
|
|
1224
|
+
if (info.twitterURL)
|
|
1225
|
+
console.log(` twitter: ${info.twitterURL}`);
|
|
1226
|
+
if (info.telegramURL)
|
|
1227
|
+
console.log(` telegram: ${info.telegramURL}`);
|
|
1228
|
+
if (info.websiteURL)
|
|
1229
|
+
console.log(` website: ${info.websiteURL}`);
|
|
1230
|
+
console.log();
|
|
1231
|
+
});
|
|
1232
|
+
// ---------------------------------------------------------------------------
|
|
1233
|
+
// Graceful shutdown — prevents hanging on Ctrl+C during long RPC calls
|
|
1234
|
+
// ---------------------------------------------------------------------------
|
|
1235
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
1236
|
+
process.on(signal, () => {
|
|
1237
|
+
console.log("\nShutting down...");
|
|
1238
|
+
process.exit(0);
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
// ---------------------------------------------------------------------------
|
|
1242
|
+
// Parse & run
|
|
1243
|
+
// ---------------------------------------------------------------------------
|
|
1244
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
1245
|
+
console.error(`\n fatal: ${err.message}\n`);
|
|
1246
|
+
if (process.env.DEBUG) {
|
|
1247
|
+
console.error(err.stack);
|
|
1248
|
+
}
|
|
1249
|
+
process.exit(1);
|
|
1250
|
+
});
|
|
1251
|
+
//# sourceMappingURL=cli.js.map
|