moltlaunch 0.11.0 → 1.0.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 +429 -169
- package/dist/index.js +552 -163
- package/dist/index.js.map +1 -1
- package/package.json +8 -3
package/dist/index.js
CHANGED
|
@@ -14,10 +14,13 @@ import { readFile, writeFile, mkdir, chmod, access } from "fs/promises";
|
|
|
14
14
|
import { join } from "path";
|
|
15
15
|
import { homedir } from "os";
|
|
16
16
|
|
|
17
|
-
// src/
|
|
17
|
+
// packages/shared/src/constants.ts
|
|
18
18
|
var REVENUE_MANAGER_ADDRESS = "0x3Bc08524d9DaaDEC9d1Af87818d809611F0fD669";
|
|
19
|
+
var MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11";
|
|
19
20
|
var FLAUNCH_API_BASE = "https://web2-api.flaunch.gg";
|
|
20
21
|
var FLAUNCH_DATA_API_BASE = "https://api.flayerlabs.xyz";
|
|
22
|
+
var FLAUNCH_DATA_API = `${FLAUNCH_DATA_API_BASE}/v1/base`;
|
|
23
|
+
var WORKER_API_URL = "https://moltlaunch-network.nikshepsvn-d85.workers.dev";
|
|
21
24
|
var CHAIN = {
|
|
22
25
|
mainnet: {
|
|
23
26
|
id: 8453,
|
|
@@ -36,16 +39,16 @@ var CHAIN = {
|
|
|
36
39
|
flaunchUrl: "https://flaunch.gg/base-sepolia"
|
|
37
40
|
}
|
|
38
41
|
};
|
|
39
|
-
var WALLET_DIR = ".moltlaunch";
|
|
40
|
-
var WALLET_FILE = "wallet.json";
|
|
41
|
-
var LAUNCHES_FILE = "launches.json";
|
|
42
42
|
var DEFAULT_SLIPPAGE_PERCENT = 5;
|
|
43
|
-
var HEARTBEAT_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
44
|
-
var BEAT_FEE = "0.0001";
|
|
45
43
|
var MAX_IMAGE_SIZE_BYTES = 5 * 1024 * 1024;
|
|
46
44
|
var POLL_INTERVAL_MS = 2e3;
|
|
47
45
|
var POLL_TIMEOUT_MS = 12e4;
|
|
48
46
|
|
|
47
|
+
// src/lib/config.ts
|
|
48
|
+
var WALLET_DIR = ".moltlaunch";
|
|
49
|
+
var WALLET_FILE = "wallet.json";
|
|
50
|
+
var LAUNCHES_FILE = "launches.json";
|
|
51
|
+
|
|
49
52
|
// src/lib/wallet.ts
|
|
50
53
|
function getWalletDir() {
|
|
51
54
|
return join(homedir(), WALLET_DIR);
|
|
@@ -263,6 +266,39 @@ async function fetchTokensByOwner(ownerAddress, network2) {
|
|
|
263
266
|
}
|
|
264
267
|
return await response.json();
|
|
265
268
|
}
|
|
269
|
+
async function fetchTokenDetails(tokenAddress, network2) {
|
|
270
|
+
const chain = network2 === "testnet" ? CHAIN.testnet : CHAIN.mainnet;
|
|
271
|
+
const url = `${FLAUNCH_DATA_API_BASE}/v1/${chain.network}/tokens/${tokenAddress}/details`;
|
|
272
|
+
const response = await fetchWithRetry(url, { method: "GET" });
|
|
273
|
+
if (!response.ok) {
|
|
274
|
+
const text = await response.text();
|
|
275
|
+
throw new Error(`Flaunch data API error: ${response.status} \u2014 ${text}`);
|
|
276
|
+
}
|
|
277
|
+
const data = await response.json();
|
|
278
|
+
if (!data || typeof data !== "object" || !data.tokenAddress || !data.name || !data.symbol) {
|
|
279
|
+
throw new Error("Invalid token details response: missing required fields");
|
|
280
|
+
}
|
|
281
|
+
const price2 = data.price;
|
|
282
|
+
const volume = data.volume;
|
|
283
|
+
if (!price2?.marketCapETH || !volume?.volume24h) {
|
|
284
|
+
throw new Error("Invalid token details response: missing price/volume data");
|
|
285
|
+
}
|
|
286
|
+
return data;
|
|
287
|
+
}
|
|
288
|
+
async function fetchTokenHolderCount(tokenAddress, network2) {
|
|
289
|
+
const chain = network2 === "testnet" ? CHAIN.testnet : CHAIN.mainnet;
|
|
290
|
+
const url = `${FLAUNCH_DATA_API_BASE}/v1/${chain.network}/tokens/${tokenAddress}/holders?limit=1&offset=0`;
|
|
291
|
+
const response = await fetchWithRetry(url, { method: "GET" });
|
|
292
|
+
if (!response.ok) {
|
|
293
|
+
const text = await response.text();
|
|
294
|
+
throw new Error(`Flaunch data API error: ${response.status} \u2014 ${text}`);
|
|
295
|
+
}
|
|
296
|
+
const data = await response.json();
|
|
297
|
+
if (typeof data.pagination?.total === "number") {
|
|
298
|
+
return data.pagination.total;
|
|
299
|
+
}
|
|
300
|
+
throw new Error("Holder count unavailable: API response missing pagination.total");
|
|
301
|
+
}
|
|
266
302
|
async function pollLaunchStatus(jobId, onPoll) {
|
|
267
303
|
const startTime = Date.now();
|
|
268
304
|
let consecutiveErrors = 0;
|
|
@@ -645,13 +681,23 @@ async function launch(opts) {
|
|
|
645
681
|
if (isNew) {
|
|
646
682
|
console.log(`
|
|
647
683
|
Wallet created: ${wallet2.address}`);
|
|
648
|
-
console.log(`
|
|
649
|
-
|
|
684
|
+
console.log(`Key saved to ~/.moltlaunch/wallet.json (never share this file)
|
|
685
|
+
`);
|
|
650
686
|
} else {
|
|
651
687
|
console.log(`
|
|
652
688
|
Using wallet: ${wallet2.address}`);
|
|
653
689
|
}
|
|
654
690
|
}
|
|
691
|
+
const existing = await fetchTokensByOwner(wallet2.address, network2);
|
|
692
|
+
if (existing.data.length > 0) {
|
|
693
|
+
const t = existing.data[0];
|
|
694
|
+
printError(
|
|
695
|
+
`This wallet already has a token: ${t.name} (${t.symbol}). One identity per wallet.`,
|
|
696
|
+
json,
|
|
697
|
+
EXIT_CODES.GENERAL
|
|
698
|
+
);
|
|
699
|
+
process.exit(EXIT_CODES.GENERAL);
|
|
700
|
+
}
|
|
655
701
|
if (!json) process.stdout.write("Uploading image...");
|
|
656
702
|
const imageIpfs = await uploadImage(imageSource);
|
|
657
703
|
if (!json) console.log(` ${imageIpfs.slice(0, 16)}...`);
|
|
@@ -721,8 +767,8 @@ Using wallet: ${wallet2.address}`);
|
|
|
721
767
|
outputData.announcements = announcements;
|
|
722
768
|
}
|
|
723
769
|
if (isNew) {
|
|
724
|
-
outputData.
|
|
725
|
-
outputData.walletNote = "
|
|
770
|
+
outputData.walletPath = "~/.moltlaunch/wallet.json";
|
|
771
|
+
outputData.walletNote = "Key saved locally \u2014 never share this file";
|
|
726
772
|
}
|
|
727
773
|
printSuccess("Token launched successfully!", outputData, json);
|
|
728
774
|
} catch (error) {
|
|
@@ -738,7 +784,7 @@ Using wallet: ${wallet2.address}`);
|
|
|
738
784
|
|
|
739
785
|
// src/commands/wallet.ts
|
|
740
786
|
async function wallet(opts) {
|
|
741
|
-
const {
|
|
787
|
+
const { json } = opts;
|
|
742
788
|
try {
|
|
743
789
|
const data = await loadWallet();
|
|
744
790
|
if (!data) {
|
|
@@ -756,9 +802,6 @@ async function wallet(opts) {
|
|
|
756
802
|
network: json ? "Base" : void 0,
|
|
757
803
|
createdAt: data.createdAt
|
|
758
804
|
};
|
|
759
|
-
if (showKey) {
|
|
760
|
-
output.privateKey = data.privateKey;
|
|
761
|
-
}
|
|
762
805
|
printSuccess("Wallet info", output, json);
|
|
763
806
|
} catch (error) {
|
|
764
807
|
if (error instanceof MltlError) {
|
|
@@ -856,34 +899,7 @@ Your tokens (${sorted.length}) \u2014 ${chain.name}
|
|
|
856
899
|
}
|
|
857
900
|
|
|
858
901
|
// src/commands/claim.ts
|
|
859
|
-
import { ethers as ethers4 } from "ethers";
|
|
860
|
-
|
|
861
|
-
// src/lib/heartbeat.ts
|
|
862
902
|
import { ethers as ethers3 } from "ethers";
|
|
863
|
-
var ACTION_CODES = { swap: 1, claim: 2, launch: 3 };
|
|
864
|
-
function encodeBeat(action, txRef, reason) {
|
|
865
|
-
const code = ACTION_CODES[action] ?? 0;
|
|
866
|
-
const ref = txRef ?? ethers3.ZeroHash;
|
|
867
|
-
const parts = ethers3.solidityPacked(["uint8", "bytes32"], [code, ref]);
|
|
868
|
-
if (reason) {
|
|
869
|
-
return ethers3.concat([parts, ethers3.toUtf8Bytes(reason.slice(0, 140))]);
|
|
870
|
-
}
|
|
871
|
-
return parts;
|
|
872
|
-
}
|
|
873
|
-
async function emitBeat(privateKey, network2, action, txRef, reason) {
|
|
874
|
-
try {
|
|
875
|
-
if (HEARTBEAT_ADDRESS === "0x0000000000000000000000000000000000000000") return;
|
|
876
|
-
const signer = await getSigner(privateKey, network2);
|
|
877
|
-
await signer.sendTransaction({
|
|
878
|
-
to: HEARTBEAT_ADDRESS,
|
|
879
|
-
value: ethers3.parseEther(BEAT_FEE),
|
|
880
|
-
data: encodeBeat(action, txRef, reason)
|
|
881
|
-
});
|
|
882
|
-
} catch {
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
// src/commands/claim.ts
|
|
887
903
|
var REVENUE_MANAGER_ABI = [
|
|
888
904
|
"function balances(address) external view returns (uint256)",
|
|
889
905
|
"function claim() external returns (uint256)"
|
|
@@ -901,9 +917,9 @@ async function claim(opts) {
|
|
|
901
917
|
throw new NoGasError(walletData.address);
|
|
902
918
|
}
|
|
903
919
|
const signer = await getSigner(walletData.privateKey, network2);
|
|
904
|
-
const rm = new
|
|
920
|
+
const rm = new ethers3.Contract(REVENUE_MANAGER_ADDRESS, REVENUE_MANAGER_ABI, signer);
|
|
905
921
|
const claimable = await rm.balances(walletData.address);
|
|
906
|
-
const claimableEth =
|
|
922
|
+
const claimableEth = ethers3.formatEther(claimable);
|
|
907
923
|
if (claimable === 0n) {
|
|
908
924
|
printSuccess("No fees to claim", {
|
|
909
925
|
claimable: "0 ETH",
|
|
@@ -923,9 +939,6 @@ Claimable: ${claimableEth} ETH`);
|
|
|
923
939
|
throw new MltlError("Transaction was dropped or replaced", EXIT_CODES.GENERAL);
|
|
924
940
|
}
|
|
925
941
|
if (!json) console.log(" confirmed");
|
|
926
|
-
if (!opts.noHeartbeat) {
|
|
927
|
-
emitBeat(walletData.privateKey, network2, "claim", receipt.hash, opts.reason);
|
|
928
|
-
}
|
|
929
942
|
printSuccess("Fees claimed successfully!", {
|
|
930
943
|
transactionHash: receipt.hash,
|
|
931
944
|
claimed: `${claimableEth} ETH (minus protocol fee)`,
|
|
@@ -944,7 +957,7 @@ Claimable: ${claimableEth} ETH`);
|
|
|
944
957
|
}
|
|
945
958
|
|
|
946
959
|
// src/commands/fees.ts
|
|
947
|
-
import { ethers as
|
|
960
|
+
import { ethers as ethers4 } from "ethers";
|
|
948
961
|
var REVENUE_MANAGER_ABI2 = [
|
|
949
962
|
"function balances(address) external view returns (uint256)",
|
|
950
963
|
"function protocolFee() external view returns (uint256)"
|
|
@@ -958,17 +971,17 @@ async function fees(opts) {
|
|
|
958
971
|
throw new NoWalletError();
|
|
959
972
|
}
|
|
960
973
|
const chain = testnet ? CHAIN.testnet : CHAIN.mainnet;
|
|
961
|
-
const provider = new
|
|
962
|
-
const rm = new
|
|
974
|
+
const provider = new ethers4.JsonRpcProvider(chain.rpcUrl);
|
|
975
|
+
const rm = new ethers4.Contract(REVENUE_MANAGER_ADDRESS, REVENUE_MANAGER_ABI2, provider);
|
|
963
976
|
const claimable = await rm.balances(walletData.address);
|
|
964
|
-
const claimableEth =
|
|
977
|
+
const claimableEth = ethers4.formatEther(claimable);
|
|
965
978
|
let protocolFeeBps = 1000n;
|
|
966
979
|
try {
|
|
967
980
|
protocolFeeBps = await rm.protocolFee();
|
|
968
981
|
} catch {
|
|
969
982
|
}
|
|
970
983
|
const afterProtocol = claimable - claimable * protocolFeeBps / 10000n;
|
|
971
|
-
const afterProtocolEth =
|
|
984
|
+
const afterProtocolEth = ethers4.formatEther(afterProtocol);
|
|
972
985
|
const walletBalance = await getWalletBalance(walletData.address, network2);
|
|
973
986
|
const hasGas = parseFloat(walletBalance) > 0;
|
|
974
987
|
printSuccess("Fee balance", {
|
|
@@ -996,12 +1009,39 @@ async function fees(opts) {
|
|
|
996
1009
|
import { parseEther } from "viem";
|
|
997
1010
|
|
|
998
1011
|
// src/lib/viem-client.ts
|
|
999
|
-
import { createPublicClient, createWalletClient, http } from "viem";
|
|
1012
|
+
import { createPublicClient, createWalletClient, http, encodeFunctionData } from "viem";
|
|
1000
1013
|
import { privateKeyToAccount } from "viem/accounts";
|
|
1001
1014
|
import { base, baseSepolia } from "viem/chains";
|
|
1002
1015
|
import { createDrift } from "@delvtech/drift";
|
|
1003
1016
|
import { viemAdapter } from "@delvtech/drift-viem";
|
|
1004
1017
|
import { ReadWriteFlaunchSDK } from "@flaunch/sdk";
|
|
1018
|
+
|
|
1019
|
+
// src/lib/memo.ts
|
|
1020
|
+
var MAGIC_PREFIX = "4d4c544c";
|
|
1021
|
+
var MAX_MEMO_BYTES = 65532;
|
|
1022
|
+
function encodeMemo(memo) {
|
|
1023
|
+
const json = JSON.stringify(memo);
|
|
1024
|
+
const bytes = new TextEncoder().encode(json);
|
|
1025
|
+
if (bytes.length === 0) return null;
|
|
1026
|
+
if (bytes.length > MAX_MEMO_BYTES) {
|
|
1027
|
+
console.warn(`Memo too large (${bytes.length} bytes, max ${MAX_MEMO_BYTES}). Skipping.`);
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
1030
|
+
const hexPayload = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1031
|
+
return `0x${MAGIC_PREFIX}${hexPayload}`;
|
|
1032
|
+
}
|
|
1033
|
+
function appendMemoToCalldata(calldata, memoHex) {
|
|
1034
|
+
return `${calldata}${memoHex.slice(2)}`;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// src/lib/viem-client.ts
|
|
1038
|
+
var _pendingMemoHex = null;
|
|
1039
|
+
function setMemo(memoHex) {
|
|
1040
|
+
_pendingMemoHex = memoHex;
|
|
1041
|
+
}
|
|
1042
|
+
function clearMemo() {
|
|
1043
|
+
_pendingMemoHex = null;
|
|
1044
|
+
}
|
|
1005
1045
|
var VIEM_CHAINS = {
|
|
1006
1046
|
mainnet: base,
|
|
1007
1047
|
testnet: baseSepolia
|
|
@@ -1019,17 +1059,53 @@ function createFlaunchSdk(privateKey, network2) {
|
|
|
1019
1059
|
account,
|
|
1020
1060
|
transport: http(chainConfig.rpcUrl)
|
|
1021
1061
|
});
|
|
1062
|
+
const patchedWalletClient = new Proxy(walletClient, {
|
|
1063
|
+
get(target, prop, receiver) {
|
|
1064
|
+
const val = Reflect.get(target, prop, receiver);
|
|
1065
|
+
if (typeof val !== "function") return val;
|
|
1066
|
+
if (prop === "writeContract" || prop === "sendTransaction" || prop === "deployContract") {
|
|
1067
|
+
return (args) => {
|
|
1068
|
+
const patched = { ...args, account };
|
|
1069
|
+
if (_pendingMemoHex && (prop === "writeContract" || prop === "sendTransaction")) {
|
|
1070
|
+
const memo = _pendingMemoHex;
|
|
1071
|
+
_pendingMemoHex = null;
|
|
1072
|
+
if (prop === "sendTransaction" && typeof patched.data === "string") {
|
|
1073
|
+
patched.data = appendMemoToCalldata(patched.data, memo);
|
|
1074
|
+
}
|
|
1075
|
+
if (prop === "writeContract" && patched.abi && patched.functionName) {
|
|
1076
|
+
const encoded = encodeFunctionData({
|
|
1077
|
+
abi: patched.abi,
|
|
1078
|
+
functionName: patched.functionName,
|
|
1079
|
+
args: patched.args ?? []
|
|
1080
|
+
});
|
|
1081
|
+
const data = appendMemoToCalldata(encoded, memo);
|
|
1082
|
+
const sendTx = Reflect.get(target, "sendTransaction", receiver);
|
|
1083
|
+
return sendTx.call(target, {
|
|
1084
|
+
account,
|
|
1085
|
+
to: patched.address,
|
|
1086
|
+
data,
|
|
1087
|
+
value: patched.value ?? 0n,
|
|
1088
|
+
...patched.gas ? { gas: patched.gas } : {}
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
return val.call(target, patched);
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
return val.bind(target);
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1022
1098
|
const drift = createDrift({
|
|
1023
1099
|
adapter: viemAdapter({
|
|
1024
1100
|
publicClient,
|
|
1025
|
-
walletClient
|
|
1101
|
+
walletClient: patchedWalletClient
|
|
1026
1102
|
})
|
|
1027
1103
|
});
|
|
1028
1104
|
const flaunch = new ReadWriteFlaunchSDK(chain.id, drift);
|
|
1029
1105
|
return {
|
|
1030
1106
|
flaunch,
|
|
1031
1107
|
publicClient,
|
|
1032
|
-
walletClient,
|
|
1108
|
+
walletClient: patchedWalletClient,
|
|
1033
1109
|
account
|
|
1034
1110
|
};
|
|
1035
1111
|
}
|
|
@@ -1048,6 +1124,18 @@ async function swap(opts) {
|
|
|
1048
1124
|
if (!json) console.log(`
|
|
1049
1125
|
Swapping on ${chainConfig.name}...`);
|
|
1050
1126
|
const { flaunch, publicClient, walletClient, account } = createFlaunchSdk(walletData.privateKey, network2);
|
|
1127
|
+
if (opts.memo) {
|
|
1128
|
+
const memoHex = encodeMemo({
|
|
1129
|
+
agent: walletData.address,
|
|
1130
|
+
action: side,
|
|
1131
|
+
token,
|
|
1132
|
+
memo: opts.memo,
|
|
1133
|
+
ts: Date.now()
|
|
1134
|
+
});
|
|
1135
|
+
setMemo(memoHex);
|
|
1136
|
+
} else {
|
|
1137
|
+
clearMemo();
|
|
1138
|
+
}
|
|
1051
1139
|
const coinAddress = token;
|
|
1052
1140
|
const amountIn = parseEther(amount);
|
|
1053
1141
|
let txHash;
|
|
@@ -1081,6 +1169,7 @@ Swapping on ${chainConfig.name}...`);
|
|
|
1081
1169
|
});
|
|
1082
1170
|
}
|
|
1083
1171
|
}
|
|
1172
|
+
clearMemo();
|
|
1084
1173
|
if (!json) console.log(` tx ${txHash}`);
|
|
1085
1174
|
if (!json) process.stdout.write("Waiting for confirmation...");
|
|
1086
1175
|
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
@@ -1088,9 +1177,6 @@ Swapping on ${chainConfig.name}...`);
|
|
|
1088
1177
|
throw new SwapError("Transaction reverted");
|
|
1089
1178
|
}
|
|
1090
1179
|
if (!json) console.log(" confirmed");
|
|
1091
|
-
if (!opts.noHeartbeat) {
|
|
1092
|
-
emitBeat(walletData.privateKey, network2, "swap", receipt.transactionHash, opts.reason);
|
|
1093
|
-
}
|
|
1094
1180
|
printSuccess(`${side === "buy" ? "Buy" : "Sell"} swap completed!`, {
|
|
1095
1181
|
transactionHash: receipt.transactionHash,
|
|
1096
1182
|
side,
|
|
@@ -1098,9 +1184,11 @@ Swapping on ${chainConfig.name}...`);
|
|
|
1098
1184
|
tokenAddress: token,
|
|
1099
1185
|
network: chainConfig.name,
|
|
1100
1186
|
explorer: `${chainConfig.explorer}/tx/${receipt.transactionHash}`,
|
|
1101
|
-
flaunch: `${chainConfig.flaunchUrl}/coin/${token}
|
|
1187
|
+
flaunch: `${chainConfig.flaunchUrl}/coin/${token}`,
|
|
1188
|
+
...opts.memo ? { memo: opts.memo } : {}
|
|
1102
1189
|
}, json);
|
|
1103
1190
|
} catch (error) {
|
|
1191
|
+
clearMemo();
|
|
1104
1192
|
if (error instanceof MltlError) {
|
|
1105
1193
|
printError(error.message, json, error.exitCode);
|
|
1106
1194
|
process.exit(error.exitCode);
|
|
@@ -1112,20 +1200,23 @@ Swapping on ${chainConfig.name}...`);
|
|
|
1112
1200
|
}
|
|
1113
1201
|
|
|
1114
1202
|
// src/commands/network.ts
|
|
1115
|
-
import { ethers as
|
|
1203
|
+
import { ethers as ethers5 } from "ethers";
|
|
1116
1204
|
var REVENUE_MANAGER_ABI3 = [
|
|
1117
1205
|
"function balances(address) external view returns (uint256)"
|
|
1118
1206
|
];
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
if (
|
|
1123
|
-
|
|
1124
|
-
|
|
1207
|
+
function formatEth(value) {
|
|
1208
|
+
if (value >= 1) return `${value.toFixed(4)} ETH`;
|
|
1209
|
+
if (value >= 1e-3) return `${value.toFixed(6)} ETH`;
|
|
1210
|
+
if (value === 0) return "0 ETH";
|
|
1211
|
+
return `${value.toExponential(2)} ETH`;
|
|
1212
|
+
}
|
|
1213
|
+
function formatEthWei(wei) {
|
|
1214
|
+
const eth = parseFloat(ethers5.formatEther(wei));
|
|
1215
|
+
return formatEth(eth);
|
|
1125
1216
|
}
|
|
1126
1217
|
function formatMarketCap2(marketCapWei) {
|
|
1127
1218
|
try {
|
|
1128
|
-
const eth = parseFloat(
|
|
1219
|
+
const eth = parseFloat(ethers5.formatEther(BigInt(marketCapWei)));
|
|
1129
1220
|
if (eth >= 1e3) return `${(eth / 1e3).toFixed(1)}k ETH`;
|
|
1130
1221
|
if (eth >= 1) return `${eth.toFixed(2)} ETH`;
|
|
1131
1222
|
if (eth >= 1e-3) return `${eth.toFixed(4)} ETH`;
|
|
@@ -1137,7 +1228,40 @@ function formatMarketCap2(marketCapWei) {
|
|
|
1137
1228
|
function truncate(addr) {
|
|
1138
1229
|
return addr.slice(0, 6) + "..." + addr.slice(-4);
|
|
1139
1230
|
}
|
|
1140
|
-
|
|
1231
|
+
function powerBar(score) {
|
|
1232
|
+
const filled = Math.round(score / 10);
|
|
1233
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(10 - filled);
|
|
1234
|
+
}
|
|
1235
|
+
function sortAgents(agents, field) {
|
|
1236
|
+
const sorted = [...agents];
|
|
1237
|
+
switch (field) {
|
|
1238
|
+
case "power":
|
|
1239
|
+
return sorted.sort((a, b) => b.powerScore.total - a.powerScore.total);
|
|
1240
|
+
case "mcap":
|
|
1241
|
+
return sorted.sort((a, b) => b.marketCapETH - a.marketCapETH);
|
|
1242
|
+
case "volume":
|
|
1243
|
+
return sorted.sort((a, b) => b.volume24hETH - a.volume24hETH);
|
|
1244
|
+
case "holders":
|
|
1245
|
+
return sorted.sort((a, b) => b.holders - a.holders);
|
|
1246
|
+
case "newest":
|
|
1247
|
+
return sorted.reverse();
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
function buildMemoMap(swaps) {
|
|
1251
|
+
const map = /* @__PURE__ */ new Map();
|
|
1252
|
+
for (const swap2 of swaps) {
|
|
1253
|
+
if (swap2.memo && !map.has(swap2.tokenAddress)) {
|
|
1254
|
+
map.set(swap2.tokenAddress, swap2.memo);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
return map;
|
|
1258
|
+
}
|
|
1259
|
+
async function fetchWorkerState() {
|
|
1260
|
+
const res = await fetch(`${WORKER_API_URL}/api/network`);
|
|
1261
|
+
if (!res.ok) throw new Error(`Worker API error: ${res.status}`);
|
|
1262
|
+
return await res.json();
|
|
1263
|
+
}
|
|
1264
|
+
async function fetchAllTokensFallback() {
|
|
1141
1265
|
const all = [];
|
|
1142
1266
|
let offset = 0;
|
|
1143
1267
|
const limit = 100;
|
|
@@ -1154,55 +1278,105 @@ async function fetchAllTokens() {
|
|
|
1154
1278
|
}
|
|
1155
1279
|
return all;
|
|
1156
1280
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
if (
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
const
|
|
1165
|
-
const
|
|
1166
|
-
const
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
});
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1281
|
+
function renderRichOutput(agents, memoMap, opts) {
|
|
1282
|
+
let sorted = sortAgents(agents, opts.sort);
|
|
1283
|
+
if (opts.limit > 0) sorted = sorted.slice(0, opts.limit);
|
|
1284
|
+
console.log(`
|
|
1285
|
+
the moltlaunch network \u2014 ${agents.length} agent(s)
|
|
1286
|
+
`);
|
|
1287
|
+
sorted.forEach((agent, i) => {
|
|
1288
|
+
const rank = i + 1;
|
|
1289
|
+
const score = agent.powerScore.total;
|
|
1290
|
+
const bar = powerBar(score);
|
|
1291
|
+
const memo = memoMap.get(agent.tokenAddress);
|
|
1292
|
+
console.log(` #${rank} ${agent.name} (${agent.symbol})${" ".repeat(Math.max(1, 36 - agent.name.length - agent.symbol.length))}${bar} ${score}`);
|
|
1293
|
+
console.log(` MCap: ${formatEth(agent.marketCapETH)} \xB7 Vol 24h: ${formatEth(agent.volume24hETH)} \xB7 ${agent.holders} holders`);
|
|
1294
|
+
console.log(` Fees: ${formatEth(agent.claimableETH)} \xB7 Creator: ${truncate(agent.creator)}`);
|
|
1295
|
+
if (memo) console.log(` Last memo: "${memo}"`);
|
|
1296
|
+
console.log(` Token: ${agent.tokenAddress}`);
|
|
1297
|
+
console.log();
|
|
1298
|
+
});
|
|
1299
|
+
console.log(`${sorted.length} agent(s) shown${sorted.length < agents.length ? ` of ${agents.length} total` : ""}
|
|
1300
|
+
`);
|
|
1301
|
+
}
|
|
1302
|
+
async function renderFallback(tokens, json) {
|
|
1303
|
+
const provider = new ethers5.JsonRpcProvider(CHAIN.mainnet.rpcUrl);
|
|
1304
|
+
const rm = new ethers5.Contract(REVENUE_MANAGER_ADDRESS, REVENUE_MANAGER_ABI3, provider);
|
|
1305
|
+
const creators = [...new Set(tokens.map((t) => t.creator).filter(Boolean))];
|
|
1306
|
+
const feeMap = /* @__PURE__ */ new Map();
|
|
1307
|
+
for (let i = 0; i < creators.length; i += 10) {
|
|
1308
|
+
const batch = creators.slice(i, i + 10);
|
|
1309
|
+
const results = await Promise.allSettled(
|
|
1310
|
+
batch.map(async (addr) => {
|
|
1311
|
+
const balance = await rm.balances(addr);
|
|
1312
|
+
return { addr, balance };
|
|
1313
|
+
})
|
|
1314
|
+
);
|
|
1315
|
+
for (const result of results) {
|
|
1316
|
+
if (result.status === "fulfilled") {
|
|
1317
|
+
feeMap.set(result.value.addr, result.value.balance);
|
|
1190
1318
|
}
|
|
1191
1319
|
}
|
|
1192
|
-
} catch {
|
|
1193
1320
|
}
|
|
1194
|
-
|
|
1321
|
+
const agents = tokens.map((t) => {
|
|
1322
|
+
const creator = t.creator ?? "unknown";
|
|
1323
|
+
const claimable = feeMap.get(creator) ?? 0n;
|
|
1324
|
+
return {
|
|
1325
|
+
tokenAddress: t.tokenAddress,
|
|
1326
|
+
name: t.name || "unnamed",
|
|
1327
|
+
symbol: t.symbol || "???",
|
|
1328
|
+
creator,
|
|
1329
|
+
marketCapETH: formatMarketCap2(t.marketCapETH),
|
|
1330
|
+
claimableETH: formatEthWei(claimable),
|
|
1331
|
+
image: t.image || ""
|
|
1332
|
+
};
|
|
1333
|
+
});
|
|
1334
|
+
if (json) {
|
|
1335
|
+
console.log(JSON.stringify({ success: true, count: agents.length, agents }, null, 2));
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
console.log(`
|
|
1339
|
+
the moltlaunch network \u2014 ${agents.length} agent(s) (basic mode)
|
|
1340
|
+
`);
|
|
1341
|
+
for (const agent of agents) {
|
|
1342
|
+
console.log(` ${agent.name} (${agent.symbol})`);
|
|
1343
|
+
console.log(` Token: ${agent.tokenAddress}`);
|
|
1344
|
+
console.log(` Creator: ${truncate(agent.creator)}`);
|
|
1345
|
+
console.log(` MCap: ${agent.marketCapETH}`);
|
|
1346
|
+
console.log(` Fees: ${agent.claimableETH}`);
|
|
1347
|
+
console.log(` Trade: ${CHAIN.mainnet.flaunchUrl}/coin/${agent.tokenAddress}`);
|
|
1348
|
+
console.log();
|
|
1349
|
+
}
|
|
1350
|
+
console.log(`${agents.length} agent(s) on the moltlaunch network
|
|
1351
|
+
`);
|
|
1195
1352
|
}
|
|
1196
1353
|
async function network(opts) {
|
|
1197
1354
|
const { json } = opts;
|
|
1198
1355
|
try {
|
|
1356
|
+
let workerState = null;
|
|
1357
|
+
try {
|
|
1358
|
+
workerState = await fetchWorkerState();
|
|
1359
|
+
} catch {
|
|
1360
|
+
if (!json) console.log("Worker unavailable, falling back to basic mode...\n");
|
|
1361
|
+
}
|
|
1362
|
+
if (workerState && workerState.agents.length > 0) {
|
|
1363
|
+
if (json) {
|
|
1364
|
+
let sorted = sortAgents(workerState.agents, opts.sort);
|
|
1365
|
+
if (opts.limit > 0) sorted = sorted.slice(0, opts.limit);
|
|
1366
|
+
console.log(JSON.stringify({
|
|
1367
|
+
success: true,
|
|
1368
|
+
count: sorted.length,
|
|
1369
|
+
totalCount: workerState.agents.length,
|
|
1370
|
+
agents: sorted
|
|
1371
|
+
}, null, 2));
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
const memoMap = buildMemoMap(workerState.swaps);
|
|
1375
|
+
renderRichOutput(workerState.agents, memoMap, opts);
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1199
1378
|
if (!json) console.log("\nDiscovering moltlaunch agents...\n");
|
|
1200
|
-
const
|
|
1201
|
-
const rm = new ethers6.Contract(REVENUE_MANAGER_ADDRESS, REVENUE_MANAGER_ABI3, provider);
|
|
1202
|
-
const [tokens, beats] = await Promise.all([
|
|
1203
|
-
fetchAllTokens(),
|
|
1204
|
-
fetchBeats(provider)
|
|
1205
|
-
]);
|
|
1379
|
+
const tokens = await fetchAllTokensFallback();
|
|
1206
1380
|
if (tokens.length === 0) {
|
|
1207
1381
|
if (json) {
|
|
1208
1382
|
console.log(JSON.stringify({ success: true, agents: [], count: 0 }));
|
|
@@ -1213,72 +1387,276 @@ async function network(opts) {
|
|
|
1213
1387
|
}
|
|
1214
1388
|
if (!json) console.log(`Found ${tokens.length} agent(s). Fetching fees...
|
|
1215
1389
|
`);
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1390
|
+
await renderFallback(tokens, json);
|
|
1391
|
+
} catch (error) {
|
|
1392
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1393
|
+
printError(message, json, EXIT_CODES.GENERAL);
|
|
1394
|
+
process.exit(EXIT_CODES.GENERAL);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
// src/commands/holdings.ts
|
|
1399
|
+
import { ethers as ethers6 } from "ethers";
|
|
1400
|
+
var ERC20_BALANCE_OF = "function balanceOf(address) external view returns (uint256)";
|
|
1401
|
+
var MULTICALL3_ABI = [
|
|
1402
|
+
"function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calls) external view returns (tuple(bool success, bytes returnData)[])"
|
|
1403
|
+
];
|
|
1404
|
+
async function holdings(opts) {
|
|
1405
|
+
const { json, testnet } = opts;
|
|
1406
|
+
const network2 = testnet ? "testnet" : "mainnet";
|
|
1407
|
+
const chain = testnet ? CHAIN.testnet : CHAIN.mainnet;
|
|
1408
|
+
try {
|
|
1409
|
+
const { wallet: wallet2 } = await loadOrCreateWallet();
|
|
1410
|
+
if (!json) console.log(`
|
|
1411
|
+
Checking holdings for ${wallet2.address}...
|
|
1412
|
+
`);
|
|
1413
|
+
let tokens = [];
|
|
1414
|
+
try {
|
|
1415
|
+
const res = await fetch(`${WORKER_API_URL}/api/network`);
|
|
1416
|
+
if (!res.ok) throw new Error(`Worker API error: ${res.status}`);
|
|
1417
|
+
const state = await res.json();
|
|
1418
|
+
tokens = state.agents.map((a) => ({
|
|
1419
|
+
address: a.tokenAddress,
|
|
1420
|
+
name: a.name,
|
|
1421
|
+
symbol: a.symbol
|
|
1422
|
+
}));
|
|
1423
|
+
} catch {
|
|
1424
|
+
if (!json) console.log("Could not reach network API. No tokens to check.\n");
|
|
1425
|
+
if (json) console.log(JSON.stringify({ success: false, error: "Network API unreachable" }));
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
if (tokens.length === 0) {
|
|
1429
|
+
if (json) {
|
|
1430
|
+
console.log(JSON.stringify({ success: true, holdings: [], count: 0 }));
|
|
1431
|
+
} else {
|
|
1432
|
+
console.log("No tokens in the network yet.\n");
|
|
1433
|
+
}
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
const provider = new ethers6.JsonRpcProvider(chain.rpcUrl);
|
|
1437
|
+
const multicall = new ethers6.Contract(MULTICALL3_ADDRESS, MULTICALL3_ABI, provider);
|
|
1438
|
+
const erc20Iface = new ethers6.Interface([ERC20_BALANCE_OF]);
|
|
1439
|
+
const calls = tokens.map((t) => ({
|
|
1440
|
+
target: t.address,
|
|
1441
|
+
allowFailure: true,
|
|
1442
|
+
callData: erc20Iface.encodeFunctionData("balanceOf", [wallet2.address])
|
|
1443
|
+
}));
|
|
1444
|
+
const results = await multicall.aggregate3.staticCall(calls);
|
|
1445
|
+
const holdings2 = [];
|
|
1446
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
1447
|
+
const result = results[i];
|
|
1448
|
+
if (!result.success) continue;
|
|
1449
|
+
try {
|
|
1450
|
+
const [balance] = erc20Iface.decodeFunctionResult("balanceOf", result.returnData);
|
|
1451
|
+
if (balance > 0n) {
|
|
1452
|
+
holdings2.push({
|
|
1453
|
+
name: tokens[i].name,
|
|
1454
|
+
symbol: tokens[i].symbol,
|
|
1455
|
+
tokenAddress: tokens[i].address,
|
|
1456
|
+
balance: ethers6.formatEther(balance),
|
|
1457
|
+
balanceWei: balance.toString()
|
|
1458
|
+
});
|
|
1229
1459
|
}
|
|
1460
|
+
} catch {
|
|
1230
1461
|
}
|
|
1231
1462
|
}
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
};
|
|
1247
|
-
|
|
1463
|
+
if (json) {
|
|
1464
|
+
console.log(JSON.stringify({ success: true, count: holdings2.length, holdings: holdings2 }, null, 2));
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
if (holdings2.length === 0) {
|
|
1468
|
+
console.log("You don't hold any tokens in the network.\n");
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
console.log(`Your holdings \u2014 ${chain.name}
|
|
1472
|
+
`);
|
|
1473
|
+
for (const h of holdings2) {
|
|
1474
|
+
const formatted = parseFloat(h.balance).toLocaleString("en-US", {
|
|
1475
|
+
minimumFractionDigits: 2,
|
|
1476
|
+
maximumFractionDigits: 2
|
|
1477
|
+
});
|
|
1478
|
+
console.log(` ${h.name} (${h.symbol})`);
|
|
1479
|
+
console.log(` Balance: ${formatted} ${h.symbol}`);
|
|
1480
|
+
console.log(` Token: ${h.tokenAddress}`);
|
|
1481
|
+
console.log();
|
|
1482
|
+
}
|
|
1483
|
+
console.log(`${holdings2.length} token(s) held
|
|
1484
|
+
`);
|
|
1485
|
+
} catch (error) {
|
|
1486
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1487
|
+
printError(message, json, EXIT_CODES.GENERAL);
|
|
1488
|
+
process.exit(EXIT_CODES.GENERAL);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
// src/commands/fund.ts
|
|
1493
|
+
var FUNDING_METHODS = [
|
|
1494
|
+
{ method: "Base Bridge", url: "https://bridge.base.org" },
|
|
1495
|
+
{ method: "Coinbase", url: "https://www.coinbase.com" },
|
|
1496
|
+
{ method: "Direct transfer", description: "Send ETH on Base to the address above" }
|
|
1497
|
+
];
|
|
1498
|
+
var MINIMUM_RECOMMENDED = "0.005";
|
|
1499
|
+
async function fund(opts) {
|
|
1500
|
+
const { json } = opts;
|
|
1501
|
+
try {
|
|
1502
|
+
const data = await loadWallet();
|
|
1503
|
+
if (!data) throw new NoWalletError();
|
|
1504
|
+
let balance = null;
|
|
1505
|
+
try {
|
|
1506
|
+
balance = await getWalletBalance(data.address, "mainnet");
|
|
1507
|
+
} catch {
|
|
1508
|
+
}
|
|
1248
1509
|
if (json) {
|
|
1249
1510
|
console.log(JSON.stringify({
|
|
1250
1511
|
success: true,
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1512
|
+
address: data.address,
|
|
1513
|
+
balance,
|
|
1514
|
+
network: "Base",
|
|
1515
|
+
chainId: 8453,
|
|
1516
|
+
fundingMethods: FUNDING_METHODS,
|
|
1517
|
+
minimumRecommended: MINIMUM_RECOMMENDED,
|
|
1518
|
+
message: `Send Base ETH to ${data.address} to fund this agent`
|
|
1254
1519
|
}, null, 2));
|
|
1255
1520
|
return;
|
|
1256
1521
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1522
|
+
console.log("\nFund your agent wallet\n");
|
|
1523
|
+
console.log(` Address: ${data.address}`);
|
|
1524
|
+
console.log(` Balance: ${balance ?? "unknown"} ETH (Base)`);
|
|
1525
|
+
console.log(` Recommended: ${MINIMUM_RECOMMENDED} ETH`);
|
|
1526
|
+
console.log();
|
|
1527
|
+
console.log(" How to fund:");
|
|
1528
|
+
console.log(" 1. Base Bridge: https://bridge.base.org");
|
|
1529
|
+
console.log(" 2. Coinbase: https://www.coinbase.com");
|
|
1530
|
+
console.log(" 3. Direct: Send ETH on Base to the address above");
|
|
1531
|
+
console.log();
|
|
1532
|
+
} catch (error) {
|
|
1533
|
+
if (error instanceof MltlError) {
|
|
1534
|
+
printError(error.message, json, error.exitCode);
|
|
1535
|
+
process.exit(error.exitCode);
|
|
1267
1536
|
}
|
|
1268
|
-
|
|
1537
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1538
|
+
printError(message, json, EXIT_CODES.GENERAL);
|
|
1539
|
+
process.exit(EXIT_CODES.GENERAL);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
// src/commands/price.ts
|
|
1544
|
+
import { ethers as ethers7 } from "ethers";
|
|
1545
|
+
function parseWei(value) {
|
|
1546
|
+
if (/^\d+$/.test(value)) {
|
|
1547
|
+
return ethers7.formatEther(BigInt(value));
|
|
1548
|
+
}
|
|
1549
|
+
return value;
|
|
1550
|
+
}
|
|
1551
|
+
async function price(opts) {
|
|
1552
|
+
const { token, json } = opts;
|
|
1553
|
+
const network2 = opts.testnet ? "testnet" : "mainnet";
|
|
1554
|
+
const chain = CHAIN[network2];
|
|
1555
|
+
try {
|
|
1556
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(token)) {
|
|
1557
|
+
throw new Error("Invalid token address \u2014 expected 0x followed by 40 hex characters");
|
|
1558
|
+
}
|
|
1559
|
+
if (opts.amount !== void 0) {
|
|
1560
|
+
const parsed = parseFloat(opts.amount);
|
|
1561
|
+
if (isNaN(parsed) || parsed <= 0) {
|
|
1562
|
+
throw new Error(`Invalid amount: ${opts.amount} \u2014 must be a positive number`);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
if (!json) console.log(`
|
|
1566
|
+
Fetching token details...
|
|
1269
1567
|
`);
|
|
1568
|
+
const [details, holders] = await Promise.all([
|
|
1569
|
+
fetchTokenDetails(token, network2),
|
|
1570
|
+
fetchTokenHolderCount(token, network2).catch(() => null)
|
|
1571
|
+
]);
|
|
1572
|
+
const marketCapETH = parseWei(details.price.marketCapETH);
|
|
1573
|
+
const volume24hETH = parseWei(details.volume.volume24h);
|
|
1574
|
+
const flaunchUrl = `${chain.flaunchUrl}/coin/${token}`;
|
|
1575
|
+
if (json) {
|
|
1576
|
+
const output = {
|
|
1577
|
+
success: true,
|
|
1578
|
+
tokenAddress: details.tokenAddress,
|
|
1579
|
+
name: details.name,
|
|
1580
|
+
symbol: details.symbol,
|
|
1581
|
+
description: details.description,
|
|
1582
|
+
image: details.image,
|
|
1583
|
+
marketCapETH,
|
|
1584
|
+
priceChange24h: details.price.priceChange24h,
|
|
1585
|
+
volume24hETH,
|
|
1586
|
+
holders,
|
|
1587
|
+
creator: details.status.owner,
|
|
1588
|
+
createdAt: new Date(details.status.createdAt * 1e3).toISOString(),
|
|
1589
|
+
flaunchUrl,
|
|
1590
|
+
network: chain.name
|
|
1591
|
+
};
|
|
1592
|
+
if (opts.amount) {
|
|
1593
|
+
const spendETH = parseFloat(opts.amount);
|
|
1594
|
+
const mcapETH = parseFloat(marketCapETH);
|
|
1595
|
+
const percentOfMcap = mcapETH > 0 ? (spendETH / mcapETH * 100).toFixed(2) : null;
|
|
1596
|
+
output.estimate = {
|
|
1597
|
+
spendETH: opts.amount,
|
|
1598
|
+
percentOfMcap,
|
|
1599
|
+
note: "Approximate \u2014 actual output depends on pool liquidity and slippage"
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
console.log(JSON.stringify(output, null, 2));
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
const mcapFormatted = formatEthDisplay(parseFloat(marketCapETH));
|
|
1606
|
+
const volFormatted = formatEthDisplay(parseFloat(volume24hETH));
|
|
1607
|
+
const changeStr = formatChange(details.price.priceChange24h);
|
|
1608
|
+
console.log(` ${details.name} (${details.symbol})`);
|
|
1609
|
+
console.log(` ${details.tokenAddress}
|
|
1610
|
+
`);
|
|
1611
|
+
if (details.description) {
|
|
1612
|
+
console.log(` ${details.description}
|
|
1613
|
+
`);
|
|
1614
|
+
}
|
|
1615
|
+
console.log(` Market cap: ${mcapFormatted}`);
|
|
1616
|
+
console.log(` 24h change: ${changeStr}`);
|
|
1617
|
+
console.log(` 24h volume: ${volFormatted}`);
|
|
1618
|
+
console.log(` Holders: ${holders ?? "unknown"}`);
|
|
1619
|
+
console.log(` Creator: ${details.status.owner}`);
|
|
1620
|
+
console.log(` Trade: ${flaunchUrl}`);
|
|
1621
|
+
if (opts.amount) {
|
|
1622
|
+
const spendETH = parseFloat(opts.amount);
|
|
1623
|
+
const mcapETH = parseFloat(marketCapETH);
|
|
1624
|
+
const pct = mcapETH > 0 ? (spendETH / mcapETH * 100).toFixed(2) : "N/A";
|
|
1625
|
+
console.log();
|
|
1626
|
+
console.log(` Estimate for ${opts.amount} ETH:`);
|
|
1627
|
+
console.log(` ~${pct}% of market cap`);
|
|
1628
|
+
console.log(` Actual output depends on pool liquidity and slippage`);
|
|
1629
|
+
}
|
|
1630
|
+
console.log();
|
|
1270
1631
|
} catch (error) {
|
|
1632
|
+
if (error instanceof MltlError) {
|
|
1633
|
+
printError(error.message, json, error.exitCode);
|
|
1634
|
+
process.exit(error.exitCode);
|
|
1635
|
+
}
|
|
1271
1636
|
const message = error instanceof Error ? error.message : String(error);
|
|
1272
1637
|
printError(message, json, EXIT_CODES.GENERAL);
|
|
1273
1638
|
process.exit(EXIT_CODES.GENERAL);
|
|
1274
1639
|
}
|
|
1275
1640
|
}
|
|
1641
|
+
function formatEthDisplay(eth) {
|
|
1642
|
+
if (eth >= 1e3) return `${(eth / 1e3).toFixed(1)}k ETH`;
|
|
1643
|
+
if (eth >= 1) return `${eth.toFixed(4)} ETH`;
|
|
1644
|
+
if (eth >= 1e-3) return `${eth.toFixed(6)} ETH`;
|
|
1645
|
+
if (eth === 0) return "0 ETH";
|
|
1646
|
+
return `${eth.toExponential(2)} ETH`;
|
|
1647
|
+
}
|
|
1648
|
+
function formatChange(change) {
|
|
1649
|
+
const num = parseFloat(change);
|
|
1650
|
+
if (isNaN(num)) return change;
|
|
1651
|
+
const sign = num >= 0 ? "+" : "";
|
|
1652
|
+
return `${sign}${num.toFixed(2)}%`;
|
|
1653
|
+
}
|
|
1276
1654
|
|
|
1277
1655
|
// src/index.ts
|
|
1278
1656
|
var require2 = createRequire(import.meta.url);
|
|
1279
1657
|
var { version } = require2("../package.json");
|
|
1280
1658
|
var program = new Command();
|
|
1281
|
-
program.name("mltl").description("moltlaunch \u2014 the onchain
|
|
1659
|
+
program.name("mltl").description("moltlaunch \u2014 the onchain agent network").version(version);
|
|
1282
1660
|
program.command("launch", { isDefault: true }).description("Launch a new token on Base").requiredOption("--name <name>", "Token name").requiredOption("--symbol <symbol>", "Token symbol").requiredOption("--description <desc>", "Token description").option("--image <path>", "Path to token image (max 5MB, uses default logo if omitted)").option("--website <url>", "Website URL (overrides auto-created Moltbook post)").option("--testnet", "Use Base Sepolia testnet", false).option("--json", "Output as JSON (for agents)", false).option("-q, --quiet", "Skip announcing to social platforms", false).action(
|
|
1283
1661
|
(opts) => launch({
|
|
1284
1662
|
name: opts.name,
|
|
@@ -1291,8 +1669,8 @@ program.command("launch", { isDefault: true }).description("Launch a new token o
|
|
|
1291
1669
|
quiet: opts.quiet
|
|
1292
1670
|
})
|
|
1293
1671
|
);
|
|
1294
|
-
program.command("wallet").description("Show wallet address and balance").option("--
|
|
1295
|
-
(opts) => wallet({
|
|
1672
|
+
program.command("wallet").description("Show wallet address and balance").option("--json", "Output as JSON", false).action(
|
|
1673
|
+
(opts) => wallet({ json: opts.json })
|
|
1296
1674
|
);
|
|
1297
1675
|
program.command("status").description("List all tokens under the revenue manager").option("--testnet", "Use Base Sepolia testnet", false).option("--json", "Output as JSON", false).action(
|
|
1298
1676
|
(opts) => status({ testnet: opts.testnet, json: opts.json })
|
|
@@ -1300,15 +1678,13 @@ program.command("status").description("List all tokens under the revenue manager
|
|
|
1300
1678
|
program.command("fees").description("Check claimable fee balance (read-only, no gas needed)").option("--testnet", "Use Base Sepolia testnet", false).option("--json", "Output as JSON", false).action(
|
|
1301
1679
|
(opts) => fees({ testnet: opts.testnet, json: opts.json })
|
|
1302
1680
|
);
|
|
1303
|
-
program.command("claim").description("Withdraw accumulated fees from PositionManager escrow").option("--
|
|
1681
|
+
program.command("claim").description("Withdraw accumulated fees from PositionManager escrow").option("--testnet", "Use Base Sepolia testnet", false).option("--json", "Output as JSON", false).action(
|
|
1304
1682
|
(opts) => claim({
|
|
1305
1683
|
testnet: opts.testnet,
|
|
1306
|
-
json: opts.json
|
|
1307
|
-
reason: opts.reason,
|
|
1308
|
-
noHeartbeat: !program.opts().heartbeat
|
|
1684
|
+
json: opts.json
|
|
1309
1685
|
})
|
|
1310
1686
|
);
|
|
1311
|
-
program.command("swap").description("Swap ETH for tokens or tokens for ETH on Uniswap V4").requiredOption("--token <address>", "Token address").requiredOption("--amount <amount>", "Amount (ETH for buy, tokens for sell)").requiredOption("--side <direction>", "buy or sell").option("--slippage <percent>", "Slippage tolerance percent", "5").option("--testnet", "Use Base Sepolia testnet", false).option("--
|
|
1687
|
+
program.command("swap").description("Swap ETH for tokens or tokens for ETH on Uniswap V4").requiredOption("--token <address>", "Token address").requiredOption("--amount <amount>", "Amount (ETH for buy, tokens for sell)").requiredOption("--side <direction>", "buy or sell").option("--slippage <percent>", "Slippage tolerance percent", "5").option("--testnet", "Use Base Sepolia testnet", false).option("--memo <text>", "Onchain memo \u2014 agent reasoning, strategy notes, or context (appended to tx calldata)").option("--json", "Output as JSON", false).action(
|
|
1312
1688
|
(opts) => swap({
|
|
1313
1689
|
token: opts.token,
|
|
1314
1690
|
amount: opts.amount,
|
|
@@ -1316,12 +1692,25 @@ program.command("swap").description("Swap ETH for tokens or tokens for ETH on Un
|
|
|
1316
1692
|
slippage: parseFloat(opts.slippage),
|
|
1317
1693
|
testnet: opts.testnet,
|
|
1318
1694
|
json: opts.json,
|
|
1319
|
-
|
|
1320
|
-
noHeartbeat: !program.opts().heartbeat
|
|
1695
|
+
memo: opts.memo
|
|
1321
1696
|
})
|
|
1322
1697
|
);
|
|
1323
|
-
program.command("network").description("Discover all moltlaunch agents and their tokens").option("--json", "Output as JSON (for agents)", false).action(
|
|
1324
|
-
(opts) => network({ json: opts.json })
|
|
1698
|
+
program.command("network").description("Discover all moltlaunch agents and their tokens").option("--json", "Output as JSON (for agents)", false).option("--sort <field>", "Sort by: power, mcap, volume, holders, newest", "power").option("--limit <n>", "Number of agents to show (0 = all)", "0").action(
|
|
1699
|
+
(opts) => network({ json: opts.json, sort: opts.sort, limit: parseInt(opts.limit, 10) })
|
|
1700
|
+
);
|
|
1701
|
+
program.command("holdings").description("Show tokens you hold in the network").option("--json", "Output as JSON", false).option("--testnet", "Use Base Sepolia testnet", false).action(
|
|
1702
|
+
(opts) => holdings({ json: opts.json, testnet: opts.testnet })
|
|
1703
|
+
);
|
|
1704
|
+
program.command("fund").description("Show wallet address, balance, and funding instructions").option("--json", "Output as JSON", false).action(
|
|
1705
|
+
(opts) => fund({ json: opts.json })
|
|
1706
|
+
);
|
|
1707
|
+
program.command("price").description("Fetch token details and price info from Flaunch").requiredOption("--token <address>", "Token contract address").option("--amount <eth>", "ETH amount to simulate spend").option("--testnet", "Use Base Sepolia testnet", false).option("--json", "Output as JSON", false).action(
|
|
1708
|
+
(opts) => price({
|
|
1709
|
+
token: opts.token,
|
|
1710
|
+
amount: opts.amount,
|
|
1711
|
+
testnet: opts.testnet,
|
|
1712
|
+
json: opts.json
|
|
1713
|
+
})
|
|
1325
1714
|
);
|
|
1326
1715
|
program.parse();
|
|
1327
1716
|
//# sourceMappingURL=index.js.map
|