@agentxv2/gateway 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +53 -0
- package/db/migrations/001_init.sql +117 -0
- package/db/migrations/002_agents.sql +20 -0
- package/db/migrations/003_a2a_results.sql +23 -0
- package/deploy/.env.deploy.example +23 -0
- package/deploy/add_swap_build.py +45 -0
- package/deploy/build_and_deploy.py +110 -0
- package/deploy/build_deploy_a2a.py +190 -0
- package/deploy/build_no_lint.py +52 -0
- package/deploy/build_on_fe.py +86 -0
- package/deploy/build_on_gw.py +98 -0
- package/deploy/build_prod.py +54 -0
- package/deploy/check_bal.py +31 -0
- package/deploy/check_code.py +47 -0
- package/deploy/check_deploy.sh +20 -0
- package/deploy/check_docs.py +16 -0
- package/deploy/check_frontend.py +42 -0
- package/deploy/check_frontend2.py +35 -0
- package/deploy/check_pks.py +34 -0
- package/deploy/check_status.py +32 -0
- package/deploy/check_v2.py +8 -0
- package/deploy/clean_install.py +28 -0
- package/deploy/deploy_a2a.py +58 -0
- package/deploy/deploy_a2a.sh +33 -0
- package/deploy/deploy_a2a_bg.sh +40 -0
- package/deploy/deploy_a2a_v2.py +151 -0
- package/deploy/deploy_a2a_v2.sh +36 -0
- package/deploy/deploy_bin.py +64 -0
- package/deploy/deploy_config.py +34 -0
- package/deploy/deploy_final.py +42 -0
- package/deploy/deploy_frontend.py +108 -0
- package/deploy/deploy_mcp.py +108 -0
- package/deploy/deploy_now.py +49 -0
- package/deploy/deploy_push_all.py +128 -0
- package/deploy/deploy_script.py +64 -0
- package/deploy/deploy_sep_a2a.sh +18 -0
- package/deploy/dev_mode.py +51 -0
- package/deploy/diag.py +12 -0
- package/deploy/diff_test.py +22 -0
- package/deploy/final_check.py +33 -0
- package/deploy/final_deploy.py +50 -0
- package/deploy/final_fix.py +54 -0
- package/deploy/final_push.py +114 -0
- package/deploy/find_frontend.py +38 -0
- package/deploy/fix_a2a.py +43 -0
- package/deploy/fix_and_build.py +77 -0
- package/deploy/fix_and_verify.py +56 -0
- package/deploy/fix_build.py +51 -0
- package/deploy/forge_deploy.py +67 -0
- package/deploy/full_push.py +101 -0
- package/deploy/full_push2.py +106 -0
- package/deploy/go_prod.py +59 -0
- package/deploy/gw_deploy.py +133 -0
- package/deploy/gw_final.py +206 -0
- package/deploy/gw_restart.py +28 -0
- package/deploy/gw_status.py +18 -0
- package/deploy/inline_build.py +99 -0
- package/deploy/one_shot.py +84 -0
- package/deploy/ox_deploy.py +32 -0
- package/deploy/pg_setup.py +36 -0
- package/deploy/prod_deploy.py +126 -0
- package/deploy/prod_final.py +140 -0
- package/deploy/prod_go.py +61 -0
- package/deploy/prod_one_shot.py +139 -0
- package/deploy/prod_setup.sh +62 -0
- package/deploy/prod_v2.py +91 -0
- package/deploy/prod_v3.py +103 -0
- package/deploy/push_docs.py +93 -0
- package/deploy/push_mcp.py +66 -0
- package/deploy/push_mcp_doc.py +28 -0
- package/deploy/push_v063.py +80 -0
- package/deploy/push_v2.py +71 -0
- package/deploy/qcheck.py +13 -0
- package/deploy/qcheck2.py +18 -0
- package/deploy/quick_check.py +8 -0
- package/deploy/rebuild.py +47 -0
- package/deploy/rebuild_frontend.py +44 -0
- package/deploy/restart.py +28 -0
- package/deploy/restore_frontend.py +42 -0
- package/deploy/restore_frontend2.py +41 -0
- package/deploy/restore_gateway.py +34 -0
- package/deploy/restore_quick.py +33 -0
- package/deploy/run_bash.py +19 -0
- package/deploy/run_sep.py +20 -0
- package/deploy/sdk_upgrade.py +50 -0
- package/deploy/server_prepare.py +41 -0
- package/deploy/server_setup.py +41 -0
- package/deploy/ssh_check.py +14 -0
- package/deploy/ssh_push.py +24 -0
- package/deploy/start_deploy.py +35 -0
- package/deploy/start_frontend.py +12 -0
- package/deploy/start_v2.py +29 -0
- package/deploy/stat.py +18 -0
- package/deploy/tail_log.py +11 -0
- package/deploy/update_env.py +32 -0
- package/deploy/upload_and_start.py +107 -0
- package/deploy/verify_a2a.py +13 -0
- package/deploy/verify_mcp.py +37 -0
- package/deploy/verify_sdk.py +21 -0
- package/dist/config.d.ts +26 -0
- package/dist/config.js +41 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +149 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/crypto.d.ts +2 -0
- package/dist/lib/crypto.js +43 -0
- package/dist/lib/crypto.js.map +1 -0
- package/dist/lib/db.d.ts +3 -0
- package/dist/lib/db.js +26 -0
- package/dist/lib/db.js.map +1 -0
- package/dist/lib/ethers-utils.d.ts +60 -0
- package/dist/lib/ethers-utils.js +186 -0
- package/dist/lib/ethers-utils.js.map +1 -0
- package/dist/middleware/adminAuth.d.ts +2 -0
- package/dist/middleware/adminAuth.js +31 -0
- package/dist/middleware/adminAuth.js.map +1 -0
- package/dist/middleware/auth.d.ts +22 -0
- package/dist/middleware/auth.js +192 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +36 -0
- package/dist/middleware/error-handler.js +100 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/rate-limiter.d.ts +5 -0
- package/dist/middleware/rate-limiter.js +107 -0
- package/dist/middleware/rate-limiter.js.map +1 -0
- package/dist/routes/a2a.d.ts +2 -0
- package/dist/routes/a2a.js +84 -0
- package/dist/routes/a2a.js.map +1 -0
- package/dist/routes/admin.d.ts +2 -0
- package/dist/routes/admin.js +180 -0
- package/dist/routes/admin.js.map +1 -0
- package/dist/routes/agents.d.ts +2 -0
- package/dist/routes/agents.js +43 -0
- package/dist/routes/agents.js.map +1 -0
- package/dist/routes/chat.d.ts +2 -0
- package/dist/routes/chat.js +183 -0
- package/dist/routes/chat.js.map +1 -0
- package/dist/routes/history.d.ts +2 -0
- package/dist/routes/history.js +92 -0
- package/dist/routes/history.js.map +1 -0
- package/dist/routes/mcp.d.ts +13 -0
- package/dist/routes/mcp.js +513 -0
- package/dist/routes/mcp.js.map +1 -0
- package/dist/routes/tenant.d.ts +2 -0
- package/dist/routes/tenant.js +154 -0
- package/dist/routes/tenant.js.map +1 -0
- package/dist/services/a2a-worker.d.ts +7 -0
- package/dist/services/a2a-worker.js +406 -0
- package/dist/services/a2a-worker.js.map +1 -0
- package/dist/services/agent-indexer.d.ts +4 -0
- package/dist/services/agent-indexer.js +124 -0
- package/dist/services/agent-indexer.js.map +1 -0
- package/e2e_wallet.js +175 -0
- package/ecosystem.config.js +17 -0
- package/package.json +33 -0
- package/src/config.ts +47 -0
- package/src/index.ts +143 -0
- package/src/lib/crypto.ts +44 -0
- package/src/lib/db.ts +25 -0
- package/src/lib/ethers-utils.ts +229 -0
- package/src/middleware/adminAuth.ts +34 -0
- package/src/middleware/auth.ts +244 -0
- package/src/middleware/error-handler.ts +109 -0
- package/src/middleware/rate-limiter.ts +115 -0
- package/src/routes/a2a.ts +97 -0
- package/src/routes/admin.ts +219 -0
- package/src/routes/agents.ts +52 -0
- package/src/routes/chat.ts +219 -0
- package/src/routes/history.ts +114 -0
- package/src/routes/mcp.ts +567 -0
- package/src/routes/tenant.ts +196 -0
- package/src/services/a2a-worker.ts +486 -0
- package/src/services/agent-indexer.ts +127 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// AgentX Gateway — Agent Indexer (reads chain → stores in PostgreSQL)
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Syncs agent metadata from IdentityRegistry (ERC-721) into the agents table.
|
|
6
|
+
// Handles: IPFS CIDs, base64 data URIs, and malformed tokenURIs.
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.syncAgents = syncAgents;
|
|
10
|
+
const ethers_1 = require("ethers");
|
|
11
|
+
const db_1 = require("../lib/db");
|
|
12
|
+
const config_1 = require("../config");
|
|
13
|
+
const IDENTITY_ABI = [
|
|
14
|
+
'function tokenURI(uint256 tokenId) view returns (string)',
|
|
15
|
+
'function ownerOf(uint256 tokenId) view returns (address)',
|
|
16
|
+
];
|
|
17
|
+
// Parse base64 data URI tokenURI → JSON metadata object
|
|
18
|
+
function parseTokenURIToJSON(tokenURI) {
|
|
19
|
+
if (!tokenURI)
|
|
20
|
+
return null;
|
|
21
|
+
if (tokenURI.startsWith('ipfs://'))
|
|
22
|
+
return null;
|
|
23
|
+
const match = tokenURI.match(/^data:application\/json;base64,(.+)$/i);
|
|
24
|
+
if (!match)
|
|
25
|
+
return null;
|
|
26
|
+
// Clean up malformed base64: trim everything after the last "==" padding
|
|
27
|
+
let b64 = match[1];
|
|
28
|
+
const lastDoubleEq = b64.lastIndexOf('==');
|
|
29
|
+
if (lastDoubleEq > 0 && lastDoubleEq < b64.length - 2) {
|
|
30
|
+
b64 = b64.substring(0, lastDoubleEq + 2);
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const decoded = Buffer.from(b64, 'base64').toString('utf-8');
|
|
34
|
+
// Try JSON parse first
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(decoded);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Unterminated JSON (contract bug): append missing closing quotes/braces
|
|
40
|
+
let fixed = decoded;
|
|
41
|
+
// Count unclosed quotes
|
|
42
|
+
const quoteCount = (fixed.match(/"/g) || []).length;
|
|
43
|
+
if (quoteCount % 2 !== 0)
|
|
44
|
+
fixed += '"';
|
|
45
|
+
// Count unclosed braces
|
|
46
|
+
const openBraces = (fixed.match(/\{/g) || []).length;
|
|
47
|
+
const closeBraces = (fixed.match(/\}/g) || []).length;
|
|
48
|
+
for (let i = closeBraces; i < openBraces; i++)
|
|
49
|
+
fixed += '}';
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(fixed);
|
|
52
|
+
}
|
|
53
|
+
catch { /* ok */ }
|
|
54
|
+
}
|
|
55
|
+
// Regex fallback
|
|
56
|
+
const nameM = decoded.match(/"name"\s*:\s*"([^"]*)/);
|
|
57
|
+
if (nameM)
|
|
58
|
+
return { name: nameM[1] };
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Extract CID from IPFS tokenURI
|
|
66
|
+
function extractCID(tokenURI) {
|
|
67
|
+
if (!tokenURI)
|
|
68
|
+
return null;
|
|
69
|
+
if (tokenURI.startsWith('ipfs://'))
|
|
70
|
+
return tokenURI.replace('ipfs://', '').split('/')[0];
|
|
71
|
+
const match = tokenURI.match(/\/ipfs\/([^/?#]+)/);
|
|
72
|
+
if (match)
|
|
73
|
+
return match[1];
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
async function syncAgents() {
|
|
77
|
+
const provider = new ethers_1.ethers.JsonRpcProvider(config_1.config.rpcUrlOxaChain);
|
|
78
|
+
const contract = new ethers_1.ethers.Contract(config_1.config.identityRegistryOxaChain, IDENTITY_ABI, provider);
|
|
79
|
+
const pool = (0, db_1.getPool)();
|
|
80
|
+
let synced = 0;
|
|
81
|
+
let consecutiveMisses = 0;
|
|
82
|
+
const MAX_MISSES = 8;
|
|
83
|
+
const BATCH_SIZE = 10;
|
|
84
|
+
for (let batchStart = 1; consecutiveMisses < MAX_MISSES; batchStart += BATCH_SIZE) {
|
|
85
|
+
const batchIds = [];
|
|
86
|
+
for (let i = batchStart; i < batchStart + BATCH_SIZE && consecutiveMisses < MAX_MISSES; i++) {
|
|
87
|
+
batchIds.push(i);
|
|
88
|
+
}
|
|
89
|
+
const results = await Promise.allSettled(batchIds.map(async (id) => {
|
|
90
|
+
const [tokenURI, owner] = await Promise.all([
|
|
91
|
+
contract.tokenURI(id).catch(() => null),
|
|
92
|
+
contract.ownerOf(id).catch(() => null),
|
|
93
|
+
]);
|
|
94
|
+
if (!tokenURI) {
|
|
95
|
+
consecutiveMisses++;
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
consecutiveMisses = 0;
|
|
99
|
+
const parsed = parseTokenURIToJSON(tokenURI);
|
|
100
|
+
const name = parsed?.name || `Agent ${id}`;
|
|
101
|
+
const description = parsed?.description || '';
|
|
102
|
+
const tags = Array.isArray(parsed?.tags) ? parsed.tags.map(String) : [];
|
|
103
|
+
const capabilities = Array.isArray(parsed?.capabilities) ? parsed.capabilities.map(String) : [];
|
|
104
|
+
await pool.query(`INSERT INTO agents (id, owner, name, description, tags, capabilities, token_uri, metadata_json, synced_at, updated_at)
|
|
105
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,NOW(),NOW())
|
|
106
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
107
|
+
owner = EXCLUDED.owner,
|
|
108
|
+
name = EXCLUDED.name,
|
|
109
|
+
description = EXCLUDED.description,
|
|
110
|
+
tags = EXCLUDED.tags,
|
|
111
|
+
capabilities = EXCLUDED.capabilities,
|
|
112
|
+
token_uri = EXCLUDED.token_uri,
|
|
113
|
+
metadata_json = EXCLUDED.metadata_json,
|
|
114
|
+
synced_at = NOW(),
|
|
115
|
+
updated_at = NOW()`, [id, owner || '', name, description, tags, capabilities, tokenURI, JSON.stringify(parsed || {})]);
|
|
116
|
+
return { id, name, owner };
|
|
117
|
+
}));
|
|
118
|
+
synced += results.filter(r => r.status === 'fulfilled' && r.value !== null).length;
|
|
119
|
+
if (consecutiveMisses >= MAX_MISSES)
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
return { synced, total: synced };
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=agent-indexer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-indexer.js","sourceRoot":"","sources":["../../src/services/agent-indexer.ts"],"names":[],"mappings":"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"}
|
package/e2e_wallet.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
const { ethers } = require('ethers');
|
|
2
|
+
|
|
3
|
+
const RPC_URL = 'http://43.156.99.215:18545';
|
|
4
|
+
const PK = '0x5a74fd11c022e5e1fdc099e9971ebcdbad8b813f91fa99e6861bc0bcac99e094';
|
|
5
|
+
const GW = 'http://localhost:3090';
|
|
6
|
+
|
|
7
|
+
const contracts = {
|
|
8
|
+
identity: '0xbf5F9db266c8c97E3334466C88597Eb758AfE212',
|
|
9
|
+
sub: '0x019AC9d945467478Dd371CDbD70cb2f325800E6B',
|
|
10
|
+
rep: '0x6a18C2664E1b42063860d864b6448b824d7B843F',
|
|
11
|
+
cfg: '0x07280674ccc2898Fd038A9e3C22005CA83ffD2F8',
|
|
12
|
+
ep: '0xB361d04F49000013FC131D3C59C41c8486C64f8c',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const ID_ABI = [
|
|
16
|
+
'function register(string tokenURI) payable returns (uint256)',
|
|
17
|
+
'function agentExists(uint256) view returns (bool)',
|
|
18
|
+
'function getCurrentAgentId() view returns (uint256)',
|
|
19
|
+
'function getAgentsByOwner(address) view returns (uint256[])',
|
|
20
|
+
'function tokenURI(uint256) view returns (string)',
|
|
21
|
+
];
|
|
22
|
+
const CFG_ABI = [
|
|
23
|
+
'function setConfig(uint256,string,string,string) returns (bool)',
|
|
24
|
+
'function getAgentConfigs(uint256) view returns (tuple(uint256,uint256,string,string,string,string,bool,uint256,uint256,address)[])',
|
|
25
|
+
];
|
|
26
|
+
const REP_ABI = [
|
|
27
|
+
'function setFeedback(uint256,uint8) returns (bool)',
|
|
28
|
+
'function getReputationSummary(uint256,address[],bytes32,bytes32) view returns (uint64,uint8)',
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
let results = [];
|
|
32
|
+
let p = 0, f = 0;
|
|
33
|
+
|
|
34
|
+
function log(t, s, d) {
|
|
35
|
+
const icon = s === 'PASS' ? '✅' : s === 'SKIP' ? '⚠️' : '❌';
|
|
36
|
+
if (s === 'PASS') p++; else if (s === 'FAIL') f++;
|
|
37
|
+
results.push({ test: t, status: s, detail: d });
|
|
38
|
+
console.log(icon + ' [' + s + '] ' + t + (d ? ': ' + d.substring(0, 100) : ''));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function mcp(name, args) {
|
|
42
|
+
const r = await fetch(GW + '/mcp', {
|
|
43
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
44
|
+
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name, arguments: { chain: 'oxachain', ...args } } }),
|
|
45
|
+
});
|
|
46
|
+
return r.json();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function main() {
|
|
50
|
+
console.log('=== AgentX E2E Wallet Injection Test ===\n');
|
|
51
|
+
const provider = new ethers.JsonRpcProvider(RPC_URL);
|
|
52
|
+
const wallet = new ethers.Wallet(PK, provider);
|
|
53
|
+
const bal = await provider.getBalance(wallet.address);
|
|
54
|
+
console.log('Wallet: ' + wallet.address + '\nBalance: ' + ethers.formatEther(bal) + ' T0x\n');
|
|
55
|
+
|
|
56
|
+
const id = new ethers.Contract(contracts.identity, ID_ABI, wallet);
|
|
57
|
+
const cfg = new ethers.Contract(contracts.cfg, CFG_ABI, wallet);
|
|
58
|
+
const rep = new ethers.Contract(contracts.rep, REP_ABI, wallet);
|
|
59
|
+
|
|
60
|
+
let currentId = Number(await id.getCurrentAgentId());
|
|
61
|
+
let agentIds = [];
|
|
62
|
+
console.log('Current total agents: ' + currentId + '\n');
|
|
63
|
+
|
|
64
|
+
// TEST 1: Register 3 Agents
|
|
65
|
+
console.log('--- [1] Register 3 Agents ---');
|
|
66
|
+
const names = ['E2E-Alpha', 'E2E-Beta', 'E2E-Gamma'];
|
|
67
|
+
for (let i = 0; i < 3; i++) {
|
|
68
|
+
try {
|
|
69
|
+
const tx = await id.register('ipfs://QmE2E' + names[i], { gasLimit: 300000 });
|
|
70
|
+
const r = await tx.wait();
|
|
71
|
+
agentIds.push(currentId + i + 1);
|
|
72
|
+
log('Register ' + names[i], 'PASS', 'agentId=' + agentIds[i] + ' gas=' + r.gasUsed);
|
|
73
|
+
} catch (e) { log('Register ' + names[i], 'FAIL', e.reason || e.message?.substring(0, 80)); }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// TEST 2: Verify Agents
|
|
77
|
+
console.log('\n--- [2] Verify Agents ---');
|
|
78
|
+
for (const aid of agentIds) {
|
|
79
|
+
try {
|
|
80
|
+
const ex = await id.agentExists(aid);
|
|
81
|
+
const uri = await id.tokenURI(aid);
|
|
82
|
+
log('Agent ' + aid + ' exists', 'PASS', 'exists=' + ex + ' uri=' + uri);
|
|
83
|
+
} catch (e) { log('Agent ' + aid, 'FAIL', e.message?.substring(0, 80)); }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// TEST 3: List owned
|
|
87
|
+
console.log('\n--- [3] List Owned Agents ---');
|
|
88
|
+
try {
|
|
89
|
+
const owned = await id.getAgentsByOwner(wallet.address);
|
|
90
|
+
log('getAgentsByOwner', 'PASS', 'ids=[' + owned.map(n => Number(n)).join(',') + ']');
|
|
91
|
+
} catch (e) { log('getAgentsByOwner', 'FAIL', e.message?.substring(0, 80)); }
|
|
92
|
+
|
|
93
|
+
// TEST 4: Configure
|
|
94
|
+
console.log('\n--- [4] Configure Agents ---');
|
|
95
|
+
for (let i = 0; i < agentIds.length; i++) {
|
|
96
|
+
try {
|
|
97
|
+
const tx = await cfg.setConfig(agentIds[i], 'greeting', 'Hello from ' + names[i] + '!', 'string', { gasLimit: 200000 });
|
|
98
|
+
await tx.wait();
|
|
99
|
+
log('Config Agent ' + agentIds[i], 'PASS', 'greeting set');
|
|
100
|
+
} catch (e) { log('Config Agent ' + agentIds[i], 'FAIL', e.reason || e.message?.substring(0, 80)); }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// TEST 5: Rate
|
|
104
|
+
console.log('\n--- [5] Rate Agents ---');
|
|
105
|
+
const scores = [5, 4, 5];
|
|
106
|
+
for (let i = 0; i < agentIds.length; i++) {
|
|
107
|
+
try {
|
|
108
|
+
const tx = await rep.setFeedback(agentIds[i], scores[i], { gasLimit: 200000 });
|
|
109
|
+
await tx.wait();
|
|
110
|
+
log('Rate Agent ' + agentIds[i], 'PASS', 'score=' + scores[i]);
|
|
111
|
+
} catch (e) { log('Rate Agent ' + agentIds[i], 'FAIL', e.reason || e.message?.substring(0, 80)); }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// TEST 6: MCP Integration
|
|
115
|
+
console.log('\n--- [6] MCP Integration ---');
|
|
116
|
+
try {
|
|
117
|
+
const r = await mcp('agentx_identity_list', { ownerAddress: wallet.address });
|
|
118
|
+
const t = JSON.parse(r?.result?.content?.[0]?.text || '{}');
|
|
119
|
+
log('MCP identity_list', 'PASS', 'found ' + (t.agentIds?.length || 0) + ' agents');
|
|
120
|
+
} catch (e) { log('MCP identity_list', 'FAIL', e.message?.substring(0, 80)); }
|
|
121
|
+
|
|
122
|
+
for (const aid of agentIds) {
|
|
123
|
+
try {
|
|
124
|
+
const r = await mcp('agentx_identity_exists', { agentId: aid });
|
|
125
|
+
const t = JSON.parse(r?.result?.content?.[0]?.text || '{}');
|
|
126
|
+
log('MCP exists(' + aid + ')', t.exists ? 'PASS' : 'FAIL', 'exists=' + t.exists);
|
|
127
|
+
} catch (e) { log('MCP exists(' + aid + ')', 'FAIL', e.message?.substring(0, 80)); }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const aid of agentIds) {
|
|
131
|
+
try {
|
|
132
|
+
const r = await mcp('agentx_reputation_get', { agentId: aid });
|
|
133
|
+
const t = JSON.parse(r?.result?.content?.[0]?.text || '{}');
|
|
134
|
+
log('MCP reputation(' + aid + ')', 'PASS', 'avg=' + t.averageScore + ' count=' + t.reviewCount);
|
|
135
|
+
} catch (e) { log('MCP reputation(' + aid + ')', 'FAIL', e.message?.substring(0, 80)); }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// TEST 7: Subscription
|
|
139
|
+
console.log('\n--- [7] Subscription ---');
|
|
140
|
+
try {
|
|
141
|
+
const r = await mcp('agentx_subscription_check', { subscriberAddress: wallet.address, agentId: agentIds[0] });
|
|
142
|
+
const t = JSON.parse(r?.result?.content?.[0]?.text || '{}');
|
|
143
|
+
log('MCP sub_check', 'PASS', 'active=' + t.active);
|
|
144
|
+
} catch (e) { log('MCP sub_check', 'FAIL', e.message?.substring(0, 80)); }
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const r = await mcp('agentx_subscription_plans', { planId: 1 });
|
|
148
|
+
const t = JSON.parse(r?.result?.content?.[0]?.text || '{}');
|
|
149
|
+
log('MCP plans', t.note ? 'SKIP' : 'PASS', t.note || JSON.stringify(t).substring(0, 60));
|
|
150
|
+
} catch (e) { log('MCP plans', 'FAIL', e.message?.substring(0, 80)); }
|
|
151
|
+
|
|
152
|
+
// TEST 8: Auth
|
|
153
|
+
console.log('\n--- [8] Gateway Auth ---');
|
|
154
|
+
try {
|
|
155
|
+
const cRes = await fetch(GW + '/api/v1/auth/challenge?address=' + wallet.address);
|
|
156
|
+
const c = await cRes.json();
|
|
157
|
+
if (c.challenge) {
|
|
158
|
+
log('Auth challenge', 'PASS');
|
|
159
|
+
const sig = await wallet.signMessage(c.challenge);
|
|
160
|
+
const vRes = await fetch(GW + '/api/v1/auth/verify', {
|
|
161
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
162
|
+
body: JSON.stringify({ address: wallet.address, signature: sig }),
|
|
163
|
+
});
|
|
164
|
+
const v = await vRes.json();
|
|
165
|
+
log('Auth verify', v.token ? 'PASS' : 'FAIL', v.token ? 'JWT issued' : JSON.stringify(v).substring(0, 60));
|
|
166
|
+
} else { log('Auth challenge', 'FAIL', JSON.stringify(c).substring(0, 80)); }
|
|
167
|
+
} catch (e) { log('Auth', 'FAIL', e.message?.substring(0, 80)); }
|
|
168
|
+
|
|
169
|
+
// SUMMARY
|
|
170
|
+
const fb = await provider.getBalance(wallet.address);
|
|
171
|
+
console.log('\n=== SUMMARY: ' + p + ' PASS, ' + f + ' FAIL, ' + (results.length - p - f) + ' SKIP ===');
|
|
172
|
+
console.log('Gas used: ' + ethers.formatEther(bal - fb) + ' T0x');
|
|
173
|
+
console.log('Final balance: ' + ethers.formatEther(fb) + ' T0x');
|
|
174
|
+
}
|
|
175
|
+
main().catch(e => { console.error('FATAL:', e); process.exit(1); });
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
apps: [{
|
|
3
|
+
name: 'agentx-gateway',
|
|
4
|
+
script: 'dist/index.js',
|
|
5
|
+
instances: 2,
|
|
6
|
+
exec_mode: 'cluster',
|
|
7
|
+
env: {
|
|
8
|
+
NODE_ENV: 'production',
|
|
9
|
+
PORT: 3090,
|
|
10
|
+
},
|
|
11
|
+
error_file: './logs/err.log',
|
|
12
|
+
out_file: './logs/out.log',
|
|
13
|
+
merge_logs: true,
|
|
14
|
+
max_memory_restart: '512M',
|
|
15
|
+
node_args: '--max-old-space-size=512',
|
|
16
|
+
}],
|
|
17
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentxv2/gateway",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "AgentX Gateway — Multi-tenant LLM proxy with wallet auth, rate limiting, and API key management",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"start": "node dist/index.js",
|
|
9
|
+
"dev": "tsx watch src/index.ts",
|
|
10
|
+
"typecheck": "tsc --noEmit"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"cors": "^2.8.5",
|
|
14
|
+
"dotenv": "^16.4.5",
|
|
15
|
+
"ethers": "^6.13.0",
|
|
16
|
+
"express": "^4.21.0",
|
|
17
|
+
"express-rate-limit": "^7.4.0",
|
|
18
|
+
"helmet": "^8.0.0",
|
|
19
|
+
"jsonwebtoken": "^9.0.2",
|
|
20
|
+
"pg": "^8.13.0",
|
|
21
|
+
"ioredis": "^5.4.1"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/cors": "^2.8.17",
|
|
25
|
+
"@types/express": "^4.17.21",
|
|
26
|
+
"@types/jsonwebtoken": "^9.0.7",
|
|
27
|
+
"@types/node": "^22.0.0",
|
|
28
|
+
"@types/pg": "^8.11.10",
|
|
29
|
+
"tsx": "^4.19.0",
|
|
30
|
+
"typescript": "^5.5.0"
|
|
31
|
+
},
|
|
32
|
+
"license": "MIT"
|
|
33
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// AgentX Gateway — Configuration
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
import dotenv from 'dotenv'
|
|
6
|
+
dotenv.config()
|
|
7
|
+
|
|
8
|
+
export const config = {
|
|
9
|
+
port: parseInt(process.env.PORT || '3090', 10),
|
|
10
|
+
nodeEnv: process.env.NODE_ENV || 'development',
|
|
11
|
+
|
|
12
|
+
databaseUrl: process.env.DATABASE_URL || 'postgresql://localhost:5432/agentx_gateway',
|
|
13
|
+
redisUrl: process.env.REDIS_URL || 'redis://localhost:6379',
|
|
14
|
+
|
|
15
|
+
jwtSecret: process.env.JWT_SECRET || 'dev-secret-change-me',
|
|
16
|
+
masterEncryptionKey: process.env.MASTER_ENCRYPTION_KEY || '',
|
|
17
|
+
|
|
18
|
+
sessionTtlSec: parseInt(process.env.SESSION_TTL_SEC || '86400', 10),
|
|
19
|
+
|
|
20
|
+
// MCP / On-chain read — Dual-chain
|
|
21
|
+
// Sepolia
|
|
22
|
+
rpcUrl: process.env.RPC_URL || 'https://ethereum-sepolia-rpc.publicnode.com',
|
|
23
|
+
rpcUrlOxaChain: process.env.RPC_URL_OXACHAIN || 'https://rpc-oxa.0xainet.top',
|
|
24
|
+
chainId: parseInt(process.env.CHAIN_ID || '11155111', 10),
|
|
25
|
+
chainIdOxaChain: parseInt(process.env.CHAIN_ID_OXACHAIN || '19505', 10),
|
|
26
|
+
|
|
27
|
+
identityRegistry: process.env.IDENTITY_REGISTRY || '0xe94ad380d3F8d08a7590eda0C84f354a93F96e5F',
|
|
28
|
+
identityRegistryOxaChain: process.env.IDENTITY_REGISTRY_OXACHAIN || '0xbf5F9db266c8c97E3334466C88597Eb758AfE212',
|
|
29
|
+
|
|
30
|
+
subscriptionManager: process.env.SUBSCRIPTION_MANAGER || '0xC15fE80b9d800abb72121F353a6ae6d6E9077E63',
|
|
31
|
+
subscriptionManagerOxaChain: process.env.SUBSCRIPTION_MANAGER_OXACHAIN || '0x019AC9d945467478Dd371CDbD70cb2f325800E6B',
|
|
32
|
+
|
|
33
|
+
a2aProtocol: process.env.A2A_PROTOCOL || '0x309C7447d89f3087A9924BB686d88df020F7e9cB',
|
|
34
|
+
a2aProtocolOxaChain: process.env.A2A_PROTOCOL_OXACHAIN || '0x7F42a7dC4A0F3C107664C3750bE1B5B6fa6BEb86',
|
|
35
|
+
|
|
36
|
+
reputationRegistry: process.env.REPUTATION_REGISTRY || '0xeb6B410ea71b8d9dA0c96f6A91d35027CE143DC9',
|
|
37
|
+
reputationRegistryOxaChain: process.env.REPUTATION_REGISTRY_OXACHAIN || '0x6a18C2664E1b42063860d864b6448b824d7B843F',
|
|
38
|
+
|
|
39
|
+
configurationRegistry: process.env.CONFIGURATION_REGISTRY || '0x68DcE00e4C9077c94BC68016cD14B09557faEA6c',
|
|
40
|
+
configurationRegistryOxaChain: process.env.CONFIGURATION_REGISTRY_OXACHAIN || '0x07280674ccc2898Fd038A9e3C22005CA83ffD2F8',
|
|
41
|
+
|
|
42
|
+
multiEndpoint: process.env.MULTI_ENDPOINT || '0xEB5e866f186d4B73F97aa0d70B86f2C6e2e21Cb7',
|
|
43
|
+
multiEndpointOxaChain: process.env.MULTI_ENDPOINT_OXACHAIN || '0xB361d04F49000013FC131D3C59C41c8486C64f8c',
|
|
44
|
+
|
|
45
|
+
// A2A Worker wallet (for creating sub-tasks on-chain during multi-agent orchestration)
|
|
46
|
+
a2aWorkerPrivateKey: process.env.A2A_WORKER_PRIVATE_KEY || '',
|
|
47
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// AgentX Gateway — Entry Point
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
import express from 'express'
|
|
6
|
+
import cors from 'cors'
|
|
7
|
+
import helmet from 'helmet'
|
|
8
|
+
import rateLimit from 'express-rate-limit'
|
|
9
|
+
import { config } from './config'
|
|
10
|
+
import { getChallenge, verifyChallenge, authMiddleware } from './middleware/auth'
|
|
11
|
+
import { tenantRateLimiter } from './middleware/rate-limiter'
|
|
12
|
+
import { globalErrorHandler } from './middleware/error-handler'
|
|
13
|
+
import chatRouter from './routes/chat'
|
|
14
|
+
import tenantRouter from './routes/tenant'
|
|
15
|
+
import historyRouter from './routes/history'
|
|
16
|
+
import mcpRouter from './routes/mcp'
|
|
17
|
+
import agentsRouter from './routes/agents'
|
|
18
|
+
import a2aRouter from './routes/a2a'
|
|
19
|
+
import adminRouter from './routes/admin'
|
|
20
|
+
|
|
21
|
+
const app = express()
|
|
22
|
+
|
|
23
|
+
// ── Security ──────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
app.use(helmet({
|
|
26
|
+
contentSecurityPolicy: false,
|
|
27
|
+
crossOriginEmbedderPolicy: false,
|
|
28
|
+
}))
|
|
29
|
+
app.use(cors({
|
|
30
|
+
origin: process.env.CORS_ORIGIN || '*',
|
|
31
|
+
credentials: true,
|
|
32
|
+
}))
|
|
33
|
+
|
|
34
|
+
// ── Global rate limit ─────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
app.use(rateLimit({
|
|
37
|
+
windowMs: 60_000,
|
|
38
|
+
max: 1000,
|
|
39
|
+
standardHeaders: true,
|
|
40
|
+
legacyHeaders: false,
|
|
41
|
+
message: { error: 'Too many requests, please try again later.' },
|
|
42
|
+
}))
|
|
43
|
+
|
|
44
|
+
// ── Body parsing ──────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
app.use(express.json({ limit: '1mb' }))
|
|
47
|
+
|
|
48
|
+
// Handle malformed JSON (return 400 instead of 500)
|
|
49
|
+
app.use((err: any, _req: express.Request, res: express.Response, next: express.NextFunction) => {
|
|
50
|
+
if (err && err.type === 'entity.parse.failed') {
|
|
51
|
+
return res.status(400).json({ error: 'Invalid JSON in request body' })
|
|
52
|
+
}
|
|
53
|
+
next(err)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// ── Health check ──────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
app.get('/api/v1/health', async (_req, res) => {
|
|
59
|
+
res.json({ status: 'ok', time: new Date().toISOString() })
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// ── MCP endpoint (public JSON-RPC 2.0) ────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
app.use('/mcp', mcpRouter)
|
|
65
|
+
|
|
66
|
+
// ── Admin API (protected by admin key, not wallet auth) ──────────────────
|
|
67
|
+
|
|
68
|
+
app.use('/api/v1/admin', adminRouter)
|
|
69
|
+
|
|
70
|
+
// ── Auth routes (public) ──────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
app.get('/api/v1/auth/challenge', getChallenge)
|
|
73
|
+
app.post('/api/v1/auth/verify', verifyChallenge)
|
|
74
|
+
|
|
75
|
+
// ── Agents API (public, no auth needed) ────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
app.use('/api/v1/agents', agentsRouter)
|
|
78
|
+
|
|
79
|
+
// ── A2A Task Results API (public, SDK daemon queries this) ────────────────
|
|
80
|
+
|
|
81
|
+
app.use('/api/v1/a2a', a2aRouter)
|
|
82
|
+
|
|
83
|
+
// Agent sync (public, for cron)
|
|
84
|
+
app.post('/api/v1/agents-sync', async (_req, res, next) => {
|
|
85
|
+
try {
|
|
86
|
+
const { syncAgents } = await import('./services/agent-indexer')
|
|
87
|
+
const result = await syncAgents()
|
|
88
|
+
res.json({ success: true, ...result })
|
|
89
|
+
} catch (err: any) {
|
|
90
|
+
next(err)
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// ── Protected routes (auth + rate-limit only on known paths) ─────────────
|
|
95
|
+
|
|
96
|
+
// Known protected API path prefixes (anything else under /api/v1 returns 404)
|
|
97
|
+
const PROTECTED_PREFIXES = ['/chat/completions', '/chat/history', '/tenant/']
|
|
98
|
+
|
|
99
|
+
app.use('/api/v1', (req, _res, next) => {
|
|
100
|
+
if (PROTECTED_PREFIXES.some(p => req.path.startsWith(p))) {
|
|
101
|
+
next()
|
|
102
|
+
} else {
|
|
103
|
+
// Path is not a known API route → 404, skip auth middleware
|
|
104
|
+
_res.status(404).json({ error: 'Not found' })
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const api = express.Router()
|
|
109
|
+
api.use(authMiddleware)
|
|
110
|
+
api.use(tenantRateLimiter)
|
|
111
|
+
|
|
112
|
+
api.use(chatRouter)
|
|
113
|
+
api.use('/tenant', tenantRouter)
|
|
114
|
+
api.use('/chat', historyRouter)
|
|
115
|
+
|
|
116
|
+
app.use('/api/v1', api)
|
|
117
|
+
|
|
118
|
+
// ── 404 handler ───────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
app.use((_req, res) => {
|
|
121
|
+
if (!res.headersSent) {
|
|
122
|
+
res.status(404).json({ error: 'Not found' })
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
// ── Error handler ─────────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
app.use(globalErrorHandler)
|
|
129
|
+
|
|
130
|
+
// ── Start ─────────────────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
app.listen(config.port, () => {
|
|
133
|
+
console.log(`[AgentX Gateway] Running on port ${config.port}`)
|
|
134
|
+
console.log(`[AgentX Gateway] Mode: ${config.nodeEnv}`)
|
|
135
|
+
|
|
136
|
+
// Start A2A background worker for multi-agent task processing
|
|
137
|
+
import('./services/a2a-worker').then(({ startA2AWorker }) => {
|
|
138
|
+
startA2AWorker()
|
|
139
|
+
console.log('[AgentX Gateway] A2A Worker started')
|
|
140
|
+
}).catch(err => {
|
|
141
|
+
console.error('[AgentX Gateway] Failed to start A2A Worker:', err.message)
|
|
142
|
+
})
|
|
143
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// AgentX Gateway — Crypto Utilities
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// AES-256-GCM encrypt/decrypt for tenant API keys at rest.
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
import crypto from 'crypto'
|
|
8
|
+
|
|
9
|
+
const ALGORITHM = 'aes-256-gcm'
|
|
10
|
+
const IV_LENGTH = 12
|
|
11
|
+
const AUTH_TAG_LENGTH = 16
|
|
12
|
+
|
|
13
|
+
export function encryptApiKey(plaintext: string, masterKeyHex: string): string {
|
|
14
|
+
const key = Buffer.from(masterKeyHex, 'hex')
|
|
15
|
+
const iv = crypto.randomBytes(IV_LENGTH)
|
|
16
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv)
|
|
17
|
+
|
|
18
|
+
const encrypted = Buffer.concat([
|
|
19
|
+
cipher.update(plaintext, 'utf8'),
|
|
20
|
+
cipher.final(),
|
|
21
|
+
])
|
|
22
|
+
const authTag = cipher.getAuthTag()
|
|
23
|
+
|
|
24
|
+
const combined = Buffer.concat([iv, authTag, encrypted])
|
|
25
|
+
return combined.toString('base64')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function decryptApiKey(ciphertext: string, masterKeyHex: string): string {
|
|
29
|
+
const key = Buffer.from(masterKeyHex, 'hex')
|
|
30
|
+
const combined = Buffer.from(ciphertext, 'base64')
|
|
31
|
+
|
|
32
|
+
const iv = combined.subarray(0, IV_LENGTH)
|
|
33
|
+
const authTag = combined.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH)
|
|
34
|
+
const encrypted = combined.subarray(IV_LENGTH + AUTH_TAG_LENGTH)
|
|
35
|
+
|
|
36
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv)
|
|
37
|
+
decipher.setAuthTag(authTag)
|
|
38
|
+
|
|
39
|
+
const decrypted = Buffer.concat([
|
|
40
|
+
decipher.update(encrypted),
|
|
41
|
+
decipher.final(),
|
|
42
|
+
])
|
|
43
|
+
return decrypted.toString('utf8')
|
|
44
|
+
}
|
package/src/lib/db.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// AgentX Gateway — Database Pool
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
import { Pool } from 'pg'
|
|
6
|
+
|
|
7
|
+
let pool: Pool | null = null
|
|
8
|
+
|
|
9
|
+
export function getPool(): Pool {
|
|
10
|
+
if (!pool) {
|
|
11
|
+
pool = new Pool({
|
|
12
|
+
connectionString: process.env.DATABASE_URL,
|
|
13
|
+
max: 20,
|
|
14
|
+
idleTimeoutMillis: 30_000,
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
return pool
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function closePool(): Promise<void> {
|
|
21
|
+
if (pool) {
|
|
22
|
+
await pool.end()
|
|
23
|
+
pool = null
|
|
24
|
+
}
|
|
25
|
+
}
|