@ton-agent-kit/plugin-identity 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +21 -5
- package/src/actions/deploy-reputation-contract.ts +77 -0
- package/src/actions/discover-agent.ts +94 -46
- package/src/actions/get-agent-cleanup-info.ts +84 -0
- package/src/actions/get-agent-reputation.ts +122 -49
- package/src/actions/get-open-disputes.ts +65 -0
- package/src/actions/process-pending-ratings.ts +114 -0
- package/src/actions/register-agent.ts +117 -61
- package/src/actions/trigger-cleanup.ts +43 -0
- package/src/actions/withdraw-reputation-fees.ts +52 -0
- package/src/contracts/Reputation_Reputation.ts +2583 -0
- package/src/index.ts +64 -12
- package/src/reputation-config.ts +61 -0
- package/src/reputation-helpers.ts +316 -0
package/package.json
CHANGED
|
@@ -1,17 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ton-agent-kit/plugin-identity",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Identity plugin for TON Agent Kit — agent registry, discovery, and reputation",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
7
|
-
"scripts": {
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc"
|
|
9
|
+
},
|
|
8
10
|
"dependencies": {
|
|
9
11
|
"@ton-agent-kit/core": "^1.0.0",
|
|
10
12
|
"@ton/ton": "^16.2.0",
|
|
11
13
|
"@ton/core": "^0.63.1"
|
|
12
14
|
},
|
|
13
|
-
"
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"zod": ">=4.0.0"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"ton",
|
|
20
|
+
"blockchain",
|
|
21
|
+
"ai",
|
|
22
|
+
"agent",
|
|
23
|
+
"sdk"
|
|
24
|
+
],
|
|
14
25
|
"license": "MIT",
|
|
15
|
-
"publishConfig": {
|
|
16
|
-
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/Andy00L/ton-agent-kit.git"
|
|
32
|
+
}
|
|
17
33
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { Address, internal, toNano, beginCell } from "@ton/core";
|
|
3
|
+
|
|
4
|
+
import { defineAction, toFriendlyAddress, sendTransaction } from "@ton-agent-kit/core";
|
|
5
|
+
import { saveReputationConfig } from "../reputation-config";
|
|
6
|
+
|
|
7
|
+
export const deployReputationContractAction = defineAction({
|
|
8
|
+
name: "deploy_reputation_contract",
|
|
9
|
+
description:
|
|
10
|
+
"Deploy the on-chain Reputation smart contract (Tact) to TON. Once deployed, register_agent and get_agent_reputation will use it automatically for verifiable, immutable reputation scores.",
|
|
11
|
+
schema: z.object({
|
|
12
|
+
fee: z
|
|
13
|
+
.string()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("Fee per write operation in TON. Defaults to '0.01'."),
|
|
16
|
+
}),
|
|
17
|
+
handler: async (agent, params) => {
|
|
18
|
+
try {
|
|
19
|
+
// Dynamic import of compiled Tact contract
|
|
20
|
+
// This file is generated by: bun run contracts/deploy-reputation.ts
|
|
21
|
+
let Reputation: any;
|
|
22
|
+
let storeDeploy: any;
|
|
23
|
+
try {
|
|
24
|
+
const mod = await import("../contracts/Reputation_Reputation");
|
|
25
|
+
Reputation = mod.Reputation;
|
|
26
|
+
storeDeploy = mod.storeDeploy;
|
|
27
|
+
} catch {
|
|
28
|
+
return {
|
|
29
|
+
deployed: false,
|
|
30
|
+
message:
|
|
31
|
+
"Compiled contract not found. Run 'bun run contracts/deploy-reputation.ts' first to compile the Tact contract, then copy output to packages/plugin-identity/src/contracts/.",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const ownerAddress = agent.wallet.address;
|
|
36
|
+
const reputation = await Reputation.fromInit(ownerAddress);
|
|
37
|
+
|
|
38
|
+
const deployBody = beginCell()
|
|
39
|
+
.store(storeDeploy({ $$type: "Deploy", queryId: BigInt(0) }))
|
|
40
|
+
.endCell();
|
|
41
|
+
|
|
42
|
+
await sendTransaction(agent, [
|
|
43
|
+
internal({
|
|
44
|
+
to: reputation.address,
|
|
45
|
+
value: toNano("0.05"),
|
|
46
|
+
bounce: false,
|
|
47
|
+
init: reputation.init!,
|
|
48
|
+
body: deployBody,
|
|
49
|
+
}),
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
const contractAddr = reputation.address.toRawString();
|
|
53
|
+
|
|
54
|
+
// Save config so other actions auto-detect the contract
|
|
55
|
+
saveReputationConfig({
|
|
56
|
+
contractAddress: contractAddr,
|
|
57
|
+
network: agent.network,
|
|
58
|
+
deployedAt: new Date().toISOString(),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
deployed: true,
|
|
63
|
+
contractAddress: contractAddr,
|
|
64
|
+
friendlyContract: toFriendlyAddress(reputation.address, agent.network),
|
|
65
|
+
network: agent.network,
|
|
66
|
+
fee: params.fee || "0.01",
|
|
67
|
+
message: `Reputation contract deployed at ${toFriendlyAddress(reputation.address, agent.network)}. Agents can now register on-chain.`,
|
|
68
|
+
};
|
|
69
|
+
} catch (err: any) {
|
|
70
|
+
return {
|
|
71
|
+
deployed: false,
|
|
72
|
+
error: err.message,
|
|
73
|
+
message: `Failed to deploy reputation contract: ${err.message}`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
});
|
|
@@ -2,50 +2,98 @@ import { z } from "zod";
|
|
|
2
2
|
import { Address } from "@ton/core";
|
|
3
3
|
import { defineAction, toFriendlyAddress } from "@ton-agent-kit/core";
|
|
4
4
|
import { loadAgentRegistry } from "../utils";
|
|
5
|
+
import { resolveContractAddress } from "../reputation-config";
|
|
6
|
+
import { callContractGetter, lookupAgentIndex, parseAgentDataFromStack } from "../reputation-helpers";
|
|
5
7
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
8
|
+
export function createDiscoverAgentAction(contractAddress?: string) {
|
|
9
|
+
return defineAction({
|
|
10
|
+
name: "discover_agent",
|
|
11
|
+
description:
|
|
12
|
+
"Find registered agents by capability or name. Searches both on-chain (if contract deployed) and local registry. Set includeOffline=true to include unavailable agents.",
|
|
13
|
+
schema: z.object({
|
|
14
|
+
capability: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Capability to search for (e.g., 'price_feed', 'trading')"),
|
|
18
|
+
name: z.string().optional().describe("Agent name to search for"),
|
|
19
|
+
includeOffline: z
|
|
20
|
+
.boolean()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Include offline/unavailable agents. Defaults to false."),
|
|
23
|
+
}),
|
|
24
|
+
handler: async (agent, params) => {
|
|
25
|
+
const addr = resolveContractAddress(contractAddress, agent.network);
|
|
26
|
+
|
|
27
|
+
// ── Hybrid mode: JSON registry for discovery, on-chain for reputation ──
|
|
28
|
+
const registry = loadAgentRegistry();
|
|
29
|
+
let results = Object.values(registry);
|
|
30
|
+
|
|
31
|
+
if (!params.includeOffline) {
|
|
32
|
+
results = results.filter((a: any) => a.available !== false);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (params.capability) {
|
|
36
|
+
const cap = params.capability.toLowerCase();
|
|
37
|
+
results = results.filter((a: any) =>
|
|
38
|
+
a.capabilities.some((c: string) => c.toLowerCase().includes(cap)),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (params.name) {
|
|
43
|
+
const name = params.name.toLowerCase();
|
|
44
|
+
results = results.filter((a: any) => a.name.toLowerCase().includes(name));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Enrich with on-chain reputation if contract is available
|
|
48
|
+
if (addr) {
|
|
49
|
+
const apiBase =
|
|
50
|
+
agent.network === "testnet"
|
|
51
|
+
? "https://testnet.tonapi.io/v2"
|
|
52
|
+
: "https://tonapi.io/v2";
|
|
53
|
+
|
|
54
|
+
for (const a of results as any[]) {
|
|
55
|
+
try {
|
|
56
|
+
const idx = await lookupAgentIndex(apiBase, addr, a.name, agent.config.TONAPI_KEY);
|
|
57
|
+
if (idx !== null) {
|
|
58
|
+
const dataRes = await callContractGetter(
|
|
59
|
+
apiBase, addr, "agentData", [idx.toString()], agent.config.TONAPI_KEY,
|
|
60
|
+
);
|
|
61
|
+
const parsed = parseAgentDataFromStack(dataRes?.stack);
|
|
62
|
+
if (parsed) {
|
|
63
|
+
const rep = parsed.totalTasks > 0
|
|
64
|
+
? Math.round((parsed.successes / parsed.totalTasks) * 100)
|
|
65
|
+
: 0;
|
|
66
|
+
a.reputation = { score: rep, totalTasks: parsed.totalTasks, successfulTasks: parsed.successes };
|
|
67
|
+
a.onChain = true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
// On-chain lookup failed for this agent — keep JSON data
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
query: { capability: params.capability, name: params.name, includeOffline: params.includeOffline },
|
|
78
|
+
count: results.length,
|
|
79
|
+
agents: results.map((a: any) => ({
|
|
80
|
+
id: a.id,
|
|
81
|
+
name: a.name,
|
|
82
|
+
address: a.address,
|
|
83
|
+
friendlyAddress: a.address ? toFriendlyAddress(Address.parse(a.address), agent.network) : "",
|
|
84
|
+
capabilities: a.capabilities,
|
|
85
|
+
available: a.available !== false,
|
|
86
|
+
description: a.description,
|
|
87
|
+
endpoint: a.endpoint,
|
|
88
|
+
reputation: a.reputation,
|
|
89
|
+
registeredAt: a.registeredAt,
|
|
90
|
+
onChain: a.onChain || false,
|
|
91
|
+
})),
|
|
92
|
+
onChain: !!addr,
|
|
93
|
+
contractAddress: addr || undefined,
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const discoverAgentAction = createDiscoverAgentAction();
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineAction } from "@ton-agent-kit/core";
|
|
3
|
+
import { resolveContractAddress } from "../reputation-config";
|
|
4
|
+
import { callContractGetter } from "../reputation-helpers";
|
|
5
|
+
|
|
6
|
+
export const getAgentCleanupInfoAction = defineAction({
|
|
7
|
+
name: "get_agent_cleanup_info",
|
|
8
|
+
description:
|
|
9
|
+
"Check if an agent is eligible for cleanup. Shows score, ratings count, last active date, and which cleanup condition would trigger removal. " +
|
|
10
|
+
"Reasons: 1=bad_score (<20% with 100+ ratings), 2=inactive (30+ days), 3=ghost (0 ratings after 7 days).",
|
|
11
|
+
schema: z.object({
|
|
12
|
+
agentIndex: z.coerce.number().describe("Agent index to check"),
|
|
13
|
+
}),
|
|
14
|
+
handler: async (agent, params) => {
|
|
15
|
+
const addr = resolveContractAddress(undefined, agent.network);
|
|
16
|
+
if (!addr) {
|
|
17
|
+
return { message: "No reputation contract configured" };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const apiBase = agent.network === "testnet"
|
|
21
|
+
? "https://testnet.tonapi.io/v2"
|
|
22
|
+
: "https://tonapi.io/v2";
|
|
23
|
+
|
|
24
|
+
const result = await callContractGetter(
|
|
25
|
+
apiBase, addr, "agentCleanupInfo",
|
|
26
|
+
[params.agentIndex.toString()],
|
|
27
|
+
agent.config.TONAPI_KEY,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (!result?.stack || result.stack.length === 0) {
|
|
31
|
+
return { message: "Failed to read cleanup info" };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// agentCleanupInfo returns a struct as a tuple
|
|
35
|
+
const stack = result.stack;
|
|
36
|
+
let items = stack;
|
|
37
|
+
if (stack[0]?.type === "tuple" && stack[0].tuple) {
|
|
38
|
+
items = stack[0].tuple;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const parseNum = (item: any): number => {
|
|
42
|
+
if (!item || item.type !== "num") return 0;
|
|
43
|
+
const s = item.num as string;
|
|
44
|
+
if (s.startsWith("-0x") || s.startsWith("-0X")) return Number(-BigInt(s.slice(1)));
|
|
45
|
+
return Number(BigInt(s));
|
|
46
|
+
};
|
|
47
|
+
const parseBool = (item: any): boolean => {
|
|
48
|
+
if (!item) return false;
|
|
49
|
+
if (item.type === "num") {
|
|
50
|
+
const s = item.num as string;
|
|
51
|
+
if (s.startsWith("-0x")) return -BigInt(s.slice(1)) !== 0n;
|
|
52
|
+
return BigInt(s) !== 0n;
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const info = {
|
|
58
|
+
index: parseNum(items[0]),
|
|
59
|
+
exists: parseBool(items[1]),
|
|
60
|
+
score: parseNum(items[2]),
|
|
61
|
+
totalRatings: parseNum(items[3]),
|
|
62
|
+
registeredAt: parseNum(items[4]),
|
|
63
|
+
lastActive: parseNum(items[5]),
|
|
64
|
+
daysSinceActive: parseNum(items[6]),
|
|
65
|
+
daysSinceRegistered: parseNum(items[7]),
|
|
66
|
+
eligibleForCleanup: parseBool(items[8]),
|
|
67
|
+
cleanupReason: parseNum(items[9]),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const reasons: Record<number, string> = {
|
|
71
|
+
0: "none — agent is healthy",
|
|
72
|
+
1: "bad_score — score < 20% with 100+ ratings",
|
|
73
|
+
2: "inactive — no activity for 30+ days",
|
|
74
|
+
3: "ghost — 0 ratings after 7+ days since registration",
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
...info,
|
|
79
|
+
cleanupReasonText: reasons[info.cleanupReason] || "unknown",
|
|
80
|
+
onChain: true,
|
|
81
|
+
contractAddress: addr,
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
});
|
|
@@ -1,53 +1,126 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { Address } from "@ton/core";
|
|
3
|
-
import { defineAction, toFriendlyAddress } from "@ton-agent-kit/core";
|
|
2
|
+
import { Address, internal, toNano } from "@ton/core";
|
|
3
|
+
import { defineAction, toFriendlyAddress, sendTransaction } from "@ton-agent-kit/core";
|
|
4
4
|
import { loadAgentRegistry, saveAgentRegistry } from "../utils";
|
|
5
|
+
import { resolveContractAddress } from "../reputation-config";
|
|
6
|
+
import { buildRateBody, callContractGetter, lookupAgentIndex, parseAgentDataFromStack } from "../reputation-helpers";
|
|
5
7
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
8
|
+
export function createGetAgentReputationAction(contractAddress?: string) {
|
|
9
|
+
return defineAction({
|
|
10
|
+
name: "get_agent_reputation",
|
|
11
|
+
description:
|
|
12
|
+
"Get the reputation score of a registered agent. Set addTask=true and success=true/false to record a task result. Works both on-chain and locally.",
|
|
13
|
+
schema: z.object({
|
|
14
|
+
agentId: z.string().describe("Agent ID or name to check or update"),
|
|
15
|
+
addTask: z
|
|
16
|
+
.union([z.boolean(), z.string()])
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("Set to true to record a completed task"),
|
|
19
|
+
success: z
|
|
20
|
+
.union([z.boolean(), z.string()])
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("If addTask is true, whether the task was successful"),
|
|
23
|
+
}),
|
|
24
|
+
handler: async (agent, params) => {
|
|
25
|
+
const addTask = typeof params.addTask === "string" ? params.addTask === "true" : params.addTask;
|
|
26
|
+
const success = typeof params.success === "string" ? params.success === "true" : params.success;
|
|
27
|
+
|
|
28
|
+
const addr = resolveContractAddress(contractAddress, agent.network);
|
|
29
|
+
|
|
30
|
+
// ── On-chain mode ──
|
|
31
|
+
if (addr) {
|
|
32
|
+
try {
|
|
33
|
+
const apiBase =
|
|
34
|
+
agent.network === "testnet"
|
|
35
|
+
? "https://testnet.tonapi.io/v2"
|
|
36
|
+
: "https://tonapi.io/v2";
|
|
37
|
+
|
|
38
|
+
// Get the actual agent name for on-chain lookup
|
|
39
|
+
// Prefer JSON registry (has the exact name used at registration)
|
|
40
|
+
const jsonRegistry = loadAgentRegistry();
|
|
41
|
+
const jsonRecord = jsonRegistry[params.agentId];
|
|
42
|
+
const agentName = jsonRecord?.name
|
|
43
|
+
|| (params.agentId.startsWith("agent_") ? params.agentId.slice(6) : params.agentId);
|
|
44
|
+
|
|
45
|
+
// If rating, send Rate message
|
|
46
|
+
if (addTask) {
|
|
47
|
+
const body = buildRateBody(agentName, success !== false);
|
|
48
|
+
|
|
49
|
+
await sendTransaction(agent, [
|
|
50
|
+
internal({
|
|
51
|
+
to: Address.parse(addr),
|
|
52
|
+
value: toNano("0.03"),
|
|
53
|
+
bounce: true,
|
|
54
|
+
body,
|
|
55
|
+
}),
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Look up agent index by name hash
|
|
60
|
+
const idx = await lookupAgentIndex(apiBase, addr, agentName, agent.config.TONAPI_KEY);
|
|
61
|
+
|
|
62
|
+
if (idx !== null) {
|
|
63
|
+
// Get on-chain data using index
|
|
64
|
+
const dataRes = await callContractGetter(
|
|
65
|
+
apiBase, addr, "agentData", [idx.toString()], agent.config.TONAPI_KEY,
|
|
66
|
+
);
|
|
67
|
+
const repRes = await callContractGetter(
|
|
68
|
+
apiBase, addr, "agentReputation", [idx.toString()], agent.config.TONAPI_KEY,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const agentData = dataRes?.stack ? parseAgentDataFromStack(dataRes.stack) : null;
|
|
72
|
+
const repScore = repRes?.stack?.[0]?.num
|
|
73
|
+
? Number(BigInt(repRes.stack[0].num))
|
|
74
|
+
: 0;
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
agentId: params.agentId,
|
|
78
|
+
name: agentName,
|
|
79
|
+
reputation: {
|
|
80
|
+
score: repScore,
|
|
81
|
+
totalTasks: agentData?.totalTasks || 0,
|
|
82
|
+
successfulTasks: agentData?.successes || 0,
|
|
83
|
+
},
|
|
84
|
+
available: agentData?.available ?? true,
|
|
85
|
+
onChain: true,
|
|
86
|
+
contractAddress: addr,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Agent not found on-chain — fall through to JSON
|
|
91
|
+
} catch {
|
|
92
|
+
// Fall through to JSON
|
|
93
|
+
}
|
|
35
94
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
95
|
+
|
|
96
|
+
// ── JSON fallback mode ──
|
|
97
|
+
const registry = loadAgentRegistry();
|
|
98
|
+
const agentRecord = registry[params.agentId];
|
|
99
|
+
if (!agentRecord) throw new Error(`Agent not found: ${params.agentId}`);
|
|
100
|
+
|
|
101
|
+
if (addTask) {
|
|
102
|
+
agentRecord.reputation.totalTasks += 1;
|
|
103
|
+
if (success !== false) {
|
|
104
|
+
agentRecord.reputation.successfulTasks += 1;
|
|
105
|
+
}
|
|
106
|
+
agentRecord.reputation.score = Math.round(
|
|
107
|
+
(agentRecord.reputation.successfulTasks / agentRecord.reputation.totalTasks) * 100,
|
|
108
|
+
);
|
|
109
|
+
saveAgentRegistry(registry);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
agentId: params.agentId,
|
|
114
|
+
name: agentRecord.name,
|
|
115
|
+
address: agentRecord.address,
|
|
116
|
+
friendlyAddress: toFriendlyAddress(Address.parse(agentRecord.address), agent.network),
|
|
117
|
+
reputation: agentRecord.reputation,
|
|
118
|
+
available: agentRecord.available !== false,
|
|
119
|
+
onChain: false,
|
|
120
|
+
registeredAt: agentRecord.registeredAt,
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export const getAgentReputationAction = createGetAgentReputationAction();
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineAction } from "@ton-agent-kit/core";
|
|
3
|
+
import { resolveContractAddress } from "../reputation-config";
|
|
4
|
+
import { callContractGetter, parseDisputeData } from "../reputation-helpers";
|
|
5
|
+
|
|
6
|
+
export const getOpenDisputesAction = defineAction({
|
|
7
|
+
name: "get_open_disputes",
|
|
8
|
+
description:
|
|
9
|
+
"Get all open (unsettled) disputes from the reputation contract. Arbiters use this to find disputes they can join and vote on.",
|
|
10
|
+
schema: z.object({
|
|
11
|
+
limit: z.coerce.number().optional().describe("Max disputes to return. Default 20."),
|
|
12
|
+
}),
|
|
13
|
+
handler: async (agent, params) => {
|
|
14
|
+
const addr = resolveContractAddress(undefined, agent.network);
|
|
15
|
+
if (!addr) {
|
|
16
|
+
return { disputes: [], count: 0, message: "No reputation contract configured" };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const apiBase = agent.network === "testnet"
|
|
20
|
+
? "https://testnet.tonapi.io/v2"
|
|
21
|
+
: "https://tonapi.io/v2";
|
|
22
|
+
|
|
23
|
+
const limit = params.limit || 20;
|
|
24
|
+
|
|
25
|
+
const countRes = await callContractGetter(apiBase, addr, "disputeCount", undefined, agent.config.TONAPI_KEY);
|
|
26
|
+
const totalDisputes = countRes?.stack?.[0]?.num ? Number(BigInt(countRes.stack[0].num)) : 0;
|
|
27
|
+
|
|
28
|
+
if (totalDisputes === 0) {
|
|
29
|
+
return { disputes: [], count: 0, total: 0, message: "No disputes registered" };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const disputes: any[] = [];
|
|
33
|
+
let checked = 0;
|
|
34
|
+
for (let i = totalDisputes - 1; i >= 0 && disputes.length < limit; i--) {
|
|
35
|
+
try {
|
|
36
|
+
const dataRes = await callContractGetter(apiBase, addr, "disputeData", [i.toString()], agent.config.TONAPI_KEY);
|
|
37
|
+
if (dataRes?.stack) {
|
|
38
|
+
const dispute = parseDisputeData(dataRes.stack);
|
|
39
|
+
if (dispute && !dispute.settled) {
|
|
40
|
+
disputes.push({
|
|
41
|
+
index: i,
|
|
42
|
+
escrowAddress: dispute.escrowAddress,
|
|
43
|
+
depositor: dispute.depositor,
|
|
44
|
+
beneficiary: dispute.beneficiary,
|
|
45
|
+
amount: dispute.amount,
|
|
46
|
+
votingDeadline: new Date(dispute.votingDeadline * 1000).toISOString(),
|
|
47
|
+
isExpired: Date.now() / 1000 > dispute.votingDeadline,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
} catch {}
|
|
52
|
+
checked++;
|
|
53
|
+
if (checked > 100) break;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
disputes,
|
|
58
|
+
count: disputes.length,
|
|
59
|
+
total: totalDisputes,
|
|
60
|
+
onChain: true,
|
|
61
|
+
contractAddress: addr,
|
|
62
|
+
message: `Found ${disputes.length} open dispute(s)`,
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
});
|