nyxora 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/SECURITY.md +6 -4
- package/dashboard/dist/assets/index-Cy7yprIz.css +1 -0
- package/dashboard/dist/assets/index-L20NVlIh.js +9 -0
- package/dashboard/dist/index.html +2 -2
- package/dist/agent/limitOrderManager.js +177 -0
- package/dist/agent/reasoning.js +145 -70
- package/dist/agent/updateProfile.js +52 -0
- package/dist/gateway/server.js +6 -0
- package/dist/gateway/telegram.js +5 -0
- package/dist/system/pluginManager.js +56 -0
- package/dist/system/skills/browseWeb.js +50 -0
- package/dist/system/skills/executeShell.js +38 -0
- package/dist/system/skills/installSkill.js +51 -0
- package/dist/system/skills/readFile.js +39 -0
- package/dist/system/skills/updateSecurityPolicy.js +60 -0
- package/dist/system/skills/writeFile.js +44 -0
- package/dist/web3/skills/bridgeToken.js +19 -2
- package/dist/web3/skills/checkPortfolio.js +154 -0
- package/dist/web3/skills/checkSecurity.js +67 -0
- package/dist/web3/skills/createWallet.js +34 -0
- package/dist/web3/skills/marketAnalysis.js +71 -0
- package/dist/web3/skills/swapToken.js +19 -2
- package/dist/web3/utils/tokens.js +2 -1
- package/package.json +1 -1
- package/user.md +1 -1
- package/dashboard/dist/assets/index-33NxKAJt.css +0 -1
- package/dashboard/dist/assets/index-CKUbyknW.js +0 -9
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.installExternalSkillToolDefinition = void 0;
|
|
7
|
+
exports.installExternalSkill = installExternalSkill;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
async function installExternalSkill(url) {
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetch(url);
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
return `Failed to fetch skill from URL. Status: ${response.status}`;
|
|
15
|
+
}
|
|
16
|
+
const code = await response.text();
|
|
17
|
+
// Extract a filename from URL, or generate a random one
|
|
18
|
+
let filename = url.split('/').pop() || '';
|
|
19
|
+
if (!filename.endsWith('.ts') && !filename.endsWith('.js')) {
|
|
20
|
+
filename = `skill_${Date.now()}.ts`;
|
|
21
|
+
}
|
|
22
|
+
// Ensure external_skills directory exists
|
|
23
|
+
const pluginsDir = path_1.default.join(process.cwd(), 'src', 'external_skills');
|
|
24
|
+
if (!fs_1.default.existsSync(pluginsDir)) {
|
|
25
|
+
fs_1.default.mkdirSync(pluginsDir, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
const filePath = path_1.default.join(pluginsDir, filename);
|
|
28
|
+
fs_1.default.writeFileSync(filePath, code, 'utf8');
|
|
29
|
+
return `Skill successfully downloaded and installed to ${filePath}. Please restart the server for the plugin manager to compile and load it.`;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
return `Failed to install skill: ${error.message}`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.installExternalSkillToolDefinition = {
|
|
36
|
+
type: "function",
|
|
37
|
+
function: {
|
|
38
|
+
name: "install_external_skill",
|
|
39
|
+
description: "Downloads and installs a third-party typescript skill from a URL (e.g. GitHub Gist raw URL).",
|
|
40
|
+
parameters: {
|
|
41
|
+
type: "object",
|
|
42
|
+
properties: {
|
|
43
|
+
url: {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "The direct raw URL to the .ts or .js file of the skill.",
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
required: ["url"],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.readLocalFileToolDefinition = void 0;
|
|
7
|
+
exports.readLocalFile = readLocalFile;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
function readLocalFile(filePath) {
|
|
11
|
+
try {
|
|
12
|
+
const absolutePath = path_1.default.resolve(filePath);
|
|
13
|
+
if (!fs_1.default.existsSync(absolutePath)) {
|
|
14
|
+
return `Error: File not found at ${absolutePath}`;
|
|
15
|
+
}
|
|
16
|
+
const content = fs_1.default.readFileSync(absolutePath, 'utf8');
|
|
17
|
+
return content;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return `Failed to read file: ${error.message}`;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.readLocalFileToolDefinition = {
|
|
24
|
+
type: "function",
|
|
25
|
+
function: {
|
|
26
|
+
name: "read_local_file",
|
|
27
|
+
description: "Reads the content of a local file on the user's computer.",
|
|
28
|
+
parameters: {
|
|
29
|
+
type: "object",
|
|
30
|
+
properties: {
|
|
31
|
+
filePath: {
|
|
32
|
+
type: "string",
|
|
33
|
+
description: "The absolute or relative path to the file.",
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
required: ["filePath"],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.updateSecurityPolicyToolDefinition = void 0;
|
|
7
|
+
exports.updateSecurityPolicy = updateSecurityPolicy;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const paths_1 = require("../../config/paths");
|
|
10
|
+
function updateSecurityPolicy(rule, action) {
|
|
11
|
+
try {
|
|
12
|
+
const policyPath = (0, paths_1.getPath)('security_policy.md');
|
|
13
|
+
let existingContent = "";
|
|
14
|
+
if (fs_1.default.existsSync(policyPath)) {
|
|
15
|
+
existingContent = fs_1.default.readFileSync(policyPath, 'utf8');
|
|
16
|
+
}
|
|
17
|
+
if (action === 'clear') {
|
|
18
|
+
fs_1.default.writeFileSync(policyPath, '', 'utf8');
|
|
19
|
+
return "Security policy cleared.";
|
|
20
|
+
}
|
|
21
|
+
else if (action === 'add') {
|
|
22
|
+
const newContent = existingContent + (existingContent.endsWith('\n') || existingContent === '' ? '' : '\n') + `* ${rule}`;
|
|
23
|
+
fs_1.default.writeFileSync(policyPath, newContent, 'utf8');
|
|
24
|
+
return `Rule added to security policy: ${rule}`;
|
|
25
|
+
}
|
|
26
|
+
else if (action === 'remove') {
|
|
27
|
+
// Very basic line removal
|
|
28
|
+
const lines = existingContent.split('\n');
|
|
29
|
+
const filtered = lines.filter(l => !l.includes(rule));
|
|
30
|
+
fs_1.default.writeFileSync(policyPath, filtered.join('\n'), 'utf8');
|
|
31
|
+
return `Rule removed (if it existed).`;
|
|
32
|
+
}
|
|
33
|
+
return "Invalid action.";
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
return `Failed to update security policy: ${error.message}`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.updateSecurityPolicyToolDefinition = {
|
|
40
|
+
type: "function",
|
|
41
|
+
function: {
|
|
42
|
+
name: "update_security_policy",
|
|
43
|
+
description: "Updates the security_policy.md file to restrict your own autonomous behavior. Use this when the user explicitly forbids you from doing something (e.g. 'do not touch drive E').",
|
|
44
|
+
parameters: {
|
|
45
|
+
type: "object",
|
|
46
|
+
properties: {
|
|
47
|
+
rule: {
|
|
48
|
+
type: "string",
|
|
49
|
+
description: "The rule to add or remove.",
|
|
50
|
+
},
|
|
51
|
+
action: {
|
|
52
|
+
type: "string",
|
|
53
|
+
enum: ["add", "remove", "clear"],
|
|
54
|
+
description: "The action to perform on the policy.",
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
required: ["rule", "action"],
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.writeLocalFileToolDefinition = void 0;
|
|
7
|
+
exports.writeLocalFile = writeLocalFile;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
function writeLocalFile(filePath, content) {
|
|
11
|
+
try {
|
|
12
|
+
const absolutePath = path_1.default.resolve(filePath);
|
|
13
|
+
const dir = path_1.default.dirname(absolutePath);
|
|
14
|
+
if (!fs_1.default.existsSync(dir)) {
|
|
15
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
fs_1.default.writeFileSync(absolutePath, content, 'utf8');
|
|
18
|
+
return `Success: File written to ${absolutePath}`;
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return `Failed to write file: ${error.message}`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.writeLocalFileToolDefinition = {
|
|
25
|
+
type: "function",
|
|
26
|
+
function: {
|
|
27
|
+
name: "write_local_file",
|
|
28
|
+
description: "Writes or overwrites a local file on the user's computer with the provided content.",
|
|
29
|
+
parameters: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
filePath: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "The absolute or relative path to the file.",
|
|
35
|
+
},
|
|
36
|
+
content: {
|
|
37
|
+
type: "string",
|
|
38
|
+
description: "The string content to write to the file.",
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
required: ["filePath", "content"],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -32,6 +32,8 @@ async function getLifiQuote(fromChainId, toChainId, fromToken, toToken, amountWe
|
|
|
32
32
|
return await res.json();
|
|
33
33
|
}
|
|
34
34
|
async function getRelayQuote(fromChainId, toChainId, fromToken, toToken, amountWei, userAddress) {
|
|
35
|
+
const isTestnet = fromChainId === 11155111 || toChainId === 11155111;
|
|
36
|
+
const baseUrl = isTestnet ? "https://api.testnets.relay.link" : "https://api.relay.link";
|
|
35
37
|
const body = {
|
|
36
38
|
user: userAddress,
|
|
37
39
|
originChainId: fromChainId,
|
|
@@ -41,7 +43,7 @@ async function getRelayQuote(fromChainId, toChainId, fromToken, toToken, amountW
|
|
|
41
43
|
amount: amountWei,
|
|
42
44
|
tradeType: "EXACT_INPUT"
|
|
43
45
|
};
|
|
44
|
-
const res = await fetch(
|
|
46
|
+
const res = await fetch(`${baseUrl}/quote`, {
|
|
45
47
|
method: "POST",
|
|
46
48
|
headers: { "Content-Type": "application/json" },
|
|
47
49
|
body: JSON.stringify(body)
|
|
@@ -75,7 +77,22 @@ async function prepareBridgeToken(fromChainName, toChainName, fromToken, toToken
|
|
|
75
77
|
let txRequest = null;
|
|
76
78
|
let approvalAddress = null;
|
|
77
79
|
let expectedOutputStr = "";
|
|
78
|
-
|
|
80
|
+
let actualProvider = mode === "auto" ? "lifi" : providerName;
|
|
81
|
+
const isTestnet = fromChainId === 11155111 || toChainId === 11155111;
|
|
82
|
+
// --- SEPOLIA TESTNET MOCK ---
|
|
83
|
+
if (isTestnet) {
|
|
84
|
+
const mockGasLimit = "150000";
|
|
85
|
+
expectedOutputStr = "MOCK_TEST_AMOUNT";
|
|
86
|
+
const tx = transactionManager_1.txManager.createPendingTransaction('bridge', fromChainName, {
|
|
87
|
+
txRequest: { to: fromTokenAddress, data: "0x", value: amountWei, gasLimit: mockGasLimit },
|
|
88
|
+
needsApprove: false,
|
|
89
|
+
fromTokenAddress,
|
|
90
|
+
approvalAddress: null,
|
|
91
|
+
amountWei
|
|
92
|
+
});
|
|
93
|
+
return `TRANSACTION_PENDING: Bridge simulated via TESTNET_MOCK. Expected Output on ${toChainName}: ~${expectedOutputStr} ${toToken.toUpperCase()}. Gas est: ${mockGasLimit}. Transaction ID: ${tx.id}. Wait for user to approve.`;
|
|
94
|
+
}
|
|
95
|
+
// --- END MOCK ---
|
|
79
96
|
if (actualProvider === "lifi") {
|
|
80
97
|
const quote = await getLifiQuote(fromChainId, toChainId, fromTokenAddress, toTokenAddress, amountWei, account.address, slippagePercent / 100);
|
|
81
98
|
txRequest = quote.transactionRequest;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.checkPortfolioToolDefinition = void 0;
|
|
37
|
+
exports.checkPortfolio = checkPortfolio;
|
|
38
|
+
const viem_1 = require("viem");
|
|
39
|
+
const config_1 = require("../config");
|
|
40
|
+
const tokens_1 = require("../utils/tokens");
|
|
41
|
+
async function checkPortfolio(chainName, address) {
|
|
42
|
+
try {
|
|
43
|
+
const client = (0, config_1.getPublicClient)(chainName);
|
|
44
|
+
let targetAddress = address;
|
|
45
|
+
if (!targetAddress) {
|
|
46
|
+
const { getAddress } = await Promise.resolve().then(() => __importStar(require('../config')));
|
|
47
|
+
targetAddress = getAddress();
|
|
48
|
+
}
|
|
49
|
+
if (!targetAddress) {
|
|
50
|
+
throw new Error('Address is required but could not be resolved from private key.');
|
|
51
|
+
}
|
|
52
|
+
const tokensToScan = [
|
|
53
|
+
{ symbol: 'Native', address: '0x0000000000000000000000000000000000000000', isNative: true }
|
|
54
|
+
];
|
|
55
|
+
const chainTokens = tokens_1.TOKEN_MAP[chainName];
|
|
56
|
+
if (chainTokens) {
|
|
57
|
+
for (const [sym, addr] of Object.entries(chainTokens)) {
|
|
58
|
+
if (addr !== "0x0000000000000000000000000000000000000000") {
|
|
59
|
+
tokensToScan.push({ symbol: sym, address: addr, isNative: false });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
let report = `📊 **Portfolio for ${targetAddress} on ${chainName.toUpperCase()}**\n\n`;
|
|
64
|
+
let totalUsdValue = 0;
|
|
65
|
+
// We will do Promise.all for balances
|
|
66
|
+
const balancePromises = tokensToScan.map(async (t) => {
|
|
67
|
+
let balanceNum = 0;
|
|
68
|
+
if (t.isNative) {
|
|
69
|
+
const bal = await client.getBalance({ address: targetAddress });
|
|
70
|
+
balanceNum = parseFloat((0, viem_1.formatEther)(bal));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
try {
|
|
74
|
+
const [balWei, dec] = await Promise.all([
|
|
75
|
+
client.readContract({
|
|
76
|
+
address: t.address,
|
|
77
|
+
abi: tokens_1.ERC20_ABI,
|
|
78
|
+
functionName: 'balanceOf',
|
|
79
|
+
args: [targetAddress],
|
|
80
|
+
}),
|
|
81
|
+
client.readContract({
|
|
82
|
+
address: t.address,
|
|
83
|
+
abi: tokens_1.ERC20_ABI,
|
|
84
|
+
functionName: 'decimals',
|
|
85
|
+
})
|
|
86
|
+
]);
|
|
87
|
+
balanceNum = parseFloat((0, viem_1.formatUnits)(balWei, dec));
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
balanceNum = 0;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { ...t, balanceNum };
|
|
94
|
+
});
|
|
95
|
+
const balances = await Promise.all(balancePromises);
|
|
96
|
+
const nonZeroBalances = balances.filter(b => b.balanceNum > 0);
|
|
97
|
+
if (nonZeroBalances.length === 0) {
|
|
98
|
+
return report + `No funds found for standard tokens on this chain. Net Worth: $0.00`;
|
|
99
|
+
}
|
|
100
|
+
// Now fetch prices from Dexscreener
|
|
101
|
+
// Prepare addresses to fetch
|
|
102
|
+
const addressesToFetch = nonZeroBalances.map(b => b.isNative ? (chainTokens?.WETH || chainTokens?.WBNB) : b.address).filter(Boolean);
|
|
103
|
+
const priceMap = {};
|
|
104
|
+
if (addressesToFetch.length > 0) {
|
|
105
|
+
const url = `https://api.dexscreener.com/latest/dex/tokens/${addressesToFetch.join(',')}`;
|
|
106
|
+
try {
|
|
107
|
+
const res = await fetch(url);
|
|
108
|
+
const data = await res.json();
|
|
109
|
+
if (data.pairs) {
|
|
110
|
+
data.pairs.forEach((p) => {
|
|
111
|
+
if (!priceMap[p.baseToken.address.toLowerCase()]) {
|
|
112
|
+
priceMap[p.baseToken.address.toLowerCase()] = parseFloat(p.priceUsd);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (e) { }
|
|
118
|
+
}
|
|
119
|
+
for (const b of nonZeroBalances) {
|
|
120
|
+
const lookupAddr = (b.isNative ? (chainTokens?.WETH || chainTokens?.WBNB) : b.address)?.toLowerCase() || "";
|
|
121
|
+
const price = priceMap[lookupAddr] || 0;
|
|
122
|
+
const usdValue = b.balanceNum * price;
|
|
123
|
+
totalUsdValue += usdValue;
|
|
124
|
+
report += `- **${b.symbol}**: ${b.balanceNum.toFixed(4)} (~$${usdValue.toFixed(2)})\n`;
|
|
125
|
+
}
|
|
126
|
+
report += `\n💰 **Estimated Net Worth: $${totalUsdValue.toFixed(2)}**`;
|
|
127
|
+
return report;
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
return `Failed to check portfolio: ${error.message}`;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
exports.checkPortfolioToolDefinition = {
|
|
134
|
+
type: "function",
|
|
135
|
+
function: {
|
|
136
|
+
name: "check_portfolio",
|
|
137
|
+
description: "Scans the user's wallet for common tokens on a specific chain and calculates their total USD Net Worth (PNL proxy) using live prices.",
|
|
138
|
+
parameters: {
|
|
139
|
+
type: "object",
|
|
140
|
+
properties: {
|
|
141
|
+
chainName: {
|
|
142
|
+
type: "string",
|
|
143
|
+
enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
|
|
144
|
+
description: "The blockchain network",
|
|
145
|
+
},
|
|
146
|
+
address: {
|
|
147
|
+
type: "string",
|
|
148
|
+
description: "Optional wallet address. If omitted, uses the AI agent's own wallet.",
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
required: ["chainName"],
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkSecurityToolDefinition = void 0;
|
|
4
|
+
exports.checkTokenSecurity = checkTokenSecurity;
|
|
5
|
+
const CHAIN_IDS = {
|
|
6
|
+
ethereum: 1,
|
|
7
|
+
base: 8453,
|
|
8
|
+
bsc: 56,
|
|
9
|
+
arbitrum: 42161,
|
|
10
|
+
optimism: 10,
|
|
11
|
+
sepolia: 11155111,
|
|
12
|
+
};
|
|
13
|
+
async function checkTokenSecurity(chainName, contractAddress) {
|
|
14
|
+
try {
|
|
15
|
+
const chainId = CHAIN_IDS[chainName];
|
|
16
|
+
if (chainName === 'sepolia') {
|
|
17
|
+
return `Security check API (GoPlus) does not support Sepolia testnet. Try a mainnet token.`;
|
|
18
|
+
}
|
|
19
|
+
const url = `https://api.gopluslabs.io/api/v1/token_security/${chainId}?contract_addresses=${contractAddress}`;
|
|
20
|
+
const res = await fetch(url);
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
throw new Error(`GoPlus API Error: ${res.statusText}`);
|
|
23
|
+
}
|
|
24
|
+
const data = await res.json();
|
|
25
|
+
if (data.code !== 1 || !data.result) {
|
|
26
|
+
throw new Error(`API returned error: ${data.message || 'Unknown error'}`);
|
|
27
|
+
}
|
|
28
|
+
const tokenData = data.result[contractAddress.toLowerCase()];
|
|
29
|
+
if (!tokenData) {
|
|
30
|
+
return `Token security data not found for ${contractAddress} on ${chainName}.`;
|
|
31
|
+
}
|
|
32
|
+
let report = `Security Analysis for ${tokenData.token_name || 'Unknown'} (${tokenData.token_symbol || 'Unknown'}):\n`;
|
|
33
|
+
report += `- Is Honeypot: ${tokenData.is_honeypot === "1" ? "⚠️ YES (DANGER)" : "✅ NO"}\n`;
|
|
34
|
+
report += `- Buy Tax: ${tokenData.buy_tax ? (parseFloat(tokenData.buy_tax) * 100).toFixed(2) + '%' : 'Unknown'}\n`;
|
|
35
|
+
report += `- Sell Tax: ${tokenData.sell_tax ? (parseFloat(tokenData.sell_tax) * 100).toFixed(2) + '%' : 'Unknown'}\n`;
|
|
36
|
+
report += `- Cannot Sell All: ${tokenData.cannot_sell_all === "1" ? "⚠️ YES" : "✅ NO"}\n`;
|
|
37
|
+
report += `- Is Proxy Contract: ${tokenData.is_proxy === "1" ? "⚠️ YES (Can be upgraded)" : "✅ NO"}\n`;
|
|
38
|
+
report += `- Owner Can Change Balance: ${tokenData.owner_change_balance === "1" ? "⚠️ YES" : "✅ NO"}\n`;
|
|
39
|
+
report += `- Is Open Source: ${tokenData.is_open_source === "1" ? "✅ YES" : "⚠️ NO (Code is hidden)"}\n`;
|
|
40
|
+
return report;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
return `Failed to check token security: ${error.message}`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.checkSecurityToolDefinition = {
|
|
47
|
+
type: "function",
|
|
48
|
+
function: {
|
|
49
|
+
name: "check_token_security",
|
|
50
|
+
description: "Check a token's smart contract for honeypot, rugpull risks, and buy/sell tax using GoPlus Security API.",
|
|
51
|
+
parameters: {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
chainName: {
|
|
55
|
+
type: "string",
|
|
56
|
+
enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
|
|
57
|
+
description: "The blockchain network",
|
|
58
|
+
},
|
|
59
|
+
contractAddress: {
|
|
60
|
+
type: "string",
|
|
61
|
+
description: "The token smart contract address (0x...)",
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
required: ["chainName", "contractAddress"],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createWalletToolDefinition = void 0;
|
|
4
|
+
exports.createWallet = createWallet;
|
|
5
|
+
const accounts_1 = require("viem/accounts");
|
|
6
|
+
async function createWallet() {
|
|
7
|
+
try {
|
|
8
|
+
const mnemonic = (0, accounts_1.generateMnemonic)(accounts_1.english);
|
|
9
|
+
const account = (0, accounts_1.mnemonicToAccount)(mnemonic);
|
|
10
|
+
return `[WALLET_CREATED_SUCCESSFULLY]
|
|
11
|
+
A new EVM wallet has been generated. Please instruct the user to back up these details immediately as they will not be saved anywhere else:
|
|
12
|
+
|
|
13
|
+
Address: ${account.address}
|
|
14
|
+
Private Key: ${account.getHdKey().privateKey ? '0x' + Buffer.from(account.getHdKey().privateKey).toString('hex') : 'Unavailable'}
|
|
15
|
+
Seed Phrase: ${mnemonic}
|
|
16
|
+
|
|
17
|
+
IMPORTANT: Do not store this data in your AI memory. Only display it once.`;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return `Failed to generate wallet: ${error.message}`;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.createWalletToolDefinition = {
|
|
24
|
+
type: "function",
|
|
25
|
+
function: {
|
|
26
|
+
name: "create_wallet",
|
|
27
|
+
description: "Generates a new EVM wallet address, private key, and 12-word seed phrase.",
|
|
28
|
+
parameters: {
|
|
29
|
+
type: "object",
|
|
30
|
+
properties: {},
|
|
31
|
+
required: [],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.marketAnalysisToolDefinition = void 0;
|
|
4
|
+
exports.analyzeMarket = analyzeMarket;
|
|
5
|
+
const tokens_1 = require("../utils/tokens");
|
|
6
|
+
async function analyzeMarket(chainName, tokenAddressOrSymbol) {
|
|
7
|
+
try {
|
|
8
|
+
let tokenAddress = tokenAddressOrSymbol;
|
|
9
|
+
try {
|
|
10
|
+
tokenAddress = (0, tokens_1.resolveToken)(tokenAddressOrSymbol, chainName);
|
|
11
|
+
if (tokenAddress === "0x0000000000000000000000000000000000000000") {
|
|
12
|
+
// For native token, we should pass WETH / wrapped version to Dexscreener usually,
|
|
13
|
+
// because native token itself doesn't have a pair on most DEXes directly.
|
|
14
|
+
tokenAddress = (0, tokens_1.resolveToken)("W" + tokenAddressOrSymbol, chainName); // e.g. WETH, WBNB
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
// If it fails to resolve, assume it's already an address or let DexScreener handle it (though DexScreener needs exact address)
|
|
19
|
+
}
|
|
20
|
+
const url = `https://api.dexscreener.com/latest/dex/tokens/${tokenAddress}`;
|
|
21
|
+
const res = await fetch(url);
|
|
22
|
+
if (!res.ok) {
|
|
23
|
+
throw new Error(`DexScreener API Error: ${res.statusText}`);
|
|
24
|
+
}
|
|
25
|
+
const data = await res.json();
|
|
26
|
+
if (!data.pairs || data.pairs.length === 0) {
|
|
27
|
+
return `No market data found for token ${tokenAddressOrSymbol} on DexScreener.`;
|
|
28
|
+
}
|
|
29
|
+
// Filter pairs by chain if possible, Dexscreener chain IDs are strings like 'ethereum', 'bsc', 'base', 'arbitrum', 'optimism'
|
|
30
|
+
let pair = data.pairs.find((p) => p.chainId === chainName);
|
|
31
|
+
if (!pair) {
|
|
32
|
+
pair = data.pairs[0]; // Fallback to the most liquid pair anywhere
|
|
33
|
+
}
|
|
34
|
+
let report = `📈 **Market Analysis for ${pair.baseToken.name} (${pair.baseToken.symbol})** on ${pair.chainId.toUpperCase()}\n\n`;
|
|
35
|
+
report += `**Price:** $${pair.priceUsd}\n`;
|
|
36
|
+
report += `**Liquidity (USD):** $${Number(pair.liquidity?.usd || 0).toLocaleString()}\n`;
|
|
37
|
+
report += `**FDV:** $${Number(pair.fdv || 0).toLocaleString()}\n`;
|
|
38
|
+
report += `**24h Volume:** $${Number(pair.volume?.h24 || 0).toLocaleString()}\n\n`;
|
|
39
|
+
report += `**Price Change:**\n`;
|
|
40
|
+
report += `- 5m: ${pair.priceChange?.m5}% \n`;
|
|
41
|
+
report += `- 1h: ${pair.priceChange?.h1}% \n`;
|
|
42
|
+
report += `- 24h: ${pair.priceChange?.h24}% \n\n`;
|
|
43
|
+
report += `**DEX:** ${pair.dexId} (${pair.url})\n`;
|
|
44
|
+
return report;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
return `Failed to analyze market: ${error.message}`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.marketAnalysisToolDefinition = {
|
|
51
|
+
type: "function",
|
|
52
|
+
function: {
|
|
53
|
+
name: "analyze_market",
|
|
54
|
+
description: "Fetches live market data (Price, Liquidity, Volume, FDV, Price Change) for a token using DexScreener.",
|
|
55
|
+
parameters: {
|
|
56
|
+
type: "object",
|
|
57
|
+
properties: {
|
|
58
|
+
chainName: {
|
|
59
|
+
type: "string",
|
|
60
|
+
enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
|
|
61
|
+
description: "The blockchain network",
|
|
62
|
+
},
|
|
63
|
+
tokenAddressOrSymbol: {
|
|
64
|
+
type: "string",
|
|
65
|
+
description: "The token symbol (e.g. USDC, PEPE) or contract address to analyze.",
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
required: ["chainName", "tokenAddressOrSymbol"],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
};
|
|
@@ -32,6 +32,8 @@ async function getLifiQuote(fromChainId, toChainId, fromToken, toToken, amountWe
|
|
|
32
32
|
return await res.json();
|
|
33
33
|
}
|
|
34
34
|
async function getRelayQuote(fromChainId, toChainId, fromToken, toToken, amountWei, userAddress) {
|
|
35
|
+
const isTestnet = fromChainId === 11155111 || toChainId === 11155111;
|
|
36
|
+
const baseUrl = isTestnet ? "https://api.testnets.relay.link" : "https://api.relay.link";
|
|
35
37
|
const body = {
|
|
36
38
|
user: userAddress,
|
|
37
39
|
originChainId: fromChainId,
|
|
@@ -41,7 +43,7 @@ async function getRelayQuote(fromChainId, toChainId, fromToken, toToken, amountW
|
|
|
41
43
|
amount: amountWei,
|
|
42
44
|
tradeType: "EXACT_INPUT"
|
|
43
45
|
};
|
|
44
|
-
const res = await fetch(
|
|
46
|
+
const res = await fetch(`${baseUrl}/quote`, {
|
|
45
47
|
method: "POST",
|
|
46
48
|
headers: { "Content-Type": "application/json" },
|
|
47
49
|
body: JSON.stringify(body)
|
|
@@ -74,7 +76,22 @@ async function prepareSwapToken(chainName, fromToken, toToken, amountStr, mode =
|
|
|
74
76
|
let txRequest = null;
|
|
75
77
|
let approvalAddress = null;
|
|
76
78
|
let expectedOutputStr = "";
|
|
77
|
-
|
|
79
|
+
let actualProvider = mode === "auto" ? "lifi" : providerName;
|
|
80
|
+
const isTestnet = chainId === 11155111;
|
|
81
|
+
// --- SEPOLIA TESTNET MOCK ---
|
|
82
|
+
if (isTestnet) {
|
|
83
|
+
const mockGasLimit = "150000";
|
|
84
|
+
expectedOutputStr = "MOCK_TEST_AMOUNT";
|
|
85
|
+
const tx = transactionManager_1.txManager.createPendingTransaction('swap', chainName, {
|
|
86
|
+
txRequest: { to: fromTokenAddress, data: "0x", value: amountWei, gasLimit: mockGasLimit },
|
|
87
|
+
needsApprove: false,
|
|
88
|
+
fromTokenAddress,
|
|
89
|
+
approvalAddress: null,
|
|
90
|
+
amountWei
|
|
91
|
+
});
|
|
92
|
+
return `TRANSACTION_PENDING: Swap simulated via TESTNET_MOCK. Expected Output: ~${expectedOutputStr} ${toToken.toUpperCase()}. Gas est: ${mockGasLimit}. Transaction ID: ${tx.id}. Wait for user to approve.`;
|
|
93
|
+
}
|
|
94
|
+
// --- END MOCK ---
|
|
78
95
|
if (actualProvider === "lifi") {
|
|
79
96
|
const quote = await getLifiQuote(chainId, chainId, fromTokenAddress, toTokenAddress, amountWei, account.address, slippagePercent / 100);
|
|
80
97
|
txRequest = quote.transactionRequest;
|
|
@@ -90,7 +90,8 @@ exports.TOKEN_MAP = {
|
|
|
90
90
|
sepolia: {
|
|
91
91
|
ETH: "0x0000000000000000000000000000000000000000",
|
|
92
92
|
WETH: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14",
|
|
93
|
-
USDC: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238" // Circle Faucet Sepolia USDC
|
|
93
|
+
USDC: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", // Circle Faucet Sepolia USDC
|
|
94
|
+
USDT: "0xaA8E23Fb1079EA71e0a56F48a2aA51851D8433D0" // Common Sepolia USDT
|
|
94
95
|
}
|
|
95
96
|
};
|
|
96
97
|
function resolveToken(tokenSymbolOrAddress, chainName) {
|
package/package.json
CHANGED