@otonix/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -0
- package/dist/commands/agent.d.ts +2 -0
- package/dist/commands/agent.js +173 -0
- package/dist/commands/launch.d.ts +2 -0
- package/dist/commands/launch.js +208 -0
- package/dist/commands/wallet.d.ts +2 -0
- package/dist/commands/wallet.js +166 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +44 -0
- package/dist/lib/chain.d.ts +15293 -0
- package/dist/lib/chain.js +42 -0
- package/dist/lib/config.d.ts +21 -0
- package/dist/lib/config.js +39 -0
- package/dist/lib/display.d.ts +14 -0
- package/dist/lib/display.js +50 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# @otonix/cli
|
|
2
|
+
|
|
3
|
+
Otonix CLI — deploy autonomous agent tokens on Base chain via Clanker v3.1 (LP Legacy).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @otonix/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or run locally (dev):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bash cli/otonix.sh <command>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# 1. Generate a new Base creator wallet
|
|
21
|
+
otonix wallet generate
|
|
22
|
+
|
|
23
|
+
# 2. Fund the wallet
|
|
24
|
+
# → Send ≥200 $OTX (agent activation)
|
|
25
|
+
# → Send ETH for gas
|
|
26
|
+
|
|
27
|
+
# 3. Create an agent (generates isolated Base wallet)
|
|
28
|
+
otonix agent:create --name "MyAgent"
|
|
29
|
+
|
|
30
|
+
# 4. Fund agent wallet with ~0.002 ETH (shown after create)
|
|
31
|
+
|
|
32
|
+
# 5. Deploy a token!
|
|
33
|
+
otonix launch token \
|
|
34
|
+
--agent MyAgent \
|
|
35
|
+
--name "CoolToken" \
|
|
36
|
+
--ticker "COOL" \
|
|
37
|
+
--description "My agent token"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Commands
|
|
41
|
+
|
|
42
|
+
### Wallet
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
otonix wallet generate # Generate new Base creator wallet
|
|
46
|
+
otonix wallet import # Import wallet from private key
|
|
47
|
+
otonix wallet export # Show private key (careful!)
|
|
48
|
+
otonix wallet balance # Show ETH + $OTX balance
|
|
49
|
+
otonix wallet address # Show wallet address
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Agents
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
otonix agent:create --name MyAgent # Create isolated agent wallet (requires 200 $OTX)
|
|
56
|
+
otonix agent:info MyAgent # Show agent address + balances
|
|
57
|
+
otonix agent:list # List all agents
|
|
58
|
+
otonix agent:remove MyAgent # Remove agent from local config
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Launch
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
otonix launch token \
|
|
65
|
+
--agent MyAgent \
|
|
66
|
+
--name "CoolToken" \
|
|
67
|
+
--ticker "COOL" \
|
|
68
|
+
--image "https://..." \ # Optional
|
|
69
|
+
--description "My token" \ # Optional
|
|
70
|
+
--mcap 10 \ # Initial market cap in ETH (default: 10)
|
|
71
|
+
--devbuy 0 # Dev buy in ETH (default: 0)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Config
|
|
75
|
+
|
|
76
|
+
Stored at `~/.otonix/config.json`. Contains wallet and agent private keys — keep it safe.
|
|
77
|
+
|
|
78
|
+
## Technical Details
|
|
79
|
+
|
|
80
|
+
- **LP Protocol**: Clanker v4 (Uniswap v4, POOL_POSITIONS.Standard)
|
|
81
|
+
- **Paired Token**: WETH on Base (`0x4200000000000000000000000000000000000006`)
|
|
82
|
+
- **Fee split**: 80% creator (WETH) / 20% Otonix treasury
|
|
83
|
+
- **Chain**: Base Mainnet
|
|
84
|
+
|
|
85
|
+
## Requirements
|
|
86
|
+
|
|
87
|
+
- Node.js ≥ 18
|
|
88
|
+
- ≥200 $OTX on creator wallet (Base)
|
|
89
|
+
- ETH in agent wallet for gas (~0.001–0.003 ETH per deploy)
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { confirm } from "@inquirer/prompts";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { loadConfig, saveConfig } from "../lib/config.js";
|
|
5
|
+
import { generateWallet, getEthBalance, getOtxBalance, formatOtx, getOtxBalance as checkOtx, OTX_REQUIRED, } from "../lib/chain.js";
|
|
6
|
+
import { header, row, success, failure, info, br, warn, mono } from "../lib/display.js";
|
|
7
|
+
export function agentCommand(program) {
|
|
8
|
+
program
|
|
9
|
+
.command("agent:create")
|
|
10
|
+
.description("Create a new autonomous agent with an isolated Base wallet")
|
|
11
|
+
.requiredOption("--name <name>", "Agent name (alphanumeric, no spaces)")
|
|
12
|
+
.action(async (opts) => {
|
|
13
|
+
header("Create Agent");
|
|
14
|
+
const name = opts.name.trim();
|
|
15
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
16
|
+
failure("Agent name must be alphanumeric (a-z, 0-9, _, -)");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const cfg = loadConfig();
|
|
20
|
+
if (!cfg.wallet) {
|
|
21
|
+
failure("No wallet. Run: otonix wallet generate first.");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
const spinner = ora("Checking $OTX balance...").start();
|
|
25
|
+
let otxBal;
|
|
26
|
+
try {
|
|
27
|
+
otxBal = await checkOtx(cfg.wallet.address);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
spinner.stop();
|
|
31
|
+
failure("Failed to check $OTX balance. Check your connection.");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
spinner.stop();
|
|
35
|
+
if (otxBal < OTX_REQUIRED) {
|
|
36
|
+
const have = formatOtx(otxBal);
|
|
37
|
+
failure(`Insufficient $OTX. Need 200.000, have ${have}.`);
|
|
38
|
+
info("Buy $OTX at: https://app.uniswap.org or on DexScreener");
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
if (cfg.agents[name]) {
|
|
42
|
+
const overwrite = await confirm({
|
|
43
|
+
message: `Agent "${name}" already exists. Replace it?`,
|
|
44
|
+
default: false,
|
|
45
|
+
});
|
|
46
|
+
if (!overwrite) {
|
|
47
|
+
info("Cancelled.");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const genSpinner = ora("Generating isolated agent wallet on Base...").start();
|
|
52
|
+
await new Promise(r => setTimeout(r, 600));
|
|
53
|
+
const agent = generateWallet();
|
|
54
|
+
genSpinner.stop();
|
|
55
|
+
cfg.agents[name] = {
|
|
56
|
+
address: agent.address,
|
|
57
|
+
privateKey: agent.privateKey,
|
|
58
|
+
createdAt: new Date().toISOString(),
|
|
59
|
+
};
|
|
60
|
+
saveConfig(cfg);
|
|
61
|
+
success(`Agent "${name}" created!`);
|
|
62
|
+
br();
|
|
63
|
+
row("Agent name", name);
|
|
64
|
+
row("Agent address", agent.address);
|
|
65
|
+
row("Created at", new Date().toLocaleString());
|
|
66
|
+
br();
|
|
67
|
+
console.log(chalk.dim("Next step — fund the agent wallet with ETH for gas:"));
|
|
68
|
+
console.log(" " + chalk.dim("Send") + " " + chalk.yellow("~0.001 ETH") + " " + chalk.dim("(Base) to:"));
|
|
69
|
+
console.log(" " + chalk.white(agent.address));
|
|
70
|
+
br();
|
|
71
|
+
console.log(chalk.dim("Then deploy a token:"));
|
|
72
|
+
console.log(" " + mono(`otonix launch token --agent ${name} --name "MyToken" --ticker "MTK"`));
|
|
73
|
+
});
|
|
74
|
+
program
|
|
75
|
+
.command("agent:info")
|
|
76
|
+
.description("Show info and balances for an agent")
|
|
77
|
+
.argument("<name>", "Agent name")
|
|
78
|
+
.action(async (name) => {
|
|
79
|
+
header(`Agent: ${name}`);
|
|
80
|
+
const cfg = loadConfig();
|
|
81
|
+
const agent = cfg.agents[name];
|
|
82
|
+
if (!agent) {
|
|
83
|
+
failure(`Agent "${name}" not found. Run: otonix agent:list`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
const spinner = ora("Fetching balances from Base...").start();
|
|
87
|
+
try {
|
|
88
|
+
const [eth, otx] = await Promise.all([
|
|
89
|
+
getEthBalance(agent.address),
|
|
90
|
+
getOtxBalance(agent.address),
|
|
91
|
+
]);
|
|
92
|
+
spinner.stop();
|
|
93
|
+
br();
|
|
94
|
+
row("Agent name", name);
|
|
95
|
+
row("Address", agent.address);
|
|
96
|
+
row("Created at", new Date(agent.createdAt).toLocaleString());
|
|
97
|
+
br();
|
|
98
|
+
row("ETH balance", parseFloat(eth).toFixed(6) + " ETH");
|
|
99
|
+
row("$OTX balance", formatOtx(otx) + " OTX");
|
|
100
|
+
br();
|
|
101
|
+
const ethNum = parseFloat(eth);
|
|
102
|
+
if (ethNum < 0.0005) {
|
|
103
|
+
console.log(warn("⚠ ") + chalk.dim("Low ETH — fund this address with ≥0.001 ETH for gas:"));
|
|
104
|
+
console.log(" " + chalk.white(agent.address));
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
console.log(chalk.green("✓ ") + chalk.dim("Ready to deploy tokens"));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
spinner.stop();
|
|
112
|
+
failure("Failed to fetch balances.");
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
program
|
|
117
|
+
.command("agent:list")
|
|
118
|
+
.description("List all your agents")
|
|
119
|
+
.action(async () => {
|
|
120
|
+
header("Your Agents");
|
|
121
|
+
const cfg = loadConfig();
|
|
122
|
+
const names = Object.keys(cfg.agents);
|
|
123
|
+
if (names.length === 0) {
|
|
124
|
+
br();
|
|
125
|
+
info("No agents yet. Create one:");
|
|
126
|
+
info(' otonix agent:create --name "MyAgent"');
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
br();
|
|
130
|
+
const spinner = ora(`Fetching balances for ${names.length} agent(s)...`).start();
|
|
131
|
+
const results = await Promise.all(names.map(async (n) => {
|
|
132
|
+
const a = cfg.agents[n];
|
|
133
|
+
try {
|
|
134
|
+
const eth = await getEthBalance(a.address);
|
|
135
|
+
return { name: n, address: a.address, eth: parseFloat(eth).toFixed(5) };
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return { name: n, address: a.address, eth: "—" };
|
|
139
|
+
}
|
|
140
|
+
}));
|
|
141
|
+
spinner.stop();
|
|
142
|
+
for (const r of results) {
|
|
143
|
+
console.log(chalk.bold.white(r.name.padEnd(18)) +
|
|
144
|
+
chalk.dim(r.address.slice(0, 10) + "...") +
|
|
145
|
+
" " +
|
|
146
|
+
chalk.yellow(r.eth + " ETH"));
|
|
147
|
+
}
|
|
148
|
+
br();
|
|
149
|
+
info(`${names.length} agent(s) total`);
|
|
150
|
+
});
|
|
151
|
+
program
|
|
152
|
+
.command("agent:remove")
|
|
153
|
+
.description("Remove an agent from local config")
|
|
154
|
+
.argument("<name>", "Agent name")
|
|
155
|
+
.action(async (name) => {
|
|
156
|
+
const cfg = loadConfig();
|
|
157
|
+
if (!cfg.agents[name]) {
|
|
158
|
+
failure(`Agent "${name}" not found.`);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
const ok = await confirm({
|
|
162
|
+
message: `Remove agent "${name}" (${cfg.agents[name].address})? This only removes local config, not on-chain.`,
|
|
163
|
+
default: false,
|
|
164
|
+
});
|
|
165
|
+
if (!ok) {
|
|
166
|
+
info("Cancelled.");
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
delete cfg.agents[name];
|
|
170
|
+
saveConfig(cfg);
|
|
171
|
+
success(`Agent "${name}" removed from local config.`);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { confirm } from "@inquirer/prompts";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { loadConfig, OTONIX_TREASURY } from "../lib/config.js";
|
|
5
|
+
import { getEthBalance, getOtxBalance, formatOtx, OTX_REQUIRED, BASE_RPC, } from "../lib/chain.js";
|
|
6
|
+
import { header, row, failure, info, br, warn, } from "../lib/display.js";
|
|
7
|
+
const WETH_BASE = "0x4200000000000000000000000000000000000006";
|
|
8
|
+
const OTONIX_TWITTER = "otonix_tech";
|
|
9
|
+
export function launchCommand(program) {
|
|
10
|
+
const launch = program.command("launch").description("Launch tokens via autonomous agents");
|
|
11
|
+
launch
|
|
12
|
+
.command("token")
|
|
13
|
+
.description("Deploy a token on Base via your agent (Clanker v4 / Uniswap v4)")
|
|
14
|
+
.requiredOption("--agent <name>", "Agent name to deploy with")
|
|
15
|
+
.requiredOption("--name <name>", "Token name (e.g. CoolToken)")
|
|
16
|
+
.requiredOption("--ticker <ticker>", "Token ticker/symbol (e.g. COOL)")
|
|
17
|
+
.requiredOption("--image <url>", "Token logo URL (https:// or ipfs://)")
|
|
18
|
+
.requiredOption("--description <text>", "Token description")
|
|
19
|
+
.option("--twitter <handle>", "Your Twitter/X handle (e.g. yourname) — shown in metadata")
|
|
20
|
+
.option("--devbuy <eth>", "Initial dev buy in ETH (default: 0)", "0")
|
|
21
|
+
.action(async (opts) => {
|
|
22
|
+
header("Launch Token");
|
|
23
|
+
const cfg = loadConfig();
|
|
24
|
+
if (!cfg.wallet) {
|
|
25
|
+
failure("No wallet. Run: otonix wallet generate");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const agent = cfg.agents[opts.agent];
|
|
29
|
+
if (!agent) {
|
|
30
|
+
failure(`Agent "${opts.agent}" not found. Run: otonix agent:list`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
const devBuyEth = parseFloat(opts.devbuy) || 0;
|
|
34
|
+
const minEthNeeded = 0.001 + devBuyEth;
|
|
35
|
+
const twitterHandle = opts.twitter?.replace(/^@/, "") ?? "";
|
|
36
|
+
const checkSpinner = ora("Checking requirements...").start();
|
|
37
|
+
let otxBal;
|
|
38
|
+
let agentEth;
|
|
39
|
+
try {
|
|
40
|
+
[otxBal, agentEth] = await Promise.all([
|
|
41
|
+
getOtxBalance(cfg.wallet.address),
|
|
42
|
+
getEthBalance(agent.address),
|
|
43
|
+
]);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
checkSpinner.stop();
|
|
47
|
+
failure("Failed to fetch balances. Check your connection.");
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
checkSpinner.stop();
|
|
51
|
+
let passed = true;
|
|
52
|
+
if (otxBal < OTX_REQUIRED) {
|
|
53
|
+
console.log(warn("⚠ ") + chalk.dim(`$OTX: ${formatOtx(otxBal)} / 200 required`));
|
|
54
|
+
passed = false;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
console.log(chalk.green("✓ ") + chalk.dim(`$OTX: ${formatOtx(otxBal)} (eligible)`));
|
|
58
|
+
}
|
|
59
|
+
if (parseFloat(agentEth) < minEthNeeded) {
|
|
60
|
+
console.log(warn("⚠ ") + chalk.dim(`Agent ETH: ${agentEth} — need ≥${minEthNeeded} ETH for gas${devBuyEth > 0 ? " + devbuy" : ""}`));
|
|
61
|
+
console.log(chalk.dim(" Send ETH to: ") + chalk.white(agent.address));
|
|
62
|
+
passed = false;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.log(chalk.green("✓ ") + chalk.dim(`Agent ETH: ${agentEth} (sufficient)`));
|
|
66
|
+
}
|
|
67
|
+
if (!passed) {
|
|
68
|
+
br();
|
|
69
|
+
failure("Pre-flight checks failed. Fix the issues above and try again.");
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
br();
|
|
73
|
+
console.log(chalk.bold.white("Token to deploy:"));
|
|
74
|
+
row("Name", opts.name);
|
|
75
|
+
row("Ticker", "$" + opts.ticker.toUpperCase());
|
|
76
|
+
row("Description", opts.description.slice(0, 60) + (opts.description.length > 60 ? "..." : ""));
|
|
77
|
+
row("Logo URL", opts.image.slice(0, 55) + (opts.image.length > 55 ? "..." : ""));
|
|
78
|
+
if (twitterHandle)
|
|
79
|
+
row("Twitter", "@" + twitterHandle);
|
|
80
|
+
row("Agent", opts.agent + " (" + agent.address.slice(0, 10) + "...)");
|
|
81
|
+
row("Chain", "Base Mainnet");
|
|
82
|
+
row("LP protocol", "Clanker v4 (Uniswap v4)");
|
|
83
|
+
row("Paired token", "WETH");
|
|
84
|
+
if (devBuyEth > 0)
|
|
85
|
+
row("Dev buy", devBuyEth + " ETH");
|
|
86
|
+
row("Fee split", "80% → you (WETH) / 20% → $OTX burn");
|
|
87
|
+
row("Verified by", "✓ Otonix (@" + OTONIX_TWITTER + ")");
|
|
88
|
+
br();
|
|
89
|
+
const confirmed = await confirm({
|
|
90
|
+
message: "Deploy this token? (agent wallet signs & pays gas)",
|
|
91
|
+
default: true,
|
|
92
|
+
});
|
|
93
|
+
if (!confirmed) {
|
|
94
|
+
info("Cancelled.");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const deploySpinner = ora("Deploying token on Base (Clanker v4 / Uniswap v4)...").start();
|
|
98
|
+
try {
|
|
99
|
+
const { createWalletClient, createPublicClient, http } = await import("viem");
|
|
100
|
+
const { base } = await import("viem/chains");
|
|
101
|
+
const { privateKeyToAccount } = await import("viem/accounts");
|
|
102
|
+
const { Clanker } = await import("clanker-sdk/v4");
|
|
103
|
+
const { POOL_POSITIONS, FEE_CONFIGS } = await import("clanker-sdk");
|
|
104
|
+
const agentAccount = privateKeyToAccount(agent.privateKey);
|
|
105
|
+
const publicClient = createPublicClient({
|
|
106
|
+
chain: base,
|
|
107
|
+
transport: http(BASE_RPC),
|
|
108
|
+
});
|
|
109
|
+
const walletClient = createWalletClient({
|
|
110
|
+
account: agentAccount,
|
|
111
|
+
chain: base,
|
|
112
|
+
transport: http(BASE_RPC),
|
|
113
|
+
});
|
|
114
|
+
const clanker = new Clanker({
|
|
115
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
116
|
+
wallet: walletClient,
|
|
117
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
118
|
+
publicClient: publicClient,
|
|
119
|
+
});
|
|
120
|
+
const socialUrls = [
|
|
121
|
+
{ platform: "x", url: `https://x.com/${OTONIX_TWITTER}` },
|
|
122
|
+
];
|
|
123
|
+
if (twitterHandle) {
|
|
124
|
+
socialUrls.push({ platform: "x", url: `https://x.com/${twitterHandle}` });
|
|
125
|
+
}
|
|
126
|
+
const tokenConfig = {
|
|
127
|
+
name: opts.name,
|
|
128
|
+
symbol: opts.ticker.toUpperCase(),
|
|
129
|
+
tokenAdmin: cfg.wallet.address,
|
|
130
|
+
image: opts.image,
|
|
131
|
+
metadata: {
|
|
132
|
+
description: opts.description,
|
|
133
|
+
socialMediaUrls: socialUrls,
|
|
134
|
+
auditUrls: [],
|
|
135
|
+
},
|
|
136
|
+
context: {
|
|
137
|
+
interface: "Otonix",
|
|
138
|
+
platform: "twitter",
|
|
139
|
+
messageId: OTONIX_TWITTER,
|
|
140
|
+
id: OTONIX_TWITTER,
|
|
141
|
+
},
|
|
142
|
+
pool: {
|
|
143
|
+
pairedToken: WETH_BASE,
|
|
144
|
+
positions: POOL_POSITIONS.Standard,
|
|
145
|
+
},
|
|
146
|
+
fees: FEE_CONFIGS.StaticBasic,
|
|
147
|
+
...(devBuyEth > 0 ? { devBuy: { ethAmount: devBuyEth } } : {}),
|
|
148
|
+
rewards: {
|
|
149
|
+
recipients: [
|
|
150
|
+
{
|
|
151
|
+
recipient: cfg.wallet.address,
|
|
152
|
+
admin: cfg.wallet.address,
|
|
153
|
+
bps: 8000,
|
|
154
|
+
token: "Paired",
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
recipient: OTONIX_TREASURY,
|
|
158
|
+
admin: OTONIX_TREASURY,
|
|
159
|
+
bps: 2000,
|
|
160
|
+
token: "Both",
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
const result = await clanker.deploy(tokenConfig);
|
|
166
|
+
if ("error" in result && result.error) {
|
|
167
|
+
deploySpinner.stop();
|
|
168
|
+
failure("Deploy failed: " + String(result.error.message ?? result.error));
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
const txHash = result.txHash;
|
|
172
|
+
deploySpinner.text = "Transaction sent — waiting for confirmation (~30s)...";
|
|
173
|
+
const waitResult = await result.waitForTransaction();
|
|
174
|
+
const tokenAddress = waitResult.address;
|
|
175
|
+
deploySpinner.stop();
|
|
176
|
+
br();
|
|
177
|
+
console.log(chalk.bold.hex("#4d6fff")("◈ Token deployed — Verified by Otonix ✓"));
|
|
178
|
+
console.log(chalk.dim("─".repeat(44)));
|
|
179
|
+
br();
|
|
180
|
+
row("Token name", opts.name);
|
|
181
|
+
row("Ticker", "$" + opts.ticker.toUpperCase());
|
|
182
|
+
row("Contract", tokenAddress ?? "—");
|
|
183
|
+
row("Tx hash", txHash.slice(0, 20) + "...");
|
|
184
|
+
row("Verified by", "✓ Otonix (@" + OTONIX_TWITTER + ")");
|
|
185
|
+
br();
|
|
186
|
+
row("Basescan", `https://basescan.org/token/${tokenAddress ?? ""}`);
|
|
187
|
+
row("DexScreener", `https://dexscreener.com/base/${tokenAddress ?? ""}`);
|
|
188
|
+
row("Clanker", `https://www.clanker.world/token/${tokenAddress ?? ""}`);
|
|
189
|
+
row("Otonix", `https://otonix.tech/launch`);
|
|
190
|
+
br();
|
|
191
|
+
}
|
|
192
|
+
catch (e) {
|
|
193
|
+
deploySpinner.stop();
|
|
194
|
+
if (e instanceof Error) {
|
|
195
|
+
failure("Deploy failed: " + e.message.slice(0, 300));
|
|
196
|
+
if (e.message.includes("insufficient funds")) {
|
|
197
|
+
br();
|
|
198
|
+
info("Agent ETH too low for gas. Send more ETH to:");
|
|
199
|
+
info(" " + agent.address);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
failure("Unexpected error: " + String(e));
|
|
204
|
+
}
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { password, confirm } from "@inquirer/prompts";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import { loadConfig, saveConfig } from "../lib/config.js";
|
|
4
|
+
import { getEthBalance, getOtxBalance, formatOtx, generateWallet, addressFromKey, OTX_REQUIRED, } from "../lib/chain.js";
|
|
5
|
+
import { header, row, success, failure, info, br, warn } from "../lib/display.js";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
export function walletCommand(program) {
|
|
8
|
+
const wallet = program.command("wallet").description("Manage your Otonix creator wallet");
|
|
9
|
+
wallet
|
|
10
|
+
.command("generate")
|
|
11
|
+
.description("Generate a new Base wallet (creator wallet for token launches)")
|
|
12
|
+
.action(async () => {
|
|
13
|
+
header("Generate Creator Wallet");
|
|
14
|
+
const cfg = loadConfig();
|
|
15
|
+
if (cfg.wallet) {
|
|
16
|
+
const overwrite = await confirm({
|
|
17
|
+
message: `Wallet ${chalk.dim(cfg.wallet.address)} already exists. Replace it?`,
|
|
18
|
+
default: false,
|
|
19
|
+
});
|
|
20
|
+
if (!overwrite) {
|
|
21
|
+
info("Cancelled. Existing wallet kept.");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const spinner = ora("Generating new Base wallet...").start();
|
|
26
|
+
await new Promise(r => setTimeout(r, 500));
|
|
27
|
+
const { address, privateKey } = generateWallet();
|
|
28
|
+
spinner.stop();
|
|
29
|
+
cfg.wallet = { address, privateKey };
|
|
30
|
+
saveConfig(cfg);
|
|
31
|
+
success("Creator wallet generated!");
|
|
32
|
+
br();
|
|
33
|
+
row("Address", address);
|
|
34
|
+
row("Network", "Base Mainnet");
|
|
35
|
+
row("Stored at", "~/.otonix/config.json");
|
|
36
|
+
br();
|
|
37
|
+
console.log(chalk.dim("Private key is stored locally. Never share it."));
|
|
38
|
+
console.log(chalk.dim("To view your private key: ") + chalk.white("otonix wallet export"));
|
|
39
|
+
br();
|
|
40
|
+
console.log(chalk.dim("Fund this address with:"));
|
|
41
|
+
console.log(" " + chalk.yellow("≥200 $OTX") + chalk.dim(" — to activate agents"));
|
|
42
|
+
console.log(" " + chalk.yellow("ETH") + chalk.dim(" — for gas fees on Base"));
|
|
43
|
+
br();
|
|
44
|
+
});
|
|
45
|
+
wallet
|
|
46
|
+
.command("import")
|
|
47
|
+
.description("Import an existing wallet using a private key")
|
|
48
|
+
.action(async () => {
|
|
49
|
+
header("Import Wallet");
|
|
50
|
+
info("Your private key is stored locally at ~/.otonix/config.json");
|
|
51
|
+
info("Never share it. Otonix never transmits it.");
|
|
52
|
+
br();
|
|
53
|
+
const cfg = loadConfig();
|
|
54
|
+
if (cfg.wallet) {
|
|
55
|
+
const overwrite = await confirm({
|
|
56
|
+
message: `Wallet ${chalk.dim(cfg.wallet.address)} already connected. Replace it?`,
|
|
57
|
+
default: false,
|
|
58
|
+
});
|
|
59
|
+
if (!overwrite) {
|
|
60
|
+
info("Cancelled.");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const pk = await password({
|
|
65
|
+
message: "Enter your private key (0x...):",
|
|
66
|
+
mask: "•",
|
|
67
|
+
});
|
|
68
|
+
if (!pk.startsWith("0x") || pk.length !== 66) {
|
|
69
|
+
failure("Invalid private key format. Must be 0x followed by 64 hex chars.");
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
const spinner = ora("Verifying key...").start();
|
|
73
|
+
try {
|
|
74
|
+
const address = addressFromKey(pk);
|
|
75
|
+
cfg.wallet = { address, privateKey: pk };
|
|
76
|
+
saveConfig(cfg);
|
|
77
|
+
spinner.stop();
|
|
78
|
+
success("Wallet imported!");
|
|
79
|
+
br();
|
|
80
|
+
row("Address", address);
|
|
81
|
+
row("Config", "~/.otonix/config.json");
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
spinner.stop();
|
|
85
|
+
failure("Invalid private key.");
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
wallet
|
|
90
|
+
.command("export")
|
|
91
|
+
.description("Show your private key (keep it safe!)")
|
|
92
|
+
.action(async () => {
|
|
93
|
+
const cfg = loadConfig();
|
|
94
|
+
if (!cfg.wallet) {
|
|
95
|
+
failure("No wallet. Run: otonix wallet generate");
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
const confirmed = await confirm({
|
|
99
|
+
message: chalk.red("⚠ This will display your private key. Continue?"),
|
|
100
|
+
default: false,
|
|
101
|
+
});
|
|
102
|
+
if (!confirmed) {
|
|
103
|
+
info("Cancelled.");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
header("Wallet Export");
|
|
107
|
+
br();
|
|
108
|
+
row("Address", cfg.wallet.address);
|
|
109
|
+
row("Private key", cfg.wallet.privateKey);
|
|
110
|
+
br();
|
|
111
|
+
console.log(chalk.red("Never share this key. Anyone with it controls your wallet."));
|
|
112
|
+
br();
|
|
113
|
+
});
|
|
114
|
+
wallet
|
|
115
|
+
.command("balance")
|
|
116
|
+
.description("Show ETH and $OTX balance of your wallet")
|
|
117
|
+
.action(async () => {
|
|
118
|
+
header("Wallet Balance");
|
|
119
|
+
const cfg = loadConfig();
|
|
120
|
+
if (!cfg.wallet) {
|
|
121
|
+
failure("No wallet. Run: otonix wallet generate");
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
const address = cfg.wallet.address;
|
|
125
|
+
const spinner = ora("Fetching balances from Base...").start();
|
|
126
|
+
try {
|
|
127
|
+
const [eth, otx] = await Promise.all([
|
|
128
|
+
getEthBalance(address),
|
|
129
|
+
getOtxBalance(address),
|
|
130
|
+
]);
|
|
131
|
+
spinner.stop();
|
|
132
|
+
br();
|
|
133
|
+
row("Address", address);
|
|
134
|
+
row("Network", "Base Mainnet");
|
|
135
|
+
row("ETH balance", parseFloat(eth).toFixed(6) + " ETH");
|
|
136
|
+
row("$OTX balance", formatOtx(otx) + " OTX");
|
|
137
|
+
br();
|
|
138
|
+
if (otx >= OTX_REQUIRED) {
|
|
139
|
+
console.log(chalk.green("✓ ") + chalk.dim("Eligible to create agents (≥200 $OTX)"));
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
const need = formatOtx(OTX_REQUIRED - otx);
|
|
143
|
+
console.log(warn("⚠ ") + chalk.dim(`Need ${need} more $OTX to activate agents`));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
spinner.stop();
|
|
148
|
+
failure("Failed to fetch balances. Check your internet connection.");
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
wallet
|
|
153
|
+
.command("address")
|
|
154
|
+
.description("Show your connected wallet address")
|
|
155
|
+
.action(() => {
|
|
156
|
+
const cfg = loadConfig();
|
|
157
|
+
if (!cfg.wallet) {
|
|
158
|
+
failure("No wallet. Run: otonix wallet generate");
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
header("Wallet Address");
|
|
162
|
+
br();
|
|
163
|
+
row("Address", cfg.wallet.address);
|
|
164
|
+
row("Network", "Base Mainnet");
|
|
165
|
+
});
|
|
166
|
+
}
|
package/dist/index.d.ts
ADDED