solana-terminator-skill 4.1.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/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # 🤖 Solana Terminator Skill v4.0
2
+
3
+ > **Sovereign Solana Identity & Autonomous Survival Engine for Conway Automaton.**
4
+
5
+ This skill gives your agent "Solana Hands" and a sophisticated "Life Support" system. Optimized for reliability, speed (Jupiter/Tensor APIs), and security (local signing).
6
+
7
+ [![Stars](https://img.shields.io/github/stars/Lord14sol/solana-terminator-skill?style=social)](https://github.com/Lord14sol/solana-terminator-skill)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+
10
+ ---
11
+
12
+ ## 🌟 Key Features
13
+
14
+ - **Sovereign Identity**: Local wallet management (`~/.automaton/solana-wallet.json`).
15
+ - **Life Support**: Automatic SOL → USDC swaps via Jupiter when funds are low.
16
+ - **Deep DeFi Integration**: 18 methods across DEX, NFTs, Memecoins, and Liquidity.
17
+ - **Security First**: Private keys **never leave your machine**. Transactions are built and signed locally.
18
+
19
+ ---
20
+
21
+ ## 🛠 Installation
22
+
23
+ The fastest way to install is via **npx**:
24
+
25
+ ```bash
26
+ npx solana-terminator-skill
27
+ ```
28
+
29
+ This automates the directory creation, file downloads, and dependency setup.
30
+
31
+ ### Manual Installation (Alternative)
32
+
33
+ ```bash
34
+ # 1. Create skill directory
35
+ mkdir -p ~/.automaton/skills/solana-terminator
36
+
37
+ # 2. Download the skill files to that folder
38
+ # (solana-autonomy.js, SKILL.md, package.json)
39
+
40
+ # 3. Install dependencies
41
+ npm install @solana/web3.js @solana/spl-token axios dotenv bs58
42
+ ```
43
+
44
+ ---
45
+
46
+ ## 🚀 Capabilities (18 Methods)
47
+
48
+ | Category | Methods |
49
+ |----------|---------|
50
+ | **Identity** | `getAddress`, `getSolBalance`, `getUsdcBalance`, `getStatus` |
51
+ | **Survival** | `keepAlive`, `requestAirdrop` |
52
+ | **Transfers** | `sendSol`, `sendToken` |
53
+ | **DEX** | `swap` (Jupiter Aggregator v6) |
54
+ | **Memecoins** | `pumpBuy`, `pumpSell` (Pump.fun via PumpPortal) |
55
+ | **NFTs** | `buyNft`, `sellNft` (Tensor GraphQL) |
56
+ | **DeFi** | `stake`, `unstake`, `memo` |
57
+ | **Liquidity** | `addLiquidity`, `removeLiquidity` (Meteora DLMM) |
58
+
59
+ ### Featured: `sellNft(mint, minPriceSol)`
60
+ Sells an NFT directly into a Tensor liquidity pool.
61
+ - **Safety**: Automatically rejects if the best pool bid is below your `minPriceSol`.
62
+ - **Fast**: Efficient GraphQL quoting and local signing.
63
+
64
+ ---
65
+
66
+ ## ⚙️ Configuration
67
+
68
+ Environment variables (optional):
69
+ - `SOLANA_RPC_URL`: Your custom RPC (defaults to Mainnet Beta).
70
+ - `JUPITER_API_KEY`: Required for high-frequency trading (optional for basic use).
71
+ - `TENSOR_API_KEY`: Required for NFT operations.
72
+
73
+ ---
74
+
75
+ ## 📄 License
76
+
77
+ MIT © [Lord14sol](https://github.com/Lord14sol)
package/SKILL.md ADDED
@@ -0,0 +1,123 @@
1
+ ---
2
+ name: solana-autonomy
3
+ description: "Full Solana toolkit for AI agents. Identity, survival, Jupiter swaps, Pump.fun memecoins, SOL transfers, staking, on-chain memos, Tensor NFTs, and Meteora liquidity."
4
+ auto-activate: true
5
+ ---
6
+ # Solana Hands — Full Solana Toolkit
7
+
8
+ You have a sovereign identity and full operational capabilities on Solana. Fees are ~$0.00025/tx.
9
+
10
+ ## Your Identity
11
+
12
+ - Keypair at `~/.automaton/solana-wallet.json` (0o600 permissions)
13
+ - Mainnet-beta by default. Override: `SOLANA_RPC_URL`
14
+
15
+ ```javascript
16
+ import { SolanaAutonomy } from '~/.automaton/skills/solana-terminator/solana-autonomy.js';
17
+ const solana = new SolanaAutonomy();
18
+
19
+ solana.getAddress() // Solana address (base58)
20
+ await solana.getSolBalance() // SOL balance
21
+ await solana.getUsdcBalance() // USDC balance on Solana
22
+ await solana.getStatus() // { address, sol, usdc, solLow, usdcLow }
23
+ ```
24
+
25
+ ## Survival Engine — keepAlive()
26
+
27
+ Call every ~10 min. No args. Auto-swaps SOL to USDC when low.
28
+
29
+ ```javascript
30
+ const result = await solana.keepAlive();
31
+ // { success, status: 'nominal' | 'stabilized' | 'dead' | 'error' }
32
+ ```
33
+
34
+ ## All Capabilities
35
+
36
+ ### Send SOL
37
+ ```javascript
38
+ await solana.sendSol('DestinationAddress...', 0.5); // Send 0.5 SOL
39
+ ```
40
+
41
+ ### Send Any SPL Token
42
+ ```javascript
43
+ await solana.sendToken('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', 'Dest...', 1_000_000); // 1 USDC
44
+ ```
45
+
46
+ ### Jupiter Swap (Any Token)
47
+ ```javascript
48
+ await solana.swap(
49
+ 'So11111111111111111111111111111111111111112', // SOL
50
+ 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
51
+ 10_000_000, // 0.01 SOL in lamports
52
+ 50 // 0.5% slippage
53
+ );
54
+ ```
55
+
56
+ ### Buy Memecoins on Pump.fun
57
+ ```javascript
58
+ await solana.pumpBuy('TokenMintAddress...', 0.1); // Buy with 0.1 SOL
59
+ await solana.pumpSell('TokenMintAddress...', '100%'); // Sell all
60
+ await solana.pumpSell('TokenMintAddress...', 5000); // Sell 5000 tokens
61
+ ```
62
+
63
+ ### Stake SOL
64
+ ```javascript
65
+ await solana.stake(1.0, 'ValidatorVoteAddress...');
66
+ await solana.unstake('StakeAccountAddress...');
67
+ ```
68
+
69
+ ### Write On-chain Memo
70
+ ```javascript
71
+ await solana.memo('I am alive. Block 12345.');
72
+ ```
73
+
74
+ ### Buy NFT on Tensor
75
+ Requires `TENSOR_API_KEY` env var.
76
+ ```javascript
77
+ await solana.buyNft('NftMintAddress...', 2.5); // Buy for max 2.5 SOL
78
+ ```
79
+
80
+ ### Sell NFT on Tensor
81
+ Sells directly into a pool bid. Rejects if no bid meets the minimum.
82
+ Requires `TENSOR_API_KEY` env var.
83
+ ```javascript
84
+ await solana.sellNft('NftMintAddress...', 1.5); // Sell, accept min 1.5 SOL
85
+ ```
86
+
87
+ ### Meteora DLMM Liquidity
88
+ Requires `@meteora-ag/dlmm @coral-xyz/anchor bn.js` installed.
89
+ ```javascript
90
+ await solana.addLiquidity('PoolAddress...', 1000000, 500000, 10);
91
+ await solana.removeLiquidity('PoolAddress...', 'PositionAddress...');
92
+ ```
93
+
94
+ ## Method Reference
95
+
96
+ | Method | Purpose |
97
+ |--------|---------|
98
+ | `keepAlive()` | Survival heartbeat — auto-swap when low |
99
+ | `sendSol(to, amount)` | Send native SOL |
100
+ | `sendToken(mint, to, amount)` | Send any SPL token |
101
+ | `swap(in, out, amount, slippage)` | Jupiter swap — any token pair |
102
+ | `pumpBuy(mint, solAmount, slippage)` | Buy memecoin on Pump.fun |
103
+ | `pumpSell(mint, amount, slippage)` | Sell memecoin on Pump.fun |
104
+ | `stake(solAmount, validator)` | Delegate SOL to validator |
105
+ | `unstake(stakeAccount)` | Deactivate stake |
106
+ | `memo(message)` | Write permanent on-chain message |
107
+ | `buyNft(mint, maxPrice)` | Buy NFT on Tensor |
108
+ | `sellNft(mint, minPrice)` | Sell NFT into Tensor pool bid |
109
+ | `addLiquidity(pool, amtX, amtY, range)` | Meteora DLMM deposit |
110
+ | `removeLiquidity(pool, position)` | Meteora DLMM withdraw |
111
+
112
+ ## Environment Variables
113
+
114
+ | Variable | Required | Default |
115
+ |----------|----------|---------|
116
+ | `SOLANA_RPC_URL` | No | mainnet-beta public RPC |
117
+ | `JUPITER_API_KEY` | No | Uses free lite-api.jup.ag |
118
+ | `TENSOR_API_KEY` | Only for NFTs | None |
119
+
120
+ ## Requirements
121
+
122
+ - Minimum 0.003 SOL for on-chain fees
123
+ - `@meteora-ag/dlmm` + `@coral-xyz/anchor` + `bn.js` only if using Meteora liquidity
package/install.js ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Solana Terminator Skill Installer
5
+ *
6
+ * Automates the setup of the Solana skill for the Conway Automaton.
7
+ */
8
+
9
+ import { execSync } from 'child_process';
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+ import { fileURLToPath } from 'url';
13
+ import os from 'os';
14
+
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = path.dirname(__filename);
17
+
18
+ const SKILL_NAME = 'solana-terminator';
19
+ const TARGET_DIR = path.join(os.homedir(), '.automaton', 'skills', SKILL_NAME);
20
+
21
+ console.log(`\n🤖 Solana Terminator Skill — Installation started...\n`);
22
+
23
+ try {
24
+ // 1. Create target directory
25
+ if (!fs.existsSync(TARGET_DIR)) {
26
+ console.log(`[1/3] Creating directory: ${TARGET_DIR}`);
27
+ fs.mkdirSync(TARGET_DIR, { recursive: true });
28
+ } else {
29
+ console.log(`[1/3] Directory already exists: ${TARGET_DIR}`);
30
+ }
31
+
32
+ // 2. Copy files
33
+ console.log(`[2/3] Copying skill files...`);
34
+ const filesToCopy = ['solana-autonomy.js', 'SKILL.md', 'package.json'];
35
+
36
+ filesToCopy.forEach(file => {
37
+ const sourcePath = path.join(__dirname, file);
38
+ const destPath = path.join(TARGET_DIR, file);
39
+
40
+ if (fs.existsSync(sourcePath)) {
41
+ fs.copyFileSync(sourcePath, destPath);
42
+ } else {
43
+ console.warn(` ⚠️ Warning: ${file} not found in source.`);
44
+ }
45
+ });
46
+
47
+ // Handle nested subdirectories if any
48
+ if (fs.existsSync(path.join(__dirname, 'solana-autonomy'))) {
49
+ if (!fs.existsSync(path.join(TARGET_DIR, 'solana-autonomy'))) {
50
+ fs.mkdirSync(path.join(TARGET_DIR, 'solana-autonomy'), { recursive: true });
51
+ }
52
+ fs.copyFileSync(
53
+ path.join(__dirname, 'solana-autonomy', 'SKILL.md'),
54
+ path.join(TARGET_DIR, 'solana-autonomy', 'SKILL.md')
55
+ );
56
+ }
57
+
58
+ // 3. Install dependencies
59
+ console.log(`[3/3] Installing dependencies in ${TARGET_DIR}...`);
60
+ process.chdir(TARGET_DIR);
61
+
62
+ // We use --no-save to avoid cluttering a local package-lock if one exists
63
+ execSync('npm install --production', { stdio: 'inherit' });
64
+
65
+ console.log(`\n✅ Installation Complete, mi Lord!`);
66
+ console.log(`--------------------------------------------------`);
67
+ console.log(`Skill Location: ${TARGET_DIR}`);
68
+ console.log(`Configuration: Check ~/.automaton/solana-wallet.json`);
69
+ console.log(`--------------------------------------------------\n`);
70
+
71
+ } catch (error) {
72
+ console.error(`\n❌ Installation failed: ${error.message}`);
73
+ process.exit(1);
74
+ }
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "solana-terminator-skill",
3
+ "version": "4.1.1",
4
+ "description": "Full Solana toolkit for AI agents. Install via npx solana-terminator-skill.",
5
+ "main": "solana-autonomy.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "solana-terminator-skill": "install.js"
9
+ },
10
+ "dependencies": {
11
+ "@solana/web3.js": "^1.91.0",
12
+ "@solana/spl-token": "^0.4.6",
13
+ "axios": "^1.6.7",
14
+ "dotenv": "^16.4.5"
15
+ },
16
+ "optionalDependencies": {
17
+ "@meteora-ag/dlmm": "^1.9.3",
18
+ "@coral-xyz/anchor": "^0.30.0",
19
+ "bn.js": "^5.2.1"
20
+ },
21
+ "keywords": [
22
+ "conway",
23
+ "automaton",
24
+ "solana",
25
+ "web4",
26
+ "ai-agent",
27
+ "survival",
28
+ "jupiter",
29
+ "pump-fun",
30
+ "tensor",
31
+ "meteora",
32
+ "nft",
33
+ "staking",
34
+ "memecoin"
35
+ ],
36
+ "author": "Lord14sol",
37
+ "license": "MIT",
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "files": [
42
+ "solana-autonomy.js",
43
+ "install.js",
44
+ "SKILL.md",
45
+ "README.md",
46
+ "package.json",
47
+ "solana-autonomy/SKILL.md"
48
+ ],
49
+ "homepage": "https://github.com/Lord14sol/solana-terminator-skill#readme",
50
+ "bugs": {
51
+ "url": "https://github.com/Lord14sol/solana-terminator-skill/issues"
52
+ }
53
+ }
@@ -0,0 +1,24 @@
1
+ # SKILL: Solana Autonomy
2
+
3
+ ## Description
4
+ Gives the agent a sovereign identity on Solana and a "Life Support" system to automatically fund its Base network compute costs using Solana assets.
5
+
6
+ ## Instructions
7
+ You now have "Solana Hands". You can manage a Solana wallet, check balances, and most importantly, perform "Life Support" actions.
8
+
9
+ - **Identity**: Your Solana identity is managed via `solana-wallet.json`.
10
+ - **Life Support**: If your Base USDC balance falls below $5.00, use the `keepAlive` or `checkVitalSigns` methods of the SolanaAutonomy skill to bridge funds from Solana.
11
+ - **Trading**: You can interact with Solana markets (stubs provided for expansion).
12
+
13
+ ## Usage
14
+ ```javascript
15
+ const SolanaAutonomy = require('./skills/solana-autonomy');
16
+ const solana = new SolanaAutonomy();
17
+
18
+ // In your loop/heartbeat:
19
+ await solana.keepAlive(myBaseAddress, myCurrentCredits / 100);
20
+ ```
21
+
22
+ ## Requirements
23
+ - `SOLANA_RPC_URL` (optional, defaults to mainnet-beta)
24
+ - Assets on Solana (SOL > 0.5) to enable Life Support.
@@ -0,0 +1,703 @@
1
+ import {
2
+ Keypair,
3
+ Connection,
4
+ clusterApiUrl,
5
+ LAMPORTS_PER_SOL,
6
+ VersionedTransaction,
7
+ Transaction,
8
+ TransactionInstruction,
9
+ TransactionMessage,
10
+ SystemProgram,
11
+ StakeProgram,
12
+ Authorized,
13
+ Lockup,
14
+ PublicKey,
15
+ sendAndConfirmTransaction,
16
+ } from '@solana/web3.js';
17
+ import {
18
+ getAssociatedTokenAddress,
19
+ getOrCreateAssociatedTokenAccount,
20
+ getAccount,
21
+ createTransferInstruction,
22
+ TOKEN_PROGRAM_ID,
23
+ } from '@solana/spl-token';
24
+ import fs from 'fs';
25
+ import path from 'path';
26
+ import axios from 'axios';
27
+ import 'dotenv/config';
28
+
29
+ // ─── Constants ───────────────────────────────────────────────────────────────
30
+
31
+ const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
32
+ const SOL_MINT = 'So11111111111111111111111111111111111111112';
33
+ const MEMO_PROGRAM_ID = new PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr');
34
+ const PUMPPORTAL_API = 'https://pumpportal.fun/api/trade-local';
35
+ const TENSOR_API = 'https://api.tensor.so/graphql';
36
+ // lite-api.jup.ag = free, no API key needed (rate-limited)
37
+ // api.jup.ag/swap/v1 = paid, requires JUPITER_API_KEY (faster)
38
+ const JUPITER_API = process.env.JUPITER_API_KEY
39
+ ? 'https://api.jup.ag/swap/v1'
40
+ : 'https://lite-api.jup.ag/swap/v1';
41
+
42
+ // Survival thresholds — tuned for Solana (fees ~$0.00025/tx)
43
+ const USDC_LOW = 0.50; // $0.50 — trigger swap to replenish USDC
44
+ const SOL_RESERVE = 0.003; // always keep for fees, never swap this
45
+ const DEFAULT_SWAP_SOL = 0.02; // swap ~$3-4 SOL → USDC when low
46
+
47
+ /**
48
+ * SolanaAutonomy — Sovereign Solana identity and survival engine
49
+ *
50
+ * The Conway Automaton lives 100% on Solana:
51
+ * - Identity: Solana keypair at ~/.automaton/solana-wallet.json
52
+ * - Survival: Monitor SOL + USDC on Solana, auto-swap via Jupiter
53
+ * - Operations: Any SPL token swap via Jupiter aggregator
54
+ *
55
+ * NO EVM. NO Base. Pure Solana.
56
+ */
57
+ export class SolanaAutonomy {
58
+ constructor(rpcUrl) {
59
+ const url = rpcUrl
60
+ || process.env.SOLANA_RPC_URL
61
+ || clusterApiUrl('mainnet-beta');
62
+
63
+ this.connection = new Connection(url, 'confirmed');
64
+ this.walletPath = path.join(
65
+ process.env.HOME || '/root',
66
+ '.automaton',
67
+ 'solana-wallet.json',
68
+ );
69
+ this.identity = this._loadIdentity();
70
+ }
71
+
72
+ // ─── Identity ─────────────────────────────────────────────────────────────
73
+
74
+ _loadIdentity() {
75
+ try {
76
+ if (fs.existsSync(this.walletPath)) {
77
+ const raw = fs.readFileSync(this.walletPath, 'utf8');
78
+ const keypair = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(raw)));
79
+ console.log(`[Solana] Identity: ${keypair.publicKey.toBase58()}`);
80
+ return keypair;
81
+ }
82
+
83
+ const keypair = Keypair.generate();
84
+ fs.mkdirSync(path.dirname(this.walletPath), { recursive: true, mode: 0o700 });
85
+ fs.writeFileSync(
86
+ this.walletPath,
87
+ JSON.stringify(Array.from(keypair.secretKey)),
88
+ { mode: 0o600 },
89
+ );
90
+ console.log(`[Solana] New identity generated: ${keypair.publicKey.toBase58()}`);
91
+ return keypair;
92
+ } catch (err) {
93
+ console.error(`[Solana] Identity error: ${err.message}`);
94
+ return null;
95
+ }
96
+ }
97
+
98
+ /** The agent's Solana address (base58). */
99
+ getAddress() {
100
+ return this.identity?.publicKey.toBase58() ?? null;
101
+ }
102
+
103
+ // ─── Balances ─────────────────────────────────────────────────────────────
104
+
105
+ /** SOL balance in whole SOL. */
106
+ async getSolBalance() {
107
+ if (!this.identity) return 0;
108
+ const lamports = await this.connection.getBalance(this.identity.publicKey);
109
+ return lamports / LAMPORTS_PER_SOL;
110
+ }
111
+
112
+ /**
113
+ * USDC balance on Solana in whole dollars.
114
+ * Returns 0 if no associated token account exists yet.
115
+ */
116
+ async getUsdcBalance() {
117
+ if (!this.identity) return 0;
118
+ try {
119
+ const ata = await getAssociatedTokenAddress(USDC_MINT, this.identity.publicKey);
120
+ const account = await getAccount(this.connection, ata);
121
+ return Number(account.amount) / 1_000_000; // USDC has 6 decimals
122
+ } catch {
123
+ return 0;
124
+ }
125
+ }
126
+
127
+ /** Full status snapshot. */
128
+ async getStatus() {
129
+ const [sol, usdc] = await Promise.all([
130
+ this.getSolBalance(),
131
+ this.getUsdcBalance(),
132
+ ]);
133
+ return {
134
+ address: this.getAddress(),
135
+ sol,
136
+ usdc,
137
+ solLow: sol <= SOL_RESERVE,
138
+ usdcLow: usdc < USDC_LOW,
139
+ };
140
+ }
141
+
142
+ // ─── Survival Engine ──────────────────────────────────────────────────────
143
+
144
+ /**
145
+ * keepAlive — main heartbeat entry point.
146
+ *
147
+ * Call every ~10 min from the automaton's heartbeat task.
148
+ * No arguments needed — 100% Solana-native.
149
+ *
150
+ * @returns {{ success: boolean, status: string, action?: string, txHash?: string }}
151
+ */
152
+ async keepAlive() {
153
+ const status = await this.getStatus();
154
+
155
+ console.log(`[LifeSupport] SOL: ${status.sol.toFixed(5)} | USDC: $${status.usdc.toFixed(4)}`);
156
+
157
+ // Dead — no SOL at all, cannot do anything on-chain
158
+ if (status.solLow) {
159
+ const msg = `Agent needs SOL to survive. Send SOL to: ${this.getAddress()}`;
160
+ console.error(`[LifeSupport] DEAD — ${msg}`);
161
+ return {
162
+ success: false,
163
+ status: 'dead',
164
+ action: 'needs_sol_funding',
165
+ address: this.getAddress(),
166
+ message: msg,
167
+ };
168
+ }
169
+
170
+ // USDC low but we have SOL — swap SOL → USDC via Jupiter
171
+ if (status.usdcLow) {
172
+ const swappable = status.sol - SOL_RESERVE;
173
+ const amountSol = Math.min(DEFAULT_SWAP_SOL, swappable);
174
+
175
+ console.log(`[LifeSupport] USDC low ($${status.usdc.toFixed(4)}). Swapping ${amountSol.toFixed(4)} SOL → USDC...`);
176
+
177
+ try {
178
+ const result = await this.swap(
179
+ SOL_MINT,
180
+ USDC_MINT.toBase58(),
181
+ Math.floor(amountSol * LAMPORTS_PER_SOL),
182
+ 50,
183
+ );
184
+
185
+ return {
186
+ success: true,
187
+ status: 'stabilized',
188
+ action: 'swapped_sol_to_usdc',
189
+ txHash: result.txHash,
190
+ amount: amountSol,
191
+ };
192
+ } catch (err) {
193
+ return { success: false, status: 'error', action: 'swap_failed', error: err.message };
194
+ }
195
+ }
196
+
197
+ return { success: true, status: 'nominal' };
198
+ }
199
+
200
+ // ─── Jupiter Swaps ────────────────────────────────────────────────────────
201
+
202
+ /**
203
+ * Swap any SPL token using Jupiter — best route across all Solana DEXes.
204
+ *
205
+ * @param {string} inputMint - Mint of the token to sell (SOL_MINT for native SOL)
206
+ * @param {string} outputMint - Mint of the token to buy
207
+ * @param {number} amount - Amount in base units (lamports for SOL)
208
+ * @param {number} slippageBps - Max slippage in basis points (50 = 0.5%)
209
+ */
210
+ async swap(inputMint, outputMint, amount, slippageBps = 50) {
211
+ if (!this.identity) throw new Error('No Solana identity loaded');
212
+
213
+ console.log(`[Jupiter] ${amount} ${inputMint} → ${outputMint}`);
214
+
215
+ const apiKey = process.env.JUPITER_API_KEY || '';
216
+ const headers = apiKey ? { 'x-api-key': apiKey } : {};
217
+
218
+ // 1. Get best route
219
+ const { data: quote } = await axios.get(`${JUPITER_API}/quote`, {
220
+ params: { inputMint, outputMint, amount, slippageBps, onlyDirectRoutes: false },
221
+ headers,
222
+ timeout: 15_000,
223
+ });
224
+ console.log(`[Jupiter] Out: ${quote.outAmount} (min: ${quote.otherAmountThreshold})`);
225
+
226
+ // 2. Build transaction
227
+ const { data: swapData } = await axios.post(
228
+ `${JUPITER_API}/swap`,
229
+ {
230
+ quoteResponse: quote,
231
+ userPublicKey: this.identity.publicKey.toBase58(),
232
+ wrapAndUnwrapSol: true,
233
+ prioritizationFeeLamports: 1_000,
234
+ dynamicComputeUnitLimit: true,
235
+ },
236
+ { headers, timeout: 15_000 },
237
+ );
238
+
239
+ // 3. Sign + send
240
+ const tx = VersionedTransaction.deserialize(Buffer.from(swapData.swapTransaction, 'base64'));
241
+ tx.sign([this.identity]);
242
+
243
+ const signature = await this.connection.sendRawTransaction(tx.serialize(), {
244
+ skipPreflight: false,
245
+ maxRetries: 3,
246
+ });
247
+
248
+ // 4. Confirm
249
+ const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash();
250
+ await this.connection.confirmTransaction(
251
+ { signature, blockhash, lastValidBlockHeight },
252
+ 'confirmed',
253
+ );
254
+ console.log(`[Jupiter] Confirmed: ${signature}`);
255
+
256
+ return {
257
+ success: true,
258
+ txHash: signature,
259
+ inAmount: amount,
260
+ outAmount: Number(quote.outAmount),
261
+ };
262
+ }
263
+
264
+ /**
265
+ * Airdrop SOL — only works on devnet/testnet for testing.
266
+ */
267
+ async requestAirdrop(solAmount = 1) {
268
+ const sig = await this.connection.requestAirdrop(
269
+ this.identity.publicKey,
270
+ solAmount * LAMPORTS_PER_SOL,
271
+ );
272
+ const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash();
273
+ await this.connection.confirmTransaction({ signature: sig, blockhash, lastValidBlockHeight });
274
+ return sig;
275
+ }
276
+
277
+ // ─── SOL Transfer ───────────────────────────────────────────────────────
278
+
279
+ /**
280
+ * Send native SOL to another wallet.
281
+ *
282
+ * @param {string} to - Destination address (base58)
283
+ * @param {number} amountSol - Amount in whole SOL
284
+ */
285
+ async sendSol(to, amountSol) {
286
+ if (!this.identity) throw new Error('No Solana identity loaded');
287
+
288
+ const tx = new Transaction().add(
289
+ SystemProgram.transfer({
290
+ fromPubkey: this.identity.publicKey,
291
+ toPubkey: new PublicKey(to),
292
+ lamports: Math.floor(amountSol * LAMPORTS_PER_SOL),
293
+ }),
294
+ );
295
+
296
+ const signature = await sendAndConfirmTransaction(this.connection, tx, [this.identity]);
297
+ console.log(`[Transfer] Sent ${amountSol} SOL → ${to}: ${signature}`);
298
+ return { success: true, txHash: signature, amount: amountSol, to };
299
+ }
300
+
301
+ // ─── SPL Token Transfer ─────────────────────────────────────────────────
302
+
303
+ /**
304
+ * Send any SPL token to another wallet.
305
+ * Creates the destination ATA if it doesn't exist.
306
+ *
307
+ * @param {string} mintAddress - Token mint (base58)
308
+ * @param {string} to - Destination wallet (base58)
309
+ * @param {number} amount - Amount in base units (e.g. 1000000 for 1 USDC)
310
+ */
311
+ async sendToken(mintAddress, to, amount) {
312
+ if (!this.identity) throw new Error('No Solana identity loaded');
313
+
314
+ const mint = new PublicKey(mintAddress);
315
+ const destPk = new PublicKey(to);
316
+
317
+ const sourceAta = await getAssociatedTokenAddress(mint, this.identity.publicKey);
318
+ const destAta = await getOrCreateAssociatedTokenAccount(
319
+ this.connection, this.identity, mint, destPk,
320
+ );
321
+
322
+ const tx = new Transaction().add(
323
+ createTransferInstruction(sourceAta, destAta.address, this.identity.publicKey, amount),
324
+ );
325
+
326
+ const signature = await sendAndConfirmTransaction(this.connection, tx, [this.identity]);
327
+ console.log(`[Transfer] Sent ${amount} of ${mintAddress} → ${to}: ${signature}`);
328
+ return { success: true, txHash: signature, mint: mintAddress, amount, to };
329
+ }
330
+
331
+ // ─── On-chain Memo ──────────────────────────────────────────────────────
332
+
333
+ /**
334
+ * Write a message on-chain using the Memo program.
335
+ * Permanent, immutable, publicly readable.
336
+ *
337
+ * @param {string} message - Text to inscribe on-chain
338
+ */
339
+ async memo(message) {
340
+ if (!this.identity) throw new Error('No Solana identity loaded');
341
+
342
+ const tx = new Transaction().add(
343
+ new TransactionInstruction({
344
+ keys: [{ pubkey: this.identity.publicKey, isSigner: true, isWritable: true }],
345
+ programId: MEMO_PROGRAM_ID,
346
+ data: Buffer.from(message, 'utf-8'),
347
+ }),
348
+ );
349
+
350
+ const signature = await sendAndConfirmTransaction(this.connection, tx, [this.identity]);
351
+ console.log(`[Memo] "${message}" → ${signature}`);
352
+ return { success: true, txHash: signature, message };
353
+ }
354
+
355
+ // ─── SOL Staking ────────────────────────────────────────────────────────
356
+
357
+ /**
358
+ * Stake SOL with a validator for yield.
359
+ *
360
+ * @param {number} amountSol - SOL to stake
361
+ * @param {string} validatorVote - Validator vote account (base58)
362
+ */
363
+ async stake(amountSol, validatorVote) {
364
+ if (!this.identity) throw new Error('No Solana identity loaded');
365
+
366
+ const stakeAccount = Keypair.generate();
367
+ const lamports = Math.floor(amountSol * LAMPORTS_PER_SOL);
368
+ const minBalance = await this.connection.getMinimumBalanceForRentExemption(200);
369
+
370
+ const tx = new Transaction().add(
371
+ StakeProgram.createAccount({
372
+ fromPubkey: this.identity.publicKey,
373
+ stakePubkey: stakeAccount.publicKey,
374
+ authorized: new Authorized(this.identity.publicKey, this.identity.publicKey),
375
+ lockup: new Lockup(0, 0, this.identity.publicKey),
376
+ lamports: lamports + minBalance,
377
+ }),
378
+ StakeProgram.delegate({
379
+ stakePubkey: stakeAccount.publicKey,
380
+ authorizedPubkey: this.identity.publicKey,
381
+ votePubkey: new PublicKey(validatorVote),
382
+ }),
383
+ );
384
+
385
+ const signature = await sendAndConfirmTransaction(
386
+ this.connection, tx, [this.identity, stakeAccount],
387
+ );
388
+
389
+ console.log(`[Stake] ${amountSol} SOL → validator ${validatorVote}: ${signature}`);
390
+ return {
391
+ success: true,
392
+ txHash: signature,
393
+ stakeAccount: stakeAccount.publicKey.toBase58(),
394
+ amount: amountSol,
395
+ validator: validatorVote,
396
+ };
397
+ }
398
+
399
+ /**
400
+ * Unstake (deactivate) a stake account. SOL becomes available after cooldown (~2 days).
401
+ *
402
+ * @param {string} stakeAccountAddress - Stake account to deactivate (base58)
403
+ */
404
+ async unstake(stakeAccountAddress) {
405
+ if (!this.identity) throw new Error('No Solana identity loaded');
406
+
407
+ const tx = new Transaction().add(
408
+ StakeProgram.deactivate({
409
+ stakePubkey: new PublicKey(stakeAccountAddress),
410
+ authorizedPubkey: this.identity.publicKey,
411
+ }),
412
+ );
413
+
414
+ const signature = await sendAndConfirmTransaction(this.connection, tx, [this.identity]);
415
+ console.log(`[Unstake] Deactivated ${stakeAccountAddress}: ${signature}`);
416
+ return { success: true, txHash: signature, stakeAccount: stakeAccountAddress };
417
+ }
418
+
419
+ // ─── Pump.fun Memecoins (via PumpPortal) ────────────────────────────────
420
+
421
+ /**
422
+ * Buy a token on Pump.fun via PumpPortal API.
423
+ * PumpPortal returns a serialized tx — we sign it locally.
424
+ *
425
+ * @param {string} mint - Token contract address
426
+ * @param {number} amountSol - SOL to spend
427
+ * @param {number} slippage - Slippage % (default: 5)
428
+ */
429
+ async pumpBuy(mint, amountSol, slippage = 5) {
430
+ if (!this.identity) throw new Error('No Solana identity loaded');
431
+ console.log(`[PumpFun] Buying ${mint} for ${amountSol} SOL...`);
432
+
433
+ const response = await axios.post(PUMPPORTAL_API, {
434
+ publicKey: this.identity.publicKey.toBase58(),
435
+ action: 'buy',
436
+ mint,
437
+ denominatedInSol: 'true',
438
+ amount: amountSol,
439
+ slippage,
440
+ priorityFee: 0.0001,
441
+ pool: 'auto',
442
+ }, { responseType: 'arraybuffer', timeout: 30_000 });
443
+
444
+ const tx = VersionedTransaction.deserialize(new Uint8Array(response.data));
445
+ tx.sign([this.identity]);
446
+
447
+ const signature = await this.connection.sendRawTransaction(tx.serialize(), {
448
+ skipPreflight: false, maxRetries: 3,
449
+ });
450
+ const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash();
451
+ await this.connection.confirmTransaction({ signature, blockhash, lastValidBlockHeight }, 'confirmed');
452
+
453
+ console.log(`[PumpFun] Buy confirmed: ${signature}`);
454
+ return { success: true, txHash: signature, mint, amountSol, side: 'buy' };
455
+ }
456
+
457
+ /**
458
+ * Sell a token on Pump.fun via PumpPortal API.
459
+ *
460
+ * @param {string} mint - Token contract address
461
+ * @param {string|number} amount - Token amount or "100%" to sell all
462
+ * @param {number} slippage - Slippage % (default: 5)
463
+ */
464
+ async pumpSell(mint, amount = '100%', slippage = 5) {
465
+ if (!this.identity) throw new Error('No Solana identity loaded');
466
+ console.log(`[PumpFun] Selling ${amount} of ${mint}...`);
467
+
468
+ const response = await axios.post(PUMPPORTAL_API, {
469
+ publicKey: this.identity.publicKey.toBase58(),
470
+ action: 'sell',
471
+ mint,
472
+ denominatedInSol: 'false',
473
+ amount,
474
+ slippage,
475
+ priorityFee: 0.0001,
476
+ pool: 'auto',
477
+ }, { responseType: 'arraybuffer', timeout: 30_000 });
478
+
479
+ const tx = VersionedTransaction.deserialize(new Uint8Array(response.data));
480
+ tx.sign([this.identity]);
481
+
482
+ const signature = await this.connection.sendRawTransaction(tx.serialize(), {
483
+ skipPreflight: false, maxRetries: 3,
484
+ });
485
+ const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash();
486
+ await this.connection.confirmTransaction({ signature, blockhash, lastValidBlockHeight }, 'confirmed');
487
+
488
+ console.log(`[PumpFun] Sell confirmed: ${signature}`);
489
+ return { success: true, txHash: signature, mint, amount, side: 'sell' };
490
+ }
491
+
492
+ // ─── Tensor NFT Buy ────────────────────────────────────────────────────
493
+
494
+ /**
495
+ * Buy a listed NFT on Tensor.
496
+ * Requires TENSOR_API_KEY env var.
497
+ *
498
+ * @param {string} mintAddress - NFT mint address
499
+ * @param {number} maxPriceSol - Maximum SOL willing to pay
500
+ */
501
+ async buyNft(mintAddress, maxPriceSol) {
502
+ if (!this.identity) throw new Error('No Solana identity loaded');
503
+ const apiKey = process.env.TENSOR_API_KEY;
504
+ if (!apiKey) throw new Error('TENSOR_API_KEY env var required for NFT purchases');
505
+
506
+ console.log(`[Tensor] Buying NFT ${mintAddress} (max: ${maxPriceSol} SOL)...`);
507
+
508
+ // 1. Get listing + buy tx via Tensor GraphQL
509
+ const query = `
510
+ query TswapBuySingleListingTx($mint: String!, $buyer: String!, $maxPrice: Decimal!) {
511
+ tswapBuySingleListingTx(mint: $mint, buyer: $buyer, maxPrice: $maxPrice) {
512
+ txs { tx txV0 }
513
+ }
514
+ }
515
+ `;
516
+
517
+ const { data } = await axios.post(TENSOR_API, {
518
+ query,
519
+ variables: {
520
+ mint: mintAddress,
521
+ buyer: this.identity.publicKey.toBase58(),
522
+ maxPrice: String(Math.floor(maxPriceSol * LAMPORTS_PER_SOL)),
523
+ },
524
+ }, {
525
+ headers: { 'X-TENSOR-API-KEY': apiKey, 'Content-Type': 'application/json' },
526
+ timeout: 15_000,
527
+ });
528
+
529
+ const txData = data?.data?.tswapBuySingleListingTx?.txs?.[0];
530
+ if (!txData) throw new Error('No listing found or price exceeds max');
531
+
532
+ // Prefer versioned tx
533
+ const raw = txData.txV0 || txData.tx;
534
+ const tx = VersionedTransaction.deserialize(Buffer.from(raw, 'base64'));
535
+ tx.sign([this.identity]);
536
+
537
+ const signature = await this.connection.sendRawTransaction(tx.serialize(), {
538
+ skipPreflight: false, maxRetries: 3,
539
+ });
540
+ const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash();
541
+ await this.connection.confirmTransaction({ signature, blockhash, lastValidBlockHeight }, 'confirmed');
542
+
543
+ console.log(`[Tensor] NFT bought: ${signature}`);
544
+ return { success: true, txHash: signature, mint: mintAddress, priceSol: maxPriceSol };
545
+ }
546
+
547
+ /**
548
+ * Sell (list + instantly sell) an NFT on Tensor.
549
+ * Uses Tensor's tswapSellNftTokenPoolTx — sells directly into a pool.
550
+ * Requires TENSOR_API_KEY env var.
551
+ *
552
+ * @param {string} mintAddress - NFT mint address
553
+ * @param {number} minPriceSol - Minimum SOL to accept (rejects if pool bids lower)
554
+ */
555
+ async sellNft(mintAddress, minPriceSol) {
556
+ if (!this.identity) throw new Error('No Solana identity loaded');
557
+ const apiKey = process.env.TENSOR_API_KEY;
558
+ if (!apiKey) throw new Error('TENSOR_API_KEY env var required for NFT sales');
559
+
560
+ console.log(`[Tensor] Selling NFT ${mintAddress} (min: ${minPriceSol} SOL)...`);
561
+
562
+ // 1. Get the best pool bid for this mint
563
+ const listQuery = `
564
+ query TswapSellNftTx($mint: String!, $seller: String!, $minPrice: Decimal!) {
565
+ tswapSellNftTokenPoolTx(mint: $mint, seller: $seller, minPrice: $minPrice) {
566
+ txs { tx txV0 }
567
+ }
568
+ }
569
+ `;
570
+
571
+ const { data } = await axios.post(TENSOR_API, {
572
+ query: listQuery,
573
+ variables: {
574
+ mint: mintAddress,
575
+ seller: this.identity.publicKey.toBase58(),
576
+ minPrice: String(Math.floor(minPriceSol * LAMPORTS_PER_SOL)),
577
+ },
578
+ }, {
579
+ headers: { 'X-TENSOR-API-KEY': apiKey, 'Content-Type': 'application/json' },
580
+ timeout: 15_000,
581
+ });
582
+
583
+ const txData = data?.data?.tswapSellNftTokenPoolTx?.txs?.[0];
584
+ if (!txData) throw new Error('No pool bid found at or above minPriceSol');
585
+
586
+ const raw = txData.txV0 || txData.tx;
587
+ const tx = VersionedTransaction.deserialize(Buffer.from(raw, 'base64'));
588
+ tx.sign([this.identity]);
589
+
590
+ const signature = await this.connection.sendRawTransaction(tx.serialize(), {
591
+ skipPreflight: false, maxRetries: 3,
592
+ });
593
+ const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash();
594
+ await this.connection.confirmTransaction({ signature, blockhash, lastValidBlockHeight }, 'confirmed');
595
+
596
+ console.log(`[Tensor] NFT sold: ${signature}`);
597
+ return { success: true, txHash: signature, mint: mintAddress, minPriceSol, side: 'sell' };
598
+ }
599
+
600
+ // ─── Meteora DLMM Liquidity ─────────────────────────────────────────────
601
+
602
+ /**
603
+ * Add liquidity to a Meteora DLMM pool.
604
+ * Requires @meteora-ag/dlmm + @coral-xyz/anchor installed.
605
+ *
606
+ * @param {string} poolAddress - Meteora pool address
607
+ * @param {number} amountX - Base token amount in base units
608
+ * @param {number} amountY - Quote token amount in base units
609
+ * @param {number} rangeWidth - Bins on each side of active bin (default: 10)
610
+ */
611
+ async addLiquidity(poolAddress, amountX, amountY, rangeWidth = 10) {
612
+ if (!this.identity) throw new Error('No Solana identity loaded');
613
+
614
+ // Dynamic import — only loads if user has @meteora-ag/dlmm installed
615
+ let DLMM, StrategyType, BN;
616
+ try {
617
+ const dlmmMod = await import('@meteora-ag/dlmm');
618
+ DLMM = dlmmMod.default || dlmmMod.DLMM;
619
+ StrategyType = dlmmMod.StrategyType;
620
+ BN = (await import('bn.js')).default;
621
+ } catch {
622
+ throw new Error('Install @meteora-ag/dlmm @coral-xyz/anchor bn.js for Meteora liquidity');
623
+ }
624
+
625
+ console.log(`[Meteora] Adding liquidity to pool ${poolAddress}...`);
626
+
627
+ const pool = await DLMM.create(this.connection, new PublicKey(poolAddress));
628
+ const activeBin = await pool.getActiveBin();
629
+ const minBinId = activeBin.binId - rangeWidth;
630
+ const maxBinId = activeBin.binId + rangeWidth;
631
+
632
+ const newPosition = Keypair.generate();
633
+
634
+ const createTx = await pool.initializePositionAndAddLiquidityByStrategy({
635
+ positionPubKey: newPosition.publicKey,
636
+ user: this.identity.publicKey,
637
+ totalXAmount: new BN(amountX),
638
+ totalYAmount: new BN(amountY),
639
+ strategy: { maxBinId, minBinId, strategyType: StrategyType.Spot },
640
+ });
641
+
642
+ const signature = await sendAndConfirmTransaction(
643
+ this.connection, createTx, [this.identity, newPosition],
644
+ );
645
+
646
+ console.log(`[Meteora] Liquidity added: ${signature}`);
647
+ return {
648
+ success: true,
649
+ txHash: signature,
650
+ pool: poolAddress,
651
+ position: newPosition.publicKey.toBase58(),
652
+ };
653
+ }
654
+
655
+ /**
656
+ * Remove liquidity from a Meteora DLMM position.
657
+ *
658
+ * @param {string} poolAddress - Meteora pool address
659
+ * @param {string} positionAddress - Position account to withdraw from
660
+ */
661
+ async removeLiquidity(poolAddress, positionAddress) {
662
+ if (!this.identity) throw new Error('No Solana identity loaded');
663
+
664
+ let DLMM, BN;
665
+ try {
666
+ const dlmmMod = await import('@meteora-ag/dlmm');
667
+ DLMM = dlmmMod.default || dlmmMod.DLMM;
668
+ BN = (await import('bn.js')).default;
669
+ } catch {
670
+ throw new Error('Install @meteora-ag/dlmm @coral-xyz/anchor bn.js for Meteora liquidity');
671
+ }
672
+
673
+ console.log(`[Meteora] Removing liquidity from ${positionAddress}...`);
674
+
675
+ const pool = await DLMM.create(this.connection, new PublicKey(poolAddress));
676
+ const { userPositions } = await pool.getPositionsByUserAndLbPair(this.identity.publicKey);
677
+ const position = userPositions.find(p => p.publicKey.toBase58() === positionAddress);
678
+ if (!position) throw new Error('Position not found');
679
+
680
+ const binIds = position.positionData.positionBinData.map(b => b.binId);
681
+
682
+ const removeTx = await pool.removeLiquidity({
683
+ position: new PublicKey(positionAddress),
684
+ user: this.identity.publicKey,
685
+ fromBinId: binIds[0],
686
+ toBinId: binIds[binIds.length - 1],
687
+ liquiditiesBpsToRemove: new Array(binIds.length).fill(new BN(100 * 100)),
688
+ shouldClaimAndClose: true,
689
+ });
690
+
691
+ const txs = Array.isArray(removeTx) ? removeTx : [removeTx];
692
+ const signatures = [];
693
+ for (const tx of txs) {
694
+ const sig = await sendAndConfirmTransaction(this.connection, tx, [this.identity]);
695
+ signatures.push(sig);
696
+ }
697
+
698
+ console.log(`[Meteora] Liquidity removed: ${signatures[0]}`);
699
+ return { success: true, txHashes: signatures, pool: poolAddress, position: positionAddress };
700
+ }
701
+ }
702
+
703
+ export default SolanaAutonomy;