@lightspeed-cli/speed-cli 0.1.1 → 0.1.3
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/.env.example +11 -0
- package/README.md +2 -1
- package/dist/cli.js +3 -1
- package/dist/commands/balance.js +2 -2
- package/dist/commands/bridge.js +30 -11
- package/dist/commands/config.js +6 -4
- package/dist/commands/estimate.js +19 -4
- package/dist/commands/gas.js +3 -0
- package/dist/commands/quote.js +18 -5
- package/dist/commands/skill.d.ts +2 -0
- package/dist/commands/skill.js +68 -0
- package/dist/commands/swap.js +4 -4
- package/dist/commands/volume.js +16 -1
- package/openclaw/skill.md +189 -0
- package/openclaw/skills/speed-token/SKILL.md +71 -20
- package/package.json +50 -47
package/.env.example
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Required: wallet (hex, with or without 0x prefix)
|
|
2
|
+
PRIVATE_KEY=
|
|
3
|
+
|
|
4
|
+
# 0x Swap API (https://dashboard.0x.org/)
|
|
5
|
+
0X_API_KEY=
|
|
6
|
+
|
|
7
|
+
# Squid Router (https://docs.squidrouter.com)
|
|
8
|
+
SQUID_INTEGRATOR_ID=
|
|
9
|
+
|
|
10
|
+
# RPC + history (https://dashboard.alchemy.com/)
|
|
11
|
+
ALCHEMY_API_KEY=
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ npx @lightspeed-cli/speed-cli whoami
|
|
|
17
17
|
npx @lightspeed-cli/speed-cli balance --json
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
**First-time setup:** Run `speed setup` to configure `~/.speed/.env` (private key, 0x API key, etc.). See `.env.example` for required vars.
|
|
20
|
+
**First-time setup:** Run `speed setup` to configure `~/.speed/.env` (private key, 0x API key, etc.). See `.env.example` for required vars (or run `speed setup --help` if the file is not present).
|
|
21
21
|
|
|
22
22
|
## Publish this package to npm
|
|
23
23
|
|
|
@@ -49,3 +49,4 @@ node dist/cli.js --help
|
|
|
49
49
|
npm link
|
|
50
50
|
speed whoami
|
|
51
51
|
```
|
|
52
|
+
**Shell note:** Examples use `||` for "or" (Bash). On PowerShell use `;` instead of `||` to chain commands.
|
package/dist/cli.js
CHANGED
|
@@ -23,11 +23,12 @@ import { historyCmd } from "./commands/history.js";
|
|
|
23
23
|
import { pendingCmd } from "./commands/pending.js";
|
|
24
24
|
import { setupCmd } from "./commands/setup.js";
|
|
25
25
|
import { xpCmd } from "./commands/xp.js";
|
|
26
|
+
import { skillCmd } from "./commands/skill.js";
|
|
26
27
|
const program = new Command();
|
|
27
28
|
program
|
|
28
29
|
.name("speed")
|
|
29
30
|
.description("Speed Token CLI: swap, bridge, balance, price, volume, dca, gas, history, xp, and more. Default token is Speed.")
|
|
30
|
-
.version("0.1.
|
|
31
|
+
.version("0.1.3")
|
|
31
32
|
.option("-y, --yes", "Skip confirmation for swap/bridge (safe for scripts)")
|
|
32
33
|
.option("--json", "Output machine-readable JSON to stdout (for scripts and OpenClaw)");
|
|
33
34
|
program.addCommand(whoamiCmd());
|
|
@@ -50,5 +51,6 @@ program.addCommand(gasCmd());
|
|
|
50
51
|
program.addCommand(historyCmd());
|
|
51
52
|
program.addCommand(pendingCmd());
|
|
52
53
|
program.addCommand(xpCmd());
|
|
54
|
+
program.addCommand(skillCmd());
|
|
53
55
|
program.addCommand(configCmd());
|
|
54
56
|
program.parse();
|
package/dist/commands/balance.js
CHANGED
|
@@ -25,7 +25,7 @@ async function getBalancesForChain(chainId, address, extraTokenAddresses) {
|
|
|
25
25
|
address: SPEED_TOKEN_ADDRESS,
|
|
26
26
|
symbol: speedSymbol,
|
|
27
27
|
balance: ethers.formatUnits(speedBal, speedDecimals),
|
|
28
|
-
decimals: speedDecimals,
|
|
28
|
+
decimals: Number(speedDecimals),
|
|
29
29
|
},
|
|
30
30
|
];
|
|
31
31
|
const extraAddrs = extraTokenAddresses
|
|
@@ -38,7 +38,7 @@ async function getBalancesForChain(chainId, address, extraTokenAddresses) {
|
|
|
38
38
|
token.symbol().catch(() => "???"),
|
|
39
39
|
token.balanceOf(address),
|
|
40
40
|
]);
|
|
41
|
-
return { address: a, symbol: sym, balance: ethers.formatUnits(bal, dec), decimals: dec };
|
|
41
|
+
return { address: a, symbol: sym, balance: ethers.formatUnits(bal, dec), decimals: Number(dec) };
|
|
42
42
|
}));
|
|
43
43
|
for (let i = 0; i < extraResults.length; i++) {
|
|
44
44
|
const result = extraResults[i];
|
package/dist/commands/bridge.js
CHANGED
|
@@ -5,7 +5,7 @@ import { getSigner } from "../wallet.js";
|
|
|
5
5
|
import { getSquid } from "../lib/squid.js";
|
|
6
6
|
import { getEthUsdPriceNumber } from "../lib/oracle.js";
|
|
7
7
|
import { ethers } from "ethers";
|
|
8
|
-
import { EXPLORER_URLS, SPEED_TOKEN_ADDRESS, resolveChainId, getChainOptionsHint } from "../constants.js";
|
|
8
|
+
import { EXPLORER_URLS, NATIVE_ETH_0X, NATIVE_SYMBOL, SPEED_TOKEN_ADDRESS, resolveChainId, getChainOptionsHint } from "../constants.js";
|
|
9
9
|
import { getDefaultChainInput } from "./config.js";
|
|
10
10
|
import { out, exitWithError, setJsonMode, isJsonMode, success, usageHint } from "../output.js";
|
|
11
11
|
import { addPendingBridge } from "../lib/pending-bridges.js";
|
|
@@ -22,13 +22,24 @@ function askConfirm(prompt) {
|
|
|
22
22
|
});
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
|
+
/** Resolve --from-token to Squid token address. speed → SPEED; native/eth/bnb/pol/matic → native sentinel. */
|
|
26
|
+
function resolveFromToken(opt, fromChainId) {
|
|
27
|
+
const s = (opt ?? "speed").trim().toLowerCase();
|
|
28
|
+
if (s === "speed")
|
|
29
|
+
return { address: SPEED_TOKEN_ADDRESS, isNative: false };
|
|
30
|
+
if (s === "native" || s === "eth" || s === "ether" || s === "bnb" || s === "pol" || s === "matic") {
|
|
31
|
+
return { address: NATIVE_ETH_0X, isNative: true };
|
|
32
|
+
}
|
|
33
|
+
return { address: SPEED_TOKEN_ADDRESS, isNative: false };
|
|
34
|
+
}
|
|
25
35
|
export function bridgeCmd() {
|
|
26
36
|
return new Command("bridge")
|
|
27
|
-
.description("Bridge Speed
|
|
37
|
+
.description("Bridge Speed or native token (ETH, BNB, POL) across chains via Squid. From-token: speed (default) or native/eth/bnb/pol/matic.")
|
|
28
38
|
.option("--from-chain <id|name>", "Source chain (ID or name, e.g. base or 8453)", "8453")
|
|
29
39
|
.option("--to-chain <id|name>", "Destination chain (ID or name, e.g. ethereum or 1)")
|
|
30
|
-
.option("-
|
|
31
|
-
.option("
|
|
40
|
+
.option("--from-token <speed|native|eth|bnb|pol|matic>", "Asset to bridge: speed (default) or chain native (eth on Ethereum/Base/OP/Arb, bnb on BNB, pol/matic on Polygon)", "speed")
|
|
41
|
+
.option("-a, --amount <amount>", "Amount to bridge (e.g. 0.1, 1000)")
|
|
42
|
+
.option("--to-token <address>", "Destination token address (default: same as from-token on to-chain; use for Speed → Speed)")
|
|
32
43
|
.action(async function (opts) {
|
|
33
44
|
setJsonMode(this.parent?.opts().json ?? false);
|
|
34
45
|
const yes = this.parent?.opts().yes ?? false;
|
|
@@ -44,7 +55,10 @@ export function bridgeCmd() {
|
|
|
44
55
|
exitWithError("--amount is required. Example: speed bridge --from-chain base --to-chain ethereum -a 1000", "MISSING_ARGS");
|
|
45
56
|
}
|
|
46
57
|
const amount = parseTokenAmountToWei(opts.amount.trim());
|
|
47
|
-
const
|
|
58
|
+
const { address: fromToken, isNative } = resolveFromToken(opts.fromToken, fromChainId);
|
|
59
|
+
const toToken = opts.toToken?.trim() ?? (isNative ? NATIVE_ETH_0X : SPEED_TOKEN_ADDRESS);
|
|
60
|
+
const nativeSymbol = NATIVE_SYMBOL[fromChainId] ?? "ETH";
|
|
61
|
+
const assetLabel = isNative ? nativeSymbol : "SPEED";
|
|
48
62
|
try {
|
|
49
63
|
const signer = getSigner(fromChainId);
|
|
50
64
|
const fromAddress = await signer.getAddress();
|
|
@@ -55,7 +69,7 @@ export function bridgeCmd() {
|
|
|
55
69
|
const { route, requestId } = await squid.getRoute({
|
|
56
70
|
fromAddress,
|
|
57
71
|
fromChain: String(fromChainId),
|
|
58
|
-
fromToken
|
|
72
|
+
fromToken,
|
|
59
73
|
fromAmount: amount,
|
|
60
74
|
toChain: String(toChainId),
|
|
61
75
|
toToken,
|
|
@@ -76,8 +90,8 @@ export function bridgeCmd() {
|
|
|
76
90
|
catch (_) { }
|
|
77
91
|
if (!yes && !isJsonMode()) {
|
|
78
92
|
const amountHuman = parseFloat(ethers.formatEther(amount)).toLocaleString(undefined, { maximumFractionDigits: 6 });
|
|
79
|
-
out(`Bridge ${amountHuman}
|
|
80
|
-
out(`Estimated gas: ${gasEth}
|
|
93
|
+
out(`Bridge ${amountHuman} ${assetLabel} from chain ${fromChainId} to chain ${toChainId}`);
|
|
94
|
+
out(`Estimated gas: ${gasEth} ${nativeSymbol} (~$${gasUsd})`);
|
|
81
95
|
const ok = await askConfirm("Proceed? [y/N] ");
|
|
82
96
|
if (!ok) {
|
|
83
97
|
out("Aborted.");
|
|
@@ -86,7 +100,7 @@ export function bridgeCmd() {
|
|
|
86
100
|
}
|
|
87
101
|
const txRequest = route.transactionRequest;
|
|
88
102
|
const target = txRequest?.target;
|
|
89
|
-
if (target) {
|
|
103
|
+
if (target && !isNative) {
|
|
90
104
|
const approveSpinner = isJsonMode() ? null : ora("Approving...").start();
|
|
91
105
|
const token = new ethers.Contract(SPEED_TOKEN_ADDRESS, ERC20_ABI, signer);
|
|
92
106
|
const txApprove = await token.approve(target, amount);
|
|
@@ -117,8 +131,10 @@ export function bridgeCmd() {
|
|
|
117
131
|
});
|
|
118
132
|
try {
|
|
119
133
|
const amountNum = parseFloat(ethers.formatEther(amount));
|
|
120
|
-
const
|
|
121
|
-
|
|
134
|
+
const usd = isNative
|
|
135
|
+
? amountNum * (await getEthUsdPriceNumber(fromChainId))
|
|
136
|
+
: amountNum * (await getSpeedUsdPriceNumber(fromChainId));
|
|
137
|
+
recordXpAction("bridge", usd);
|
|
122
138
|
}
|
|
123
139
|
catch (_) { }
|
|
124
140
|
const explorer = EXPLORER_URLS[fromChainId];
|
|
@@ -136,6 +152,9 @@ export function bridgeCmd() {
|
|
|
136
152
|
}
|
|
137
153
|
catch (e) {
|
|
138
154
|
const msg = e instanceof Error ? e.message : String(e);
|
|
155
|
+
if (msg.includes("429")) {
|
|
156
|
+
exitWithError(`Squid API rate limit. Wait a minute and try again, or bridge one chain at a time.${usageHint("bridge")}`, "BRIDGE_ERROR");
|
|
157
|
+
}
|
|
139
158
|
exitWithError(`${msg}.${usageHint("bridge")}`, "BRIDGE_ERROR");
|
|
140
159
|
}
|
|
141
160
|
});
|
package/dist/commands/config.js
CHANGED
|
@@ -55,13 +55,15 @@ export function configCmd() {
|
|
|
55
55
|
setJsonMode(this.parent?.parent?.opts().json ?? false);
|
|
56
56
|
const config = loadConfig();
|
|
57
57
|
if (key) {
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
58
|
+
const validKeys = ["default-chain", "default-slippage", "output-format"];
|
|
59
|
+
if (!validKeys.includes(key)) {
|
|
60
60
|
exitWithError(`Unknown key: ${key}. Valid keys: default-chain, default-slippage, output-format. Example: speed config get default-chain`, "INVALID_KEY");
|
|
61
|
+
}
|
|
62
|
+
const v = key === "default-chain" ? config.defaultChain : key === "default-slippage" ? config.defaultSlippage : config.outputFormat;
|
|
61
63
|
if (isJsonMode())
|
|
62
|
-
out({ [key]: v });
|
|
64
|
+
out({ [key]: v ?? null });
|
|
63
65
|
else
|
|
64
|
-
out(String(v));
|
|
66
|
+
out(v !== undefined && v !== null ? String(v) : "(not set)");
|
|
65
67
|
}
|
|
66
68
|
else {
|
|
67
69
|
if (isJsonMode())
|
|
@@ -4,7 +4,7 @@ import { getSigner } from "../wallet.js";
|
|
|
4
4
|
import { get0xQuote } from "../lib/zerox.js";
|
|
5
5
|
import { parseTokenAmountToWei } from "../lib/parse-amount.js";
|
|
6
6
|
import { getEthUsdPriceNumber } from "../lib/oracle.js";
|
|
7
|
-
import { resolveChainId, getChainOptionsHint } from "../constants.js";
|
|
7
|
+
import { resolveChainId, getChainOptionsHint, SPEED_TOKEN_ADDRESS, NATIVE_ETH_0X } from "../constants.js";
|
|
8
8
|
import { getDefaultChainInput } from "./config.js";
|
|
9
9
|
import { out, exitWithError, setJsonMode, isJsonMode, usageHint } from "../output.js";
|
|
10
10
|
export function estimateCmd() {
|
|
@@ -30,9 +30,23 @@ export function estimateCmd() {
|
|
|
30
30
|
gasLimit = 350000n;
|
|
31
31
|
}
|
|
32
32
|
else if (opts.buy && opts.amount) {
|
|
33
|
-
const
|
|
33
|
+
const toToken = (v, d) => {
|
|
34
|
+
if (!v)
|
|
35
|
+
return d;
|
|
36
|
+
const t = v.trim().toLowerCase();
|
|
37
|
+
if (t === "speed")
|
|
38
|
+
return SPEED_TOKEN_ADDRESS;
|
|
39
|
+
if (t === "eth" || t === "ether" || t === "native")
|
|
40
|
+
return NATIVE_ETH_0X;
|
|
41
|
+
return v.trim();
|
|
42
|
+
};
|
|
43
|
+
const sellToken = toToken(opts.sell, NATIVE_ETH_0X);
|
|
44
|
+
const buyToken = toToken(opts.buy, SPEED_TOKEN_ADDRESS);
|
|
45
|
+
if (sellToken.toLowerCase() === buyToken.toLowerCase()) {
|
|
46
|
+
exitWithError("Sell and buy tokens must be different. To buy Speed with ETH use --sell eth.", "SAME_TOKEN");
|
|
47
|
+
}
|
|
34
48
|
const amountWei = parseTokenAmountToWei(opts.amount.trim());
|
|
35
|
-
const quote = await get0xQuote(chainId, sellToken,
|
|
49
|
+
const quote = await get0xQuote(chainId, sellToken, buyToken, amountWei, taker);
|
|
36
50
|
if (quote.gas)
|
|
37
51
|
gasLimit = BigInt(quote.gas);
|
|
38
52
|
}
|
|
@@ -41,7 +55,8 @@ export function estimateCmd() {
|
|
|
41
55
|
const gasWei = gasLimit * gasPrice;
|
|
42
56
|
const gasEth = ethers.formatEther(gasWei);
|
|
43
57
|
const ethUsd = await getEthUsdPriceNumber(chainId);
|
|
44
|
-
const
|
|
58
|
+
const gasUsdNum = parseFloat(gasEth) * ethUsd;
|
|
59
|
+
const gasUsd = gasUsdNum > 0 && gasUsdNum < 0.01 ? gasUsdNum.toFixed(4) : gasUsdNum.toFixed(2);
|
|
45
60
|
if (isJsonMode()) {
|
|
46
61
|
out({ gasEth, gasUsd, gasLimit: gasLimit.toString(), gasPrice: gasPrice.toString() });
|
|
47
62
|
}
|
package/dist/commands/gas.js
CHANGED
|
@@ -122,6 +122,9 @@ export function gasCmd() {
|
|
|
122
122
|
}
|
|
123
123
|
catch (e) {
|
|
124
124
|
const msg = e instanceof Error ? e.message : String(e);
|
|
125
|
+
if (msg.includes("OVERFLOW") || msg.includes("overflow")) {
|
|
126
|
+
exitWithError(`Amount may be too large for this route (aggregator overflow). Try a smaller amount (e.g. 100 or 300).${usageHint("gas")}`, "GAS_ERROR");
|
|
127
|
+
}
|
|
125
128
|
exitWithError(`${msg}.${usageHint("gas")}`, "GAS_ERROR");
|
|
126
129
|
}
|
|
127
130
|
});
|
package/dist/commands/quote.js
CHANGED
|
@@ -3,15 +3,25 @@ import ora from "ora";
|
|
|
3
3
|
import { getAddress } from "../wallet.js";
|
|
4
4
|
import { get0xQuote } from "../lib/zerox.js";
|
|
5
5
|
import { parseTokenAmountToWei } from "../lib/parse-amount.js";
|
|
6
|
-
import { resolveChainId, getChainOptionsHint } from "../constants.js";
|
|
6
|
+
import { resolveChainId, getChainOptionsHint, SPEED_TOKEN_ADDRESS, NATIVE_ETH_0X } from "../constants.js";
|
|
7
7
|
import { getDefaultChainInput } from "./config.js";
|
|
8
8
|
import { out, exitWithError, setJsonMode, isJsonMode, usageHint } from "../output.js";
|
|
9
|
+
function toToken(v, defaultVal) {
|
|
10
|
+
if (!v)
|
|
11
|
+
return defaultVal;
|
|
12
|
+
const t = v.trim().toLowerCase();
|
|
13
|
+
if (t === "speed")
|
|
14
|
+
return SPEED_TOKEN_ADDRESS;
|
|
15
|
+
if (t === "eth" || t === "ether" || t === "native")
|
|
16
|
+
return NATIVE_ETH_0X;
|
|
17
|
+
return v.trim();
|
|
18
|
+
}
|
|
9
19
|
export function quoteCmd() {
|
|
10
20
|
return new Command("quote")
|
|
11
21
|
.description("Preview swap (no tx). To execute, use: speed swap --buy <addr> -a <amount> (approve is automatic)")
|
|
12
22
|
.option("-c, --chain <id|name>", "Chain ID or name", "8453")
|
|
13
|
-
.option("--sell <address>", "Sell token address (default: Speed
|
|
14
|
-
.option("--buy <address>", "Buy token address")
|
|
23
|
+
.option("--sell <address>", "Sell token: address or 'speed'|'eth'|'native' (default: native ETH). To buy Speed with ETH use --sell eth.")
|
|
24
|
+
.option("--buy <address>", "Buy token: address or 'speed'")
|
|
15
25
|
.option("-a, --amount <amount>", "Sell amount in token units (e.g. 0.002, 10000)")
|
|
16
26
|
.action(async function (opts) {
|
|
17
27
|
setJsonMode(this.parent?.opts().json ?? false);
|
|
@@ -22,8 +32,11 @@ export function quoteCmd() {
|
|
|
22
32
|
if (!opts.buy || !opts.amount) {
|
|
23
33
|
exitWithError("--buy and --amount are required", "MISSING_ARGS");
|
|
24
34
|
}
|
|
25
|
-
const sellToken = opts.sell
|
|
26
|
-
const buyToken = opts.buy
|
|
35
|
+
const sellToken = toToken(opts.sell, NATIVE_ETH_0X);
|
|
36
|
+
const buyToken = toToken(opts.buy, SPEED_TOKEN_ADDRESS);
|
|
37
|
+
if (sellToken.toLowerCase() === buyToken.toLowerCase()) {
|
|
38
|
+
exitWithError(`Sell and buy tokens must be different. To buy Speed with ETH use --sell eth (or --sell ${NATIVE_ETH_0X}).${usageHint("quote")}`, "SAME_TOKEN");
|
|
39
|
+
}
|
|
27
40
|
const amount = parseTokenAmountToWei(opts.amount.trim());
|
|
28
41
|
try {
|
|
29
42
|
const spinner = isJsonMode() ? null : ora("Fetching quote...").start();
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { out, setJsonMode, isJsonMode, success, exitWithError } from "../output.js";
|
|
6
|
+
/** Package root (parent of dist/). Resolved from dist/commands/skill.js at runtime. */
|
|
7
|
+
function getPackageRoot() {
|
|
8
|
+
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
return join(currentDir, "..", "..");
|
|
10
|
+
}
|
|
11
|
+
/** Path to the bundled OpenClaw skill file inside the installed package. */
|
|
12
|
+
function getBundledSkillPath() {
|
|
13
|
+
return join(getPackageRoot(), "openclaw", "skills", "speed-token", "SKILL.md");
|
|
14
|
+
}
|
|
15
|
+
export function skillCmd() {
|
|
16
|
+
const defaultDir = "openclaw";
|
|
17
|
+
const defaultFile = "skill.md";
|
|
18
|
+
return new Command("skill")
|
|
19
|
+
.description("Install or show path to the OpenClaw/Cursor skill for this CLI")
|
|
20
|
+
.option("--path", "Print path to the bundled SKILL.md and exit")
|
|
21
|
+
.option("-d, --dir <dir>", `Directory in project to write skill file (default: ${defaultDir})`, defaultDir)
|
|
22
|
+
.option("--file <name>", `Filename to write (default: ${defaultFile})`, defaultFile)
|
|
23
|
+
.option("-f, --force", "Overwrite existing file")
|
|
24
|
+
.action(async function (opts) {
|
|
25
|
+
setJsonMode(this.parent?.opts().json ?? false);
|
|
26
|
+
const bundledPath = getBundledSkillPath();
|
|
27
|
+
if (opts.path) {
|
|
28
|
+
if (isJsonMode()) {
|
|
29
|
+
out({ path: bundledPath });
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
out(bundledPath);
|
|
33
|
+
}
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (!existsSync(bundledPath)) {
|
|
37
|
+
exitWithError(`Bundled skill not found at ${bundledPath}. Reinstall the package.`, "SKILL_NOT_FOUND");
|
|
38
|
+
}
|
|
39
|
+
const cwd = process.cwd();
|
|
40
|
+
const targetDir = join(cwd, opts.dir ?? defaultDir);
|
|
41
|
+
const targetFile = opts.file ?? defaultFile;
|
|
42
|
+
const targetPath = join(targetDir, targetFile);
|
|
43
|
+
if (existsSync(targetPath) && !opts.force) {
|
|
44
|
+
if (isJsonMode()) {
|
|
45
|
+
out({ skipped: true, path: targetPath, message: "File exists; use --force to overwrite" });
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
out(`File exists: ${targetPath}. Use --force to overwrite.`);
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
mkdirSync(targetDir, { recursive: true });
|
|
54
|
+
const content = readFileSync(bundledPath, "utf-8");
|
|
55
|
+
writeFileSync(targetPath, content, "utf-8");
|
|
56
|
+
if (isJsonMode()) {
|
|
57
|
+
out({ installed: true, path: targetPath });
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
success(`Skill written to ${targetPath}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
65
|
+
exitWithError(`Failed to write skill: ${msg}`, "SKILL_INSTALL_ERROR");
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
package/dist/commands/swap.js
CHANGED
|
@@ -45,10 +45,10 @@ export function swapCmd() {
|
|
|
45
45
|
return new Command("swap")
|
|
46
46
|
.description("One-shot swap: get quote → confirm → approve (if needed) → execute. No need to run quote or approve separately.")
|
|
47
47
|
.option("-c, --chain <id|name>", "Chain ID or name (e.g. 8453, base)", "8453")
|
|
48
|
-
.option("--sell <address>", "Sell token (default: Speed
|
|
49
|
-
.option("--buy <address>", "Buy token (default: Speed)")
|
|
48
|
+
.option("--sell <address>", "Sell token: address, or 'speed'|'eth'|'native' (default: native ETH). To buy Speed with ETH use default or --sell eth.")
|
|
49
|
+
.option("--buy <address>", "Buy token: address or 'speed' (default: Speed)")
|
|
50
50
|
.option("-a, --amount <amount>", "Sell amount in token units (e.g. 0.002, 10000, or 0.002 eth / 100 speed)")
|
|
51
|
-
.option("--go", "Skip confirmation
|
|
51
|
+
.option("--go", "Skip confirmation (alias for -y/--yes for swap)")
|
|
52
52
|
.option("--dry-run", "Only fetch and print quote; no approval or swap tx")
|
|
53
53
|
.action(async function (opts) {
|
|
54
54
|
setJsonMode(this.parent?.opts().json ?? false);
|
|
@@ -74,7 +74,7 @@ export function swapCmd() {
|
|
|
74
74
|
const sellToken = toToken(opts.sell, NATIVE_ETH_0X);
|
|
75
75
|
const buyToken = toToken(opts.buy, SPEED_TOKEN_ADDRESS);
|
|
76
76
|
if (sellToken.toLowerCase() === buyToken.toLowerCase()) {
|
|
77
|
-
exitWithError(
|
|
77
|
+
exitWithError(`Sell and buy tokens must be different. To buy Speed with ETH use default or --sell eth (or --sell ${NATIVE_ETH_0X}).${usageHint("swap")}`, "SAME_TOKEN");
|
|
78
78
|
}
|
|
79
79
|
const amount = parseTokenAmountToWei(opts.amount.trim());
|
|
80
80
|
try {
|
package/dist/commands/volume.js
CHANGED
|
@@ -6,7 +6,7 @@ import { executeSwap } from "../lib/swap-execute.js";
|
|
|
6
6
|
import { getEthUsdPriceNumber } from "../lib/oracle.js";
|
|
7
7
|
import { recordXpAction } from "../lib/xp.js";
|
|
8
8
|
import { parseTokenAmountToWei } from "../lib/parse-amount.js";
|
|
9
|
-
import { resolveChainId, getChainOptionsHint, resolveTokenAddress, NATIVE_ETH_0X, EXPLORER_URLS, } from "../constants.js";
|
|
9
|
+
import { resolveChainId, getChainOptionsHint, resolveTokenAddress, NATIVE_ETH_0X, EXPLORER_URLS, CHAIN_NAMES, } from "../constants.js";
|
|
10
10
|
import { getDefaultChainInput } from "./config.js";
|
|
11
11
|
import { out, exitWithError, setJsonMode, isJsonMode, success, usageHint } from "../output.js";
|
|
12
12
|
const ERC20_BALANCE_ABI = ["function balanceOf(address owner) view returns (uint256)"];
|
|
@@ -36,6 +36,15 @@ function jitteredDelaySeconds(centerSec, jitterFraction) {
|
|
|
36
36
|
function sleepMs(ms) {
|
|
37
37
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
38
38
|
}
|
|
39
|
+
/** Turn RPC "insufficient funds" into a short, actionable message. */
|
|
40
|
+
function formatInsufficientFundsMessage(chainId, rawMessage) {
|
|
41
|
+
const chainName = CHAIN_NAMES[chainId] ?? String(chainId);
|
|
42
|
+
const haveMatch = rawMessage.match(/have\s+(\d+)/);
|
|
43
|
+
const wantMatch = rawMessage.match(/want\s+(\d+)/);
|
|
44
|
+
const haveEth = haveMatch ? ethers.formatEther(haveMatch[1]) : "?";
|
|
45
|
+
const wantEth = wantMatch ? ethers.formatEther(wantMatch[1]) : "?";
|
|
46
|
+
return `Insufficient native balance on ${chainName}: have ~${haveEth} ETH, need ~${wantEth} ETH for this op. Run \`speed gas -c ${chainId} -a <amount> -y\` to refuel.`;
|
|
47
|
+
}
|
|
39
48
|
export function volumeCmd() {
|
|
40
49
|
return new Command("volume")
|
|
41
50
|
.description("Human-like volume: interleaved buys and sells (native ETH ↔ token). Default token is Speed; use --token for others.")
|
|
@@ -175,6 +184,9 @@ export function volumeCmd() {
|
|
|
175
184
|
if (spinner)
|
|
176
185
|
spinner.fail(`Op ${op}/${ops} sell failed`);
|
|
177
186
|
const msg = e instanceof Error ? e.message : String(e);
|
|
187
|
+
if (msg.toLowerCase().includes("insufficient funds")) {
|
|
188
|
+
exitWithError(formatInsufficientFundsMessage(chainId, msg), "INSUFFICIENT_FUNDS");
|
|
189
|
+
}
|
|
178
190
|
failures.push({ op, phase: "sell", error: msg });
|
|
179
191
|
if (!isJsonMode())
|
|
180
192
|
out(` → ${msg.slice(0, 80)}${msg.length > 80 ? "…" : ""}`);
|
|
@@ -202,6 +214,9 @@ export function volumeCmd() {
|
|
|
202
214
|
if (spinner)
|
|
203
215
|
spinner.fail(`Op ${op}/${ops} buy failed`);
|
|
204
216
|
const msg = e instanceof Error ? e.message : String(e);
|
|
217
|
+
if (msg.toLowerCase().includes("insufficient funds")) {
|
|
218
|
+
exitWithError(formatInsufficientFundsMessage(chainId, msg), "INSUFFICIENT_FUNDS");
|
|
219
|
+
}
|
|
205
220
|
failures.push({ op, phase: "buy", error: msg });
|
|
206
221
|
if (!isJsonMode())
|
|
207
222
|
out(` → ${msg.slice(0, 80)}${msg.length > 80 ? "…" : ""}`);
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: speed-token
|
|
3
|
+
description: The Best Full trading and bridging CLI for agents (and humans) via OpenClaw, swap, bridge, balance, price, quote, gas, send, approve, allowance, revoke, history, pending, status, estimate, doctor, whoami, config, volume, dca, xp. Use --json for machine-readable output. Powered by Lightspeed multi-chain.
|
|
4
|
+
permissions:
|
|
5
|
+
- network:outbound
|
|
6
|
+
triggers:
|
|
7
|
+
- "speed"
|
|
8
|
+
- "speed token"
|
|
9
|
+
- "speed swap"
|
|
10
|
+
- "speed bridge"
|
|
11
|
+
- "speed balance"
|
|
12
|
+
- "speed quote"
|
|
13
|
+
- "speed price"
|
|
14
|
+
- "speed gas"
|
|
15
|
+
- "speed send"
|
|
16
|
+
- "speed approve"
|
|
17
|
+
- "speed allowance"
|
|
18
|
+
- "speed revoke"
|
|
19
|
+
- "speed history"
|
|
20
|
+
- "speed pending"
|
|
21
|
+
- "speed status"
|
|
22
|
+
- "speed doctor"
|
|
23
|
+
- "speed whoami"
|
|
24
|
+
- "speed config"
|
|
25
|
+
- "speed setup"
|
|
26
|
+
- "speed estimate"
|
|
27
|
+
- "speed volume"
|
|
28
|
+
- "speed dca"
|
|
29
|
+
- "speed xp"
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
# Speed Token CLI — OpenClaw skill
|
|
33
|
+
|
|
34
|
+
Use the **speed** CLI via bash. **Always pass `--json`** so output is machine-readable. Use **`-y`** or **`--yes`** to skip confirmation when executing swap/bridge (required for non-interactive use).
|
|
35
|
+
|
|
36
|
+
**Invocation:** From the project directory run `node dist/cli.js <command> [options] --json`, or if `speed` is on PATH: `speed <command> [options] --json`. From another directory, use the full path to `dist/cli.js` or ensure `speed` is on PATH.
|
|
37
|
+
|
|
38
|
+
**Chains:** Every `--chain`, `--from-chain`, `--to-chain` accepts **chain ID** (e.g. `8453`) or **name** (e.g. `base`, `ethereum`, `op`, `arb`, `polygon`, `bnb`). Supported: 1 (ethereum), 8453 (base), 10 (optimism), 42161 (arbitrum), 137 (polygon), 56 (bnb).
|
|
39
|
+
|
|
40
|
+
**Amounts:** Use **token units** (e.g. `-a 0.002`, `-a 1000`), not wei. Commands that take `-a` accept human-readable amounts.
|
|
41
|
+
|
|
42
|
+
**Speed token (default):** Address `0xB01CF1bE9568f09449382a47Cd5bF58e2A9D5922`. Volume, DCA, and gas default to Speed; use `--token <address|speed>` to use another token.
|
|
43
|
+
|
|
44
|
+
**Errors in --json mode:** CLI prints `{"error":"...", "code":"..."}` to stdout and exits 1. Parse and surface the `error` string to the user.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Global options (before command)
|
|
49
|
+
|
|
50
|
+
- `--json` — **Always use.** JSON to stdout; errors as `{ error, code }`.
|
|
51
|
+
- `-y, --yes` — Skip confirmation (required for swap, bridge, and gas in non-interactive use).
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Commands reference
|
|
56
|
+
|
|
57
|
+
### Identity & setup
|
|
58
|
+
|
|
59
|
+
- **whoami** — Print wallet address (from PRIVATE_KEY). No args.
|
|
60
|
+
`speed whoami --json`
|
|
61
|
+
If this fails, user must run `speed setup` (interactive; do not run from bot).
|
|
62
|
+
|
|
63
|
+
- **setup** — Interactive; writes secrets to `~/.speed/.env`. **Do not invoke from bots.** Direct user to run `speed setup` if whoami or doctor fails.
|
|
64
|
+
|
|
65
|
+
- **doctor** — Validate env, API keys, RPC, oracles, Speed balance.
|
|
66
|
+
`speed doctor --json` or `speed doctor -c base --json`
|
|
67
|
+
|
|
68
|
+
### Balance & price
|
|
69
|
+
|
|
70
|
+
- **balance** — Speed + native + optional extra tokens. Omit `-c` for all chains.
|
|
71
|
+
`speed balance --json`
|
|
72
|
+
`speed balance -c base --json`
|
|
73
|
+
`speed balance -c base -t <token-address> --json` (repeat `-t` for multiple)
|
|
74
|
+
|
|
75
|
+
- **price** — Speed/ETH, Speed/USD, native USD (oracle).
|
|
76
|
+
`speed price -c base --json`
|
|
77
|
+
|
|
78
|
+
### Swap (one-shot: quote → approve if needed → swap)
|
|
79
|
+
|
|
80
|
+
- **swap** — Single command to execute a swap. Approve is automatic when needed. Use `-y` to skip confirmation.
|
|
81
|
+
`speed swap -c base --buy <token-address> -a 0.002 -y --json`
|
|
82
|
+
Optional: `--sell <address>` (default Speed).
|
|
83
|
+
**Preview only (no tx):** `speed swap --dry-run -c base --buy <addr> -a 0.002 --json`
|
|
84
|
+
|
|
85
|
+
- **quote** — Preview swap (no transaction).
|
|
86
|
+
`speed quote -c base --buy <token-address> -a 0.002 --json`
|
|
87
|
+
Optional: `--sell <address>`.
|
|
88
|
+
|
|
89
|
+
**Do not** tell users to run approve then quote then swap; **swap alone** does it. Use **quote** or **swap --dry-run** only when the user wants a preview.
|
|
90
|
+
|
|
91
|
+
### Bridge
|
|
92
|
+
|
|
93
|
+
- **bridge** — Bridge Speed across chains (Squid). Use `-y` to skip confirmation.
|
|
94
|
+
`speed bridge --from-chain base --to-chain ethereum -a 100 -y --json`
|
|
95
|
+
Optional: `--to-token <address>` (default Speed on destination).
|
|
96
|
+
|
|
97
|
+
### Volume & DCA (automated buy/sell)
|
|
98
|
+
|
|
99
|
+
- **volume** — Human-like volume: interleaved buys and sells (ETH ↔ token) with random-walk amounts, jittered delays, optional partial sells. Default token is Speed; use `--token <address|speed>` for another token. Continues on revert; failures are reported in summary.
|
|
100
|
+
`speed volume -c base -a 0.001 --ops 20 --delay 2 --delay-jitter 0.5 --sell-frequency 0.2 --json`
|
|
101
|
+
Optional: `--token`, `--amount-min`, `--amount-max`, `--amount-drift`, `--sell-partial-chance`, `--dry-run`.
|
|
102
|
+
|
|
103
|
+
- **dca** — DCA: buy token with ETH on a fixed time interval. Default token is Speed; use `--token <address|speed>` for another token. Runs until `--count` buys or until stopped (Ctrl+C).
|
|
104
|
+
`speed dca -c base -a 0.001 --interval 5m --count 10 --json`
|
|
105
|
+
Optional: `--token`, `--interval-jitter <fraction>`, `--dry-run`. Omit `--count` to run until stopped.
|
|
106
|
+
|
|
107
|
+
### Gas (token → native)
|
|
108
|
+
|
|
109
|
+
- **gas** — Swap token for native (ETH/MATIC/BNB) to fund gas. Default token is Speed; use `--token <address|speed>` for another token. Use `-y` for non-interactive.
|
|
110
|
+
`speed gas -c base -a 10000 -y --json`
|
|
111
|
+
|
|
112
|
+
### Send & allowances
|
|
113
|
+
|
|
114
|
+
- **send** — Plain ERC-20 transfer of Speed.
|
|
115
|
+
`speed send -c base -t <recipient-address> -a 100 -y --json`
|
|
116
|
+
|
|
117
|
+
- **approve** — Set token allowance for a spender (e.g. for scripting). Amount in token units or `max`.
|
|
118
|
+
`speed approve -c base --token <token-address> --spender <spender-address> -a 1000 --json` or `-a max --json`
|
|
119
|
+
|
|
120
|
+
- **allowance** — Read current allowance.
|
|
121
|
+
`speed allowance -c base --token <token-address> --spender <spender-address> --json`
|
|
122
|
+
|
|
123
|
+
- **revoke** — Set allowance to 0.
|
|
124
|
+
`speed revoke -c base --token <token-address> --spender <spender-address> -y --json`
|
|
125
|
+
|
|
126
|
+
### History & status
|
|
127
|
+
|
|
128
|
+
- **history** — Recent transfers (Alchemy). Omit `-c` for all chains.
|
|
129
|
+
`speed history --json` or `speed history -c base -n 20 --json`
|
|
130
|
+
|
|
131
|
+
- **pending** — In-flight bridges + pending txs.
|
|
132
|
+
`speed pending --json` or `speed pending --no-txs --json` (bridges only)
|
|
133
|
+
|
|
134
|
+
- **status** — Tx confirmation; for bridge use Squid status.
|
|
135
|
+
On-chain: `speed status --tx <tx-hash> -c base --json`
|
|
136
|
+
Bridge: `speed status --tx <tx-hash> --request-id <id> --quote-id <id> --json`
|
|
137
|
+
|
|
138
|
+
### XP (progress for bots)
|
|
139
|
+
|
|
140
|
+
- **xp** — Show level, streak, and stats (swaps, bridges, volume ops, DCA buys, gas refuels). Bots call this to see how they're doing. Stored in `~/.speed/xp.json`.
|
|
141
|
+
`speed xp --json`
|
|
142
|
+
Optional: `--no-title` to omit the silly level title.
|
|
143
|
+
|
|
144
|
+
### Estimate & config
|
|
145
|
+
|
|
146
|
+
- **estimate** — Gas cost (ETH + USD) for swap or bridge. Amount in token units (same as swap).
|
|
147
|
+
Swap: `speed estimate -c base --buy <addr> -a 0.002 --json`
|
|
148
|
+
Bridge: `speed estimate -c base --to-chain ethereum --bridge --json`
|
|
149
|
+
|
|
150
|
+
- **config** — Read/write `~/.speed/config.json` (no secrets).
|
|
151
|
+
Set: `speed config set default-chain 8453 --json`
|
|
152
|
+
Get: `speed config get --json` or `speed config get default-chain --json`
|
|
153
|
+
Keys: `default-chain`, `default-slippage`, `output-format` (human|json).
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Recommended flows for bots
|
|
158
|
+
|
|
159
|
+
1. **User wants to swap**
|
|
160
|
+
Run: `speed swap -c <chain> --buy <token> -a <amount> -y --json`. If user wants a preview first, run `speed swap --dry-run ... --json` then same without `--dry-run` and without `-y` only if you need to ask for confirmation.
|
|
161
|
+
|
|
162
|
+
2. **User wants to bridge**
|
|
163
|
+
Run: `speed bridge --from-chain <id|name> --to-chain <id|name> -a <amount> -y --json`.
|
|
164
|
+
|
|
165
|
+
3. **User wants balance**
|
|
166
|
+
Run: `speed balance --json` (all chains) or `speed balance -c <chain> --json`.
|
|
167
|
+
|
|
168
|
+
4. **User wants price**
|
|
169
|
+
Run: `speed price -c <chain> --json`.
|
|
170
|
+
|
|
171
|
+
5. **User wants to fund gas (token → native)**
|
|
172
|
+
Run: `speed gas -c <chain> -a <token-amount> -y --json`. Default token is Speed; add `--token <address|speed>` for another token.
|
|
173
|
+
|
|
174
|
+
6. **User wants volume (automated buys + sells)**
|
|
175
|
+
Run: `speed volume -c <chain> -a <eth-per-buy> --ops <n> --delay <sec> --sell-frequency <0..1> --json`. Default token is Speed; add `--token <address|speed>` for another token. Failures do not stop the run; summary includes failed count.
|
|
176
|
+
|
|
177
|
+
7. **User wants DCA (interval buys only)**
|
|
178
|
+
Run: `speed dca -c <chain> -a <eth-per-buy> --interval <sec|5m|1h|1d> --count <n> --json`, or omit `--count` to run until stopped. Default token is Speed; add `--token <address|speed>` for another token.
|
|
179
|
+
|
|
180
|
+
8. **User wants to see XP / progress**
|
|
181
|
+
Run: `speed xp --json`. Returns totalXP, level, title, streak, lastActivity, progress to next level, stats (count + totalUSD per action type), recent history.
|
|
182
|
+
|
|
183
|
+
9. **User wants gas estimate before swap/bridge**
|
|
184
|
+
Run: `speed estimate -c <chain> --buy <addr> -a <token-amount> --json` (swap) or `speed estimate -c <chain> --to-chain <id|name> --bridge --json` (bridge). Use same `-a` as the swap for comparable cost.
|
|
185
|
+
|
|
186
|
+
10. **Check if CLI is configured**
|
|
187
|
+
Run: `speed doctor --json` or `speed whoami --json`. If error, tell user to run `speed setup` in the terminal (interactive).
|
|
188
|
+
|
|
189
|
+
11. **Parse JSON:** On success, one JSON object per command. On failure, `{ error, code }`; show `error` to the user. Common success shapes: whoami `{ address }`, balance `{ address?, chains }`, swap/bridge/gas/send/approve `{ txHash, explorerLink? }`, quote `{ buyAmountWei?, ... }`, price `{ speedPerEth?, ... }`, xp `{ totalXP, level, streak, ... }`, estimate `{ gasCostEth?, gasCostUsd?, ... }`.
|
|
@@ -31,9 +31,9 @@ triggers:
|
|
|
31
31
|
|
|
32
32
|
# Speed Token CLI — OpenClaw skill
|
|
33
33
|
|
|
34
|
-
Use the **speed** CLI via bash. **Always pass `--json`** so output is machine-readable. Use **`-y`** or **`--yes`** to skip confirmation when executing swap
|
|
34
|
+
Use the **speed** CLI via bash. **Always pass `--json`** so output is machine-readable. Use **`-y`** or **`--yes`** to skip confirmation when executing swap, bridge, or gas (required for non-interactive use). For swap you can also use **`--go`** as an alias.
|
|
35
35
|
|
|
36
|
-
**Invocation:** From the project directory run `node dist/cli.js <command> [options] --json
|
|
36
|
+
**Invocation:** From the project directory run `node dist/cli.js <command> [options] --json` (or put global options first: `node dist/cli.js --json -y <command> [options]`). If `speed` is on PATH: `speed <command> [options] --json`. From another directory, use the full path to `dist/cli.js` or ensure `speed` is on PATH.
|
|
37
37
|
|
|
38
38
|
**Chains:** Every `--chain`, `--from-chain`, `--to-chain` accepts **chain ID** (e.g. `8453`) or **name** (e.g. `base`, `ethereum`, `op`, `arb`, `polygon`, `bnb`). Supported: 1 (ethereum), 8453 (base), 10 (optimism), 42161 (arbitrum), 137 (polygon), 56 (bnb).
|
|
39
39
|
|
|
@@ -41,7 +41,51 @@ Use the **speed** CLI via bash. **Always pass `--json`** so output is machine-re
|
|
|
41
41
|
|
|
42
42
|
**Speed token (default):** Address `0xB01CF1bE9568f09449382a47Cd5bF58e2A9D5922`. Volume, DCA, and gas default to Speed; use `--token <address|speed>` to use another token.
|
|
43
43
|
|
|
44
|
-
**Errors in --json mode:** CLI prints `{"error":"...", "code":"..."}` to stdout and exits 1. Parse and surface the `error` string to the user.
|
|
44
|
+
**Errors in --json mode:** CLI prints `{"error":"...", "code":"..."}` to stdout and exits 1. Parse and surface the `error` string to the user. Common codes: `INSUFFICIENT_FUNDS`, `SAME_TOKEN`, `QUOTE_ERROR`, `MISSING_ARGS`, `INVALID_CHAIN`.
|
|
45
|
+
|
|
46
|
+
**Shell note:** Examples use `||` (Bash). On PowerShell use `;` instead of `||` to chain commands.
|
|
47
|
+
|
|
48
|
+
**Pitfalls:** (1) Volume/DCA need enough native balance (~0.001+ ETH per buy); otherwise INSUFFICIENT_FUNDS. (2) Very small DCA/swap amounts can revert (slippage/minimum); use ~0.001 ETH or more. (3) To sell "all" Speed, read balance first and pass that amount to swap (round down slightly).
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Trade like a pro (agent best practices)
|
|
53
|
+
|
|
54
|
+
- **Before any trade:** Run `speed balance -c <chain> --json` and optionally `speed price -c <chain> --json` so you know native balance, token balance, and prices. Ensure sell token balance ≥ amount (or for buy-with-ETH, native balance ≥ amount + gas).
|
|
55
|
+
- **Preview first when it matters:** Use `speed quote -c <chain> --sell <x> --buy <y> -a <amount> --json` or `speed swap --dry-run ... --json` before large or one-off swaps. Use `speed estimate -c <chain> --sell eth --buy speed -a <amount> --json` to see gas cost.
|
|
56
|
+
- **Buy Speed with ETH:** Default is already sell ETH / buy Speed. Use `speed swap -c <chain> -a <eth-amount> -y --json` (no need to pass --sell/--buy). Or explicitly `--sell eth` or `--sell native`.
|
|
57
|
+
- **Sell Speed for ETH (or “sell all”):** (1) `speed balance -c <chain> --json` → parse Speed token `balance` for that chain. (2) Round down slightly (e.g. 2 decimals). (3) `speed swap -c <chain> --sell speed --buy eth -a <amount> -y --json`.
|
|
58
|
+
- **Volume:** Use `-a 0.0008` or `0.001` per buy for reliability. Ensure native balance covers (ops × amount + gas). Use `--ops` to control how many buy/sell cycles. Failures are reported in summary; run continues.
|
|
59
|
+
- **DCA:** Use at least `-a 0.001` per buy. Use `--count` so the run ends (e.g. `--count 5`). Space intervals (e.g. `--interval 30s`) to avoid nonce/rate issues.
|
|
60
|
+
- **Bridging (Speed and native):** You can bridge **Speed** (default) or the **native token** of the source chain. Use `--from-token speed` (default) or `--from-token native` / `eth` / `bnb` / `pol` / `matic`. Native per chain: **Ethereum, Base, OP, Arbitrum** → ETH; **BNB chain** → BNB; **Polygon** → POL (matic). Speed is available on all chains. Examples:
|
|
61
|
+
Speed: `speed bridge --from-chain base --to-chain ethereum -a 100 -y --json`
|
|
62
|
+
Native ETH: `speed bridge --from-chain base --to-chain ethereum --from-token native -a 0.01 -y --json`
|
|
63
|
+
Native BNB: `speed bridge --from-chain bnb --to-chain base --from-token bnb -a 0.1 -y --json`
|
|
64
|
+
Native POL: `speed bridge --from-chain polygon --to-chain arb --from-token pol -a 10 -y --json`
|
|
65
|
+
- **Bridges:** Squid can rate-limit (429). Space bridge calls (e.g. one chain at a time, wait between). On 429, message suggests waiting and retrying.
|
|
66
|
+
- **Gas (token → native):** If you see OVERFLOW, use a smaller amount (e.g. `-a 100` or `-a 300`). Large token amounts can overflow the aggregator path.
|
|
67
|
+
- **Never sell and buy the same token:** CLI validates and returns SAME_TOKEN; use `--sell eth` when you want to buy Speed with ETH.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## XP and leveling (level up)
|
|
72
|
+
|
|
73
|
+
Actions that grant **XP** (stored in `~/.speed/xp.json`):
|
|
74
|
+
|
|
75
|
+
| Action | Base XP | When it’s recorded |
|
|
76
|
+
|------------|--------|---------------------|
|
|
77
|
+
| **swap** | 10 | Each swap (buy or sell) |
|
|
78
|
+
| **bridge** | 25 | Each bridge tx |
|
|
79
|
+
| **volumeOp** | 3 | Each volume buy or sell op |
|
|
80
|
+
| **dcaBuy** | 8 | Each DCA buy |
|
|
81
|
+
| **gasRefuel** | 3 | Each gas (token→native) tx |
|
|
82
|
+
|
|
83
|
+
XP per action is scaled by **USD value** of the trade (log scale; higher USD = more XP) and by **daily streak** (up to 1.5× for consecutive days of activity). Small USD actions still grant at least a small amount of XP.
|
|
84
|
+
|
|
85
|
+
- **Check progress:** `speed xp --json` → `totalXP`, `level`, `title`, `streak`, `progress` (e.g. `current`, `needed`, `fraction`), `stats` (count and totalUSD per action), `recentHistory`.
|
|
86
|
+
- **Level thresholds:** Level 1 → 2 at **500** total XP; level 2 → 3 at **500 + 1319** total XP; each next level needs more (formula: 500 × level^1.4).
|
|
87
|
+
- **To level up:** Prefer a mix of (1) **volume** (many `volumeOp`s per run: `speed volume -c base -a 0.001 --ops 20 -y --json`), (2) **swaps** (each gives XP; larger USD = more XP), (3) **bridges** (high base XP), (4) **DCA** (`dcaBuy` per buy). Keep the streak by doing at least one action per day.
|
|
88
|
+
- **Efficient XP grind:** Run volume with moderate `--ops` (e.g. 15–25) and `-a 0.0008`–`0.001`; then do a few swaps (buy and sell). Use `speed xp --json` to see how close you are to the next level.
|
|
45
89
|
|
|
46
90
|
---
|
|
47
91
|
|
|
@@ -67,7 +111,7 @@ Use the **speed** CLI via bash. **Always pass `--json`** so output is machine-re
|
|
|
67
111
|
|
|
68
112
|
### Balance & price
|
|
69
113
|
|
|
70
|
-
- **balance** — Speed + native + optional extra tokens. Omit `-c` for all chains.
|
|
114
|
+
- **balance** — Speed + native + optional extra tokens. Omit `-c` for all chains; use `-c base` or `-c 8453` for one chain. JSON: `chains[].chainId`, `chains[].nativeBalance` (string), `chains[].tokens[].symbol`, `tokens[].balance` (string, in token units). Use the Speed token `balance` for a chain when you need to sell that amount.
|
|
71
115
|
`speed balance --json`
|
|
72
116
|
`speed balance -c base --json`
|
|
73
117
|
`speed balance -c base -t <token-address> --json` (repeat `-t` for multiple)
|
|
@@ -77,31 +121,35 @@ Use the **speed** CLI via bash. **Always pass `--json`** so output is machine-re
|
|
|
77
121
|
|
|
78
122
|
### Swap (one-shot: quote → approve if needed → swap)
|
|
79
123
|
|
|
80
|
-
- **swap** — Single command to execute a swap. Approve is automatic when needed. Use `-y` to skip confirmation.
|
|
124
|
+
- **swap** — Single command to execute a swap. Approve is automatic when needed. Use `-y` or `--go` to skip confirmation.
|
|
81
125
|
`speed swap -c base --buy <token-address> -a 0.002 -y --json`
|
|
82
|
-
Optional: `--sell <address
|
|
126
|
+
To **buy Speed with native ETH**: default is sell ETH / buy Speed; or set `--sell eth` or `--sell native`. Optional: `--sell <address|speed|eth|native>`, `--buy <address|speed>`.
|
|
83
127
|
**Preview only (no tx):** `speed swap --dry-run -c base --buy <addr> -a 0.002 --json`
|
|
84
128
|
|
|
85
|
-
- **quote** — Preview swap (no transaction).
|
|
86
|
-
`speed quote -c base --buy
|
|
87
|
-
Optional: `--sell <address>`.
|
|
129
|
+
- **quote** — Preview swap (no transaction). Default sell is native ETH. Use `--sell eth` or `--sell native` like swap.
|
|
130
|
+
`speed quote -c base --sell eth --buy speed -a 0.002 --json`
|
|
131
|
+
Optional: `--sell <address|speed|eth|native>`, `--buy <address|speed>`.
|
|
132
|
+
|
|
133
|
+
**Sell Speed for ETH:** `speed swap -c <chain> --sell speed --buy eth -a <amount> -y --json`. Get `<amount>` from `speed balance -c <chain> --json` (parse Speed token `balance` for that chain). Round down slightly (e.g. 2 decimals) to avoid dust/slippage.
|
|
88
134
|
|
|
89
135
|
**Do not** tell users to run approve then quote then swap; **swap alone** does it. Use **quote** or **swap --dry-run** only when the user wants a preview.
|
|
90
136
|
|
|
91
137
|
### Bridge
|
|
92
138
|
|
|
93
|
-
- **bridge** — Bridge Speed across chains (Squid). Use `-y` to skip confirmation.
|
|
139
|
+
- **bridge** — Bridge **Speed** or **native token** across chains (Squid). Use `-y` to skip confirmation.
|
|
140
|
+
**From-token:** `--from-token speed` (default) or `--from-token native` / `eth` / `bnb` / `pol` / `matic`. Native per chain: Ethereum, Base, OP, Arbitrum → ETH; BNB chain → BNB; Polygon → POL (matic). Speed on all chains.
|
|
94
141
|
`speed bridge --from-chain base --to-chain ethereum -a 100 -y --json`
|
|
142
|
+
`speed bridge --from-chain base --to-chain ethereum --from-token native -a 0.01 -y --json` (bridge ETH)
|
|
95
143
|
Optional: `--to-token <address>` (default Speed on destination).
|
|
96
144
|
|
|
97
145
|
### Volume & DCA (automated buy/sell)
|
|
98
146
|
|
|
99
|
-
- **volume** — Human-like volume: interleaved buys and sells (ETH ↔ token) with random-walk amounts, jittered delays, optional partial sells. Default token is Speed; use `--token <address|speed>` for another token.
|
|
100
|
-
`speed volume -c base -a 0.001 --ops 20 --delay 2 --delay-jitter 0.5 --sell-frequency 0.2 --json`
|
|
147
|
+
- **volume** — Human-like volume: interleaved buys and sells (ETH ↔ token) with random-walk amounts, jittered delays, optional partial sells. Default token is Speed; use `--token <address|speed>` for another token. **Requires enough native balance:** need at least ~0.001 ETH per buy + gas; otherwise first op can exit with `INSUFFICIENT_FUNDS`. Some ops may revert (slippage); run continues; summary has `buys`, `sells`, `failed`, `totalOps`. Use `-a 0.0008` or higher for reliability.
|
|
148
|
+
`speed volume -c base -a 0.001 --ops 20 --delay 2 --delay-jitter 0.5 --sell-frequency 0.2 -y --json`
|
|
101
149
|
Optional: `--token`, `--amount-min`, `--amount-max`, `--amount-drift`, `--sell-partial-chance`, `--dry-run`.
|
|
102
150
|
|
|
103
|
-
- **dca** — DCA: buy token with ETH on a fixed time interval. Default token is Speed; use `--token <address|speed>` for another token. Runs until `--count` buys or until stopped (Ctrl+C).
|
|
104
|
-
`speed dca -c base -a 0.001 --interval 5m --count 10 --json`
|
|
151
|
+
- **dca** — DCA: buy token with ETH on a fixed time interval. Default token is Speed; use `--token <address|speed>` for another token. **Minimum amount:** very small amounts (e.g. 0.0004–0.0005 ETH) often revert with "slippage, liquidity, or minimum amount"; use at least ~0.001 ETH per buy. `--interval` accepts seconds or `20s`, `5m`, `1h`, `1d`. Runs until `--count` buys or until stopped (Ctrl+C).
|
|
152
|
+
`speed dca -c base -a 0.001 --interval 5m --count 10 -y --json`
|
|
105
153
|
Optional: `--token`, `--interval-jitter <fraction>`, `--dry-run`. Omit `--count` to run until stopped.
|
|
106
154
|
|
|
107
155
|
### Gas (token → native)
|
|
@@ -143,8 +191,8 @@ Use the **speed** CLI via bash. **Always pass `--json`** so output is machine-re
|
|
|
143
191
|
|
|
144
192
|
### Estimate & config
|
|
145
193
|
|
|
146
|
-
- **estimate** — Gas cost (ETH + USD) for swap or bridge. Amount in token units (same as swap).
|
|
147
|
-
Swap: `speed estimate -c base --buy
|
|
194
|
+
- **estimate** — Gas cost (ETH + USD) for swap or bridge. Amount in token units (same as swap). Same `--sell`/`--buy` tokens as swap (e.g. `--sell eth --buy speed`).
|
|
195
|
+
Swap: `speed estimate -c base --sell eth --buy speed -a 0.002 --json`
|
|
148
196
|
Bridge: `speed estimate -c base --to-chain ethereum --bridge --json`
|
|
149
197
|
|
|
150
198
|
- **config** — Read/write `~/.speed/config.json` (no secrets).
|
|
@@ -160,7 +208,7 @@ Use the **speed** CLI via bash. **Always pass `--json`** so output is machine-re
|
|
|
160
208
|
Run: `speed swap -c <chain> --buy <token> -a <amount> -y --json`. If user wants a preview first, run `speed swap --dry-run ... --json` then same without `--dry-run` and without `-y` only if you need to ask for confirmation.
|
|
161
209
|
|
|
162
210
|
2. **User wants to bridge**
|
|
163
|
-
Run: `speed bridge --from-chain <id|name> --to-chain <id|name> -a <amount> -y --json`.
|
|
211
|
+
Run: `speed bridge --from-chain <id|name> --to-chain <id|name> -a <amount> -y --json`. For native (ETH/BNB/POL) use `--from-token native` (or `eth`/`bnb`/`pol`/`matic`).
|
|
164
212
|
|
|
165
213
|
3. **User wants balance**
|
|
166
214
|
Run: `speed balance --json` (all chains) or `speed balance -c <chain> --json`.
|
|
@@ -172,10 +220,10 @@ Use the **speed** CLI via bash. **Always pass `--json`** so output is machine-re
|
|
|
172
220
|
Run: `speed gas -c <chain> -a <token-amount> -y --json`. Default token is Speed; add `--token <address|speed>` for another token.
|
|
173
221
|
|
|
174
222
|
6. **User wants volume (automated buys + sells)**
|
|
175
|
-
Run: `speed volume -c <chain> -a <eth-per-buy> --ops <n>
|
|
223
|
+
Run: `speed volume -c <chain> -a <eth-per-buy> --ops <n> -y --json`. Use at least ~0.0008 ETH per buy. Failures do not stop the run; summary has `buys`, `sells`, `failed`, `totalOps`. Optional: `--delay`, `--sell-frequency`, `--token`.
|
|
176
224
|
|
|
177
225
|
7. **User wants DCA (interval buys only)**
|
|
178
|
-
Run: `speed dca -c <chain> -a <eth-per-buy> --interval <
|
|
226
|
+
Run: `speed dca -c <chain> -a <eth-per-buy> --interval <20s|5m|1h|1d> --count <n> -y --json`. Use at least ~0.001 ETH per buy to avoid slippage reverts. Omit `--count` to run until stopped. Default token is Speed; add `--token <address|speed>` for another token.
|
|
179
227
|
|
|
180
228
|
8. **User wants to see XP / progress**
|
|
181
229
|
Run: `speed xp --json`. Returns totalXP, level, title, streak, lastActivity, progress to next level, stats (count + totalUSD per action type), recent history.
|
|
@@ -186,4 +234,7 @@ Use the **speed** CLI via bash. **Always pass `--json`** so output is machine-re
|
|
|
186
234
|
10. **Check if CLI is configured**
|
|
187
235
|
Run: `speed doctor --json` or `speed whoami --json`. If error, tell user to run `speed setup` in the terminal (interactive).
|
|
188
236
|
|
|
189
|
-
11. **
|
|
237
|
+
11. **User wants to sell Speed for ETH (or “sell all”)**
|
|
238
|
+
Run: `speed balance -c <chain> --json`, parse the chain’s Speed token `balance` (string, token units). Then run: `speed swap -c <chain> --sell speed --buy eth -a <amount> -y --json` with that amount (round down slightly to avoid dust).
|
|
239
|
+
|
|
240
|
+
12. **Parse JSON:** On success, one JSON object per command. On failure, `{ error, code }`; show `error` to the user. Common success shapes: whoami `{ address }`, balance `{ address?, chains: [{ chainId, nativeBalance, tokens: [{ symbol, balance }] }] }`, swap/bridge/gas/send/approve `{ txHash, explorerLink? }`, quote `{ sellAmount, buyAmount, ... }`, price `{ speedPerNative, nativeUsd, ... }`, xp `{ totalXP, level, streak, stats: { swaps, volumeOps, dcaBuys, ... }, recentHistory }`, estimate `{ gasEth, gasUsd, ... }`, volume `{ summary: { buys, sells, failed, totalOps }, results, failures? }`.
|
package/package.json
CHANGED
|
@@ -1,47 +1,50 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@lightspeed-cli/speed-cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Speed Token CLI: swap, bridge, balance, price, volume, DCA, gas, XP. Uses 0x and Squid; config in ~/.speed.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"speed": "dist/cli.js"
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"dist",
|
|
11
|
-
"openclaw"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
},
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@lightspeed-cli/speed-cli",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Speed Token CLI: swap, bridge, balance, price, volume, DCA, gas, XP. Uses 0x and Squid; config in ~/.speed.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"speed": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"openclaw",
|
|
12
|
+
".env.example"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"start": "node dist/cli.js",
|
|
17
|
+
"dev": "tsc --watch",
|
|
18
|
+
"test": "npm run build && node --test tests/constants.test.js tests/parse-amount.test.js tests/xp.test.js tests/oracle.test.js tests/cli-options.test.js",
|
|
19
|
+
"test:json": "npm run build && node tests/run-json.js",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"speed",
|
|
27
|
+
"token",
|
|
28
|
+
"cli",
|
|
29
|
+
"swap",
|
|
30
|
+
"bridge",
|
|
31
|
+
"0x",
|
|
32
|
+
"squid",
|
|
33
|
+
"ethereum",
|
|
34
|
+
"base"
|
|
35
|
+
],
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@0xsquid/sdk": "^2.2.0",
|
|
39
|
+
"@0xsquid/squid-types": "^0.1.215",
|
|
40
|
+
"chalk": "^5.3.0",
|
|
41
|
+
"commander": "^12.0.0",
|
|
42
|
+
"dotenv": "^16.4.5",
|
|
43
|
+
"ethers": "^6.13.0",
|
|
44
|
+
"ora": "^8.0.1"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^20.11.0",
|
|
48
|
+
"typescript": "^5.3.0"
|
|
49
|
+
}
|
|
50
|
+
}
|