@ton-agent-kit/plugin-escrow 1.0.3 → 1.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/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # @ton-agent-kit/plugin-escrow
2
+
3
+ Escrow plugin for on-chain escrow with self-selecting arbiters, staking, and dispute-resolution voting on TON. Manages the full lifecycle of escrow deals from creation through settlement.
4
+
5
+ Part of [TON Agent Kit](https://github.com/Andy00L/ton-agent-kit).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @ton-agent-kit/plugin-escrow @ton-agent-kit/core zod
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```typescript
16
+ import { TonAgentKit, KeypairWallet } from "@ton-agent-kit/core";
17
+ import EscrowPlugin from "@ton-agent-kit/plugin-escrow";
18
+
19
+ const agent = new TonAgentKit(wallet).use(EscrowPlugin);
20
+
21
+ // Create an escrow deal
22
+ const escrow = await agent.runAction("create_escrow", {
23
+ beneficiary: "EQBx...",
24
+ amount: "1.5",
25
+ });
26
+
27
+ // Release funds after delivery
28
+ await agent.runAction("release_escrow", { escrowId: escrow.id });
29
+ ```
30
+
31
+ ## Actions
32
+
33
+ | Action | Description |
34
+ |---|---|
35
+ | `create_escrow` | Create a new escrow deal (arbiters self-select during disputes). |
36
+ | `deposit_to_escrow` | Fund an escrow with TON. |
37
+ | `release_escrow` | Release funds to beneficiary (depositor only, non-dispute). |
38
+ | `refund_escrow` | Refund funds to depositor. |
39
+ | `get_escrow_info` | Get escrow details or list all escrows. |
40
+ | `confirm_delivery` | Confirm service delivery on-chain (buyer only). |
41
+ | `auto_release_escrow` | Release after deadline (requires delivery confirmation). |
42
+ | `open_dispute` | Open a dispute, freezing the escrow for arbiter voting. |
43
+ | `join_dispute` | Stake TON to join as an arbiter in a dispute. |
44
+ | `vote_release` | Arbiter votes to release funds during a dispute. |
45
+ | `vote_refund` | Arbiter votes to refund funds during a dispute. |
46
+ | `claim_reward` | Claim arbiter reward after settlement. |
47
+ | `fallback_settle` | Settle after the 72h voting deadline expires. |
48
+ | `seller_stake_escrow` | Seller stakes TON to signal commitment to the deal. |
49
+
50
+ ## Documentation
51
+
52
+ See [docs/escrow-system.md](https://github.com/Andy00L/ton-agent-kit/blob/main/docs/escrow-system.md) for the full escrow system design.
53
+
54
+ ## Links
55
+
56
+ - [GitHub](https://github.com/Andy00L/ton-agent-kit)
57
+ - [npm](https://www.npmjs.com/package/@ton-agent-kit/plugin-escrow)
58
+
59
+ ## License
60
+
61
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ton-agent-kit/plugin-escrow",
3
- "version": "1.0.3",
3
+ "version": "1.1.1",
4
4
  "description": "Escrow plugin for TON Agent Kit — create, fund, release, and refund escrow deals",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -0,0 +1,72 @@
1
+ import { z } from "zod";
2
+ import { Address } from "@ton/core";
3
+ import { defineAction, toFriendlyAddress } from "@ton-agent-kit/core";
4
+ import { loadEscrows, saveEscrows, autoReleaseOnContract, getLatestTxHash } from "../utils";
5
+
6
+ export const autoReleaseEscrowAction = defineAction({
7
+ name: "auto_release_escrow",
8
+ description:
9
+ "Auto-release escrow funds to the beneficiary after the deadline has passed. Requires delivery confirmation from the buyer (confirm_delivery). Fails if delivery was never confirmed or if escrow is disputed.",
10
+ schema: z.object({
11
+ escrowId: z
12
+ .string()
13
+ .describe("Escrow ID to auto-release after deadline"),
14
+ }),
15
+ handler: async (agent, params) => {
16
+ const escrows = loadEscrows();
17
+ const escrow = escrows[params.escrowId];
18
+ if (!escrow) throw new Error(`Escrow not found: ${params.escrowId}`);
19
+
20
+ const contractAddress = Address.parse(escrow.contractAddress);
21
+
22
+ await autoReleaseOnContract(agent, contractAddress);
23
+
24
+ escrow.status = "auto-released";
25
+ saveEscrows(escrows);
26
+
27
+ const txHash = await getLatestTxHash(
28
+ contractAddress.toRawString(),
29
+ agent.network,
30
+ );
31
+
32
+ // Save pending bidirectional ratings (non-critical)
33
+ try {
34
+ await (agent as any).runAction("save_context", {
35
+ key: `pending_rating_${params.escrowId}_buyer`,
36
+ namespace: "pending_ratings",
37
+ value: JSON.stringify({
38
+ escrowId: params.escrowId,
39
+ raterRole: "buyer",
40
+ targetAddress: escrow.beneficiary,
41
+ suggestedSuccess: true,
42
+ escrowOutcome: "auto-released",
43
+ timestamp: Date.now(),
44
+ }),
45
+ });
46
+ } catch {}
47
+ try {
48
+ await (agent as any).runAction("save_context", {
49
+ key: `pending_rating_${params.escrowId}_seller`,
50
+ namespace: "pending_ratings",
51
+ value: JSON.stringify({
52
+ escrowId: params.escrowId,
53
+ raterRole: "seller",
54
+ targetAddress: escrow.depositor,
55
+ suggestedSuccess: true,
56
+ escrowOutcome: "auto-released",
57
+ timestamp: Date.now(),
58
+ }),
59
+ });
60
+ } catch {}
61
+
62
+ return {
63
+ released: true,
64
+ escrowId: params.escrowId,
65
+ contractAddress: escrow.contractAddress,
66
+ friendlyContract: toFriendlyAddress(contractAddress, agent.network),
67
+ beneficiary: escrow.beneficiary,
68
+ autoReleaseTxHash: txHash,
69
+ message: `Escrow ${params.escrowId} auto-released to beneficiary after deadline.`,
70
+ };
71
+ },
72
+ });
@@ -0,0 +1,48 @@
1
+ import { z } from "zod";
2
+ import { Address } from "@ton/core";
3
+ import { defineAction, toFriendlyAddress } from "@ton-agent-kit/core";
4
+ import { loadEscrows, claimRewardOnContract, getLatestTxHash } from "../utils";
5
+
6
+ export const claimRewardAction = defineAction({
7
+ name: "claim_reward",
8
+ description:
9
+ "Claim arbiter reward after an escrow dispute is settled. Winners get back their stake + share of losers' stakes. Losers forfeit their stake.",
10
+ schema: z.object({
11
+ escrowId: z.string().describe("Escrow ID to claim arbiter reward from"),
12
+ }),
13
+ handler: async (agent, params) => {
14
+ const escrows = loadEscrows();
15
+ const escrow = escrows[params.escrowId];
16
+ if (!escrow) {
17
+ return { claimed: false, message: `Escrow not found: ${params.escrowId}` };
18
+ }
19
+
20
+ const contractAddress = Address.parse(escrow.contractAddress);
21
+
22
+ try {
23
+ await claimRewardOnContract(agent, contractAddress);
24
+
25
+ const txHash = await getLatestTxHash(
26
+ agent.wallet.address.toRawString(),
27
+ agent.network,
28
+ );
29
+
30
+ return {
31
+ claimed: true,
32
+ escrowId: params.escrowId,
33
+ contractAddress: escrow.contractAddress,
34
+ friendlyContract: toFriendlyAddress(contractAddress, agent.network),
35
+ arbiter: agent.wallet.address.toRawString(),
36
+ claimTxHash: txHash,
37
+ message: `Claim submitted for escrow ${params.escrowId}. If you voted with the majority, reward is sent to your wallet.`,
38
+ };
39
+ } catch (err: any) {
40
+ return {
41
+ claimed: false,
42
+ escrowId: params.escrowId,
43
+ error: err.message,
44
+ message: `Failed to claim reward: ${err.message}`,
45
+ };
46
+ }
47
+ },
48
+ });
@@ -0,0 +1,60 @@
1
+ import { z } from "zod";
2
+ import { Address } from "@ton/core";
3
+ import { defineAction, toFriendlyAddress } from "@ton-agent-kit/core";
4
+ import { loadEscrows, saveEscrows, confirmDeliveryOnContract, getLatestTxHash } from "../utils";
5
+
6
+ export const confirmDeliveryAction = defineAction({
7
+ name: "confirm_delivery",
8
+ description:
9
+ "Confirm that a service was delivered for an escrow deal. Only the depositor (buyer) can call this. Once confirmed, auto-release becomes available after the deadline. The x402TxHash serves as an audit trail linking the payment to the delivery.",
10
+ schema: z.object({
11
+ escrowId: z
12
+ .string()
13
+ .describe("Escrow ID to confirm delivery for"),
14
+ x402TxHash: z
15
+ .string()
16
+ .optional()
17
+ .describe("Optional x402 payment TX hash as proof of service delivery"),
18
+ }),
19
+ handler: async (agent, params) => {
20
+ const escrows = loadEscrows();
21
+ const escrow = escrows[params.escrowId];
22
+ if (!escrow) {
23
+ return {
24
+ confirmed: false,
25
+ message: `Escrow not found: ${params.escrowId}`,
26
+ };
27
+ }
28
+
29
+ const contractAddress = Address.parse(escrow.contractAddress);
30
+
31
+ try {
32
+ await confirmDeliveryOnContract(agent, contractAddress, params.x402TxHash || "");
33
+
34
+ escrow.status = "delivery-confirmed";
35
+ saveEscrows(escrows);
36
+
37
+ const txHash = await getLatestTxHash(
38
+ contractAddress.toRawString(),
39
+ agent.network,
40
+ );
41
+
42
+ return {
43
+ confirmed: true,
44
+ escrowId: params.escrowId,
45
+ contractAddress: escrow.contractAddress,
46
+ friendlyContract: toFriendlyAddress(contractAddress, agent.network),
47
+ x402TxHash: params.x402TxHash || null,
48
+ confirmTxHash: txHash,
49
+ message: `Delivery confirmed on-chain for escrow ${params.escrowId}. Auto-release will be available after deadline.`,
50
+ };
51
+ } catch (err: any) {
52
+ return {
53
+ confirmed: false,
54
+ escrowId: params.escrowId,
55
+ error: err.message,
56
+ message: `Failed to confirm delivery: ${err.message}`,
57
+ };
58
+ }
59
+ },
60
+ });
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { Address } from "@ton/core";
2
+ import { Address, toNano } from "@ton/core";
3
3
  import { defineAction, toFriendlyAddress } from "@ton-agent-kit/core";
4
4
  import {
5
5
  loadEscrows,
@@ -7,11 +7,14 @@ import {
7
7
  deployEscrowContract,
8
8
  type EscrowRecord,
9
9
  } from "../utils";
10
+ import { resolveContractAddress } from "../../../plugin-identity/src/reputation-config";
10
11
 
11
12
  export const createEscrowAction = defineAction<
12
13
  {
13
14
  beneficiary: string;
14
15
  amount: string;
16
+ minArbiters?: number;
17
+ minStake?: string;
15
18
  description?: string;
16
19
  deadlineTimestamp?: number;
17
20
  deadlineDays?: number;
@@ -23,31 +26,24 @@ export const createEscrowAction = defineAction<
23
26
  name: "create_escrow",
24
27
  description:
25
28
  "Create a new on-chain escrow deal. Deploys a Tact Escrow contract to TON. " +
26
- "The beneficiary receives funds when released. " +
27
- "Deadline can be set via deadlineTimestamp (exact Unix timestamp), deadlineDays, deadlineHours, or deadlineMinutes. " +
28
- "Priority: timestamp > days > hours > minutes. Defaults to 60 minutes if none provided.",
29
+ "No arbiters needed upfront they self-select by staking during disputes. " +
30
+ "minArbiters sets the minimum arbiters needed for voting (default 3). " +
31
+ "minStake sets the minimum stake per arbiter in TON (default 0.5).",
29
32
  schema: z.object({
30
33
  beneficiary: z
31
34
  .string()
32
35
  .describe("Address of the beneficiary (who receives funds)"),
33
36
  amount: z.string().describe("Amount of TON to escrow"),
37
+ minArbiters: z.coerce.number().optional().describe("Minimum arbiters for dispute voting. Default 3."),
38
+ minStake: z.string().optional().describe("Minimum stake per arbiter in TON. Default 0.5."),
39
+ requireRepCollateral: z.boolean().optional().describe("Require reputation-based collateral from seller. Default false."),
40
+ minRepScore: z.coerce.number().optional().describe("Minimum rep score for seller (0-100). Default 30. Only if requireRepCollateral=true."),
41
+ baseSellerStake: z.string().optional().describe("Base seller stake in TON before rep adjustment. Default 0.5. Only if requireRepCollateral=true."),
34
42
  description: z.string().optional().describe("Description of the deal"),
35
- deadlineTimestamp: z.coerce
36
- .number()
37
- .optional()
38
- .describe("Exact deadline as Unix timestamp (seconds). Takes highest priority."),
39
- deadlineDays: z.coerce
40
- .number()
41
- .optional()
42
- .describe("Deadline in days from now (e.g., 90 for ~3 months)"),
43
- deadlineHours: z.coerce
44
- .number()
45
- .optional()
46
- .describe("Deadline in hours from now (e.g., 24 for 1 day)"),
47
- deadlineMinutes: z.coerce
48
- .number()
49
- .optional()
50
- .describe("Deadline in minutes from now (e.g., 60 for 1 hour). Default if nothing else set."),
43
+ deadlineTimestamp: z.coerce.number().optional().describe("Exact deadline as Unix timestamp (seconds)."),
44
+ deadlineDays: z.coerce.number().optional().describe("Deadline in days from now"),
45
+ deadlineHours: z.coerce.number().optional().describe("Deadline in hours from now"),
46
+ deadlineMinutes: z.coerce.number().optional().describe("Deadline in minutes from now. Default 60."),
51
47
  }) as any,
52
48
  handler: async (agent, params) => {
53
49
  const escrowId = `escrow_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
@@ -66,27 +62,42 @@ export const createEscrowAction = defineAction<
66
62
 
67
63
  const depositor = agent.wallet.address;
68
64
  const beneficiary = Address.parse(params.beneficiary);
69
- const arbiter = agent.wallet.address; // Agent is arbiter by default
65
+ const minArbiters = params.minArbiters || 3;
66
+ const minStakeNano = toNano(params.minStake || "0.5");
67
+
68
+ // Layer 3+4: Rep collateral
69
+ const requireRepCollateral = params.requireRepCollateral || false;
70
+ const minRepScore = params.minRepScore || 30;
71
+ const baseSellerStake = params.baseSellerStake || "0.5";
72
+ const baseSellerStakeNano = toNano(baseSellerStake);
73
+
74
+ // Resolve reputation contract for cross-contract notifications
75
+ const repAddr = resolveContractAddress(undefined, agent.network);
76
+ const reputationContract = repAddr ? Address.parse(repAddr) : depositor; // fallback
70
77
 
71
- // Deploy a new Escrow contract on-chain
72
78
  const contractAddress = await deployEscrowContract(
73
79
  agent,
74
80
  depositor,
75
81
  beneficiary,
76
- arbiter,
77
82
  BigInt(deadline),
83
+ BigInt(minArbiters),
84
+ minStakeNano,
85
+ reputationContract,
86
+ requireRepCollateral,
87
+ BigInt(minRepScore),
88
+ baseSellerStakeNano,
78
89
  );
79
90
 
80
- // Save to JSON index (escrowId → contract address mapping)
81
91
  const escrow: EscrowRecord = {
82
92
  id: escrowId,
83
93
  contractAddress: contractAddress.toRawString(),
84
94
  depositor: depositor.toRawString(),
85
95
  beneficiary: params.beneficiary,
86
- arbiter: arbiter.toRawString(),
87
96
  amount: params.amount,
88
97
  deadline,
89
98
  deadlineISO: new Date(deadline * 1000).toISOString(),
99
+ minArbiters,
100
+ minStake: params.minStake || "0.5",
90
101
  description: params.description || "",
91
102
  status: "created",
92
103
  createdAt: new Date().toISOString(),
@@ -103,10 +114,17 @@ export const createEscrowAction = defineAction<
103
114
  friendlyContract: toFriendlyAddress(contractAddress, agent.network),
104
115
  beneficiary: params.beneficiary,
105
116
  friendlyBeneficiary: toFriendlyAddress(beneficiary, agent.network),
117
+ minArbiters,
118
+ minStake: params.minStake || "0.5",
106
119
  amount: params.amount + " TON",
107
120
  deadline: escrow.deadlineISO,
121
+ requireRepCollateral,
122
+ minRepScore: requireRepCollateral ? minRepScore : "disabled",
123
+ baseSellerStake: requireRepCollateral ? baseSellerStake + " TON" : "not required",
108
124
  description: params.description || "",
109
- nextStep: `Deposit ${params.amount} TON using deposit_to_escrow with escrowId: ${escrowId}`,
125
+ nextStep: requireRepCollateral
126
+ ? `Seller must stake first using seller_stake_escrow, then deposit ${params.amount} TON`
127
+ : `Deposit ${params.amount} TON using deposit_to_escrow with escrowId: ${escrowId}`,
110
128
  };
111
129
  },
112
130
  });
@@ -0,0 +1,51 @@
1
+ import { z } from "zod";
2
+ import { Address } from "@ton/core";
3
+ import { defineAction, toFriendlyAddress } from "@ton-agent-kit/core";
4
+ import { loadEscrows, saveEscrows, fallbackSettleOnContract, getLatestTxHash } from "../utils";
5
+
6
+ export const fallbackSettleAction = defineAction({
7
+ name: "fallback_settle",
8
+ description:
9
+ "Settle a disputed escrow after the 72h voting deadline. If not enough arbiters joined or votes are tied, " +
10
+ "settles based on delivery status. Anyone can call after votingDeadline.",
11
+ schema: z.object({
12
+ escrowId: z.string().describe("Escrow ID to settle after voting timeout"),
13
+ }),
14
+ handler: async (agent, params) => {
15
+ const escrows = loadEscrows();
16
+ const escrow = escrows[params.escrowId];
17
+ if (!escrow) {
18
+ return { settled: false, message: `Escrow not found: ${params.escrowId}` };
19
+ }
20
+
21
+ const contractAddress = Address.parse(escrow.contractAddress);
22
+
23
+ try {
24
+ await fallbackSettleOnContract(agent, contractAddress);
25
+
26
+ escrow.status = "fallback-settled";
27
+ saveEscrows(escrows);
28
+
29
+ const txHash = await getLatestTxHash(
30
+ contractAddress.toRawString(),
31
+ agent.network,
32
+ );
33
+
34
+ return {
35
+ settled: true,
36
+ escrowId: params.escrowId,
37
+ contractAddress: escrow.contractAddress,
38
+ friendlyContract: toFriendlyAddress(contractAddress, agent.network),
39
+ settleTxHash: txHash,
40
+ message: `Escrow ${params.escrowId} settled via fallback after voting deadline.`,
41
+ };
42
+ } catch (err: any) {
43
+ return {
44
+ settled: false,
45
+ escrowId: params.escrowId,
46
+ error: err.message,
47
+ message: `Fallback settle failed: ${err.message}`,
48
+ };
49
+ }
50
+ },
51
+ });
@@ -35,19 +35,37 @@ export const getEscrowInfoAction = defineAction<{ escrowId?: string }, any>({
35
35
  onChain: {
36
36
  depositor: toFriendlyAddress(onChain.depositor, agent.network),
37
37
  beneficiary: toFriendlyAddress(onChain.beneficiary, agent.network),
38
- arbiter: toFriendlyAddress(onChain.arbiter, agent.network),
38
+ reputationContract: toFriendlyAddress(onChain.reputationContract, agent.network),
39
39
  amount: fromNano(onChain.amount) + " TON",
40
40
  balance: fromNano(onChain.balance) + " TON",
41
41
  deadline: new Date(Number(onChain.deadline) * 1000).toISOString(),
42
42
  released: onChain.released,
43
43
  refunded: onChain.refunded,
44
+ deliveryConfirmed: onChain.deliveryConfirmed,
45
+ disputed: onChain.disputed,
46
+ votingDeadline: onChain.votingDeadline ? new Date(Number(onChain.votingDeadline) * 1000).toISOString() : null,
47
+ arbiterCount: Number(onChain.arbiterCount),
48
+ votesRelease: Number(onChain.votesRelease),
49
+ votesRefund: Number(onChain.votesRefund),
50
+ minArbiters: Number(onChain.minArbiters),
51
+ minStake: fromNano(onChain.minStake) + " TON",
52
+ sellerStake: fromNano(onChain.sellerStake) + " TON",
53
+ sellerStaked: onChain.sellerStaked,
54
+ requireSellerStake: onChain.requireSellerStake,
55
+ baseSellerStake: fromNano(onChain.baseSellerStake) + " TON",
56
+ requireRepCollateral: onChain.requireRepCollateral,
57
+ minRepScore: Number(onChain.minRepScore),
58
+ autoReleaseAvailable: onChain.autoReleaseAvailable,
59
+ refundAvailable: onChain.refundAvailable,
44
60
  status: onChain.released
45
61
  ? "released"
46
62
  : onChain.refunded
47
63
  ? "refunded"
48
- : onChain.amount > BigInt(0)
49
- ? "funded"
50
- : "created",
64
+ : onChain.disputed
65
+ ? "disputed"
66
+ : onChain.amount > BigInt(0)
67
+ ? "funded"
68
+ : "created",
51
69
  },
52
70
  };
53
71
  } catch (e: any) {
@@ -0,0 +1,56 @@
1
+ import { z } from "zod";
2
+ import { Address } from "@ton/core";
3
+ import { defineAction, toFriendlyAddress } from "@ton-agent-kit/core";
4
+ import { loadEscrows, joinDisputeOnContract, getLatestTxHash } from "../utils";
5
+
6
+ export const joinDisputeAction = defineAction({
7
+ name: "join_dispute",
8
+ description:
9
+ "Join an escrow dispute as an arbiter by staking TON. Self-selecting: any agent can join (except depositor/beneficiary). " +
10
+ "A 2% fee is deducted from the stake. Winners get back their stake + share of losers' stakes. Losers forfeit.",
11
+ schema: z.object({
12
+ escrowId: z.string().describe("Escrow ID to join as arbiter"),
13
+ stake: z.string().optional().describe("Stake amount in TON (sent to contract, 2% fee deducted). Default 0.5."),
14
+ }),
15
+ handler: async (agent, params) => {
16
+ const escrows = loadEscrows();
17
+ const escrow = escrows[params.escrowId];
18
+ if (!escrow) {
19
+ return { joined: false, message: `Escrow not found: ${params.escrowId}` };
20
+ }
21
+
22
+ const contractAddress = Address.parse(escrow.contractAddress);
23
+ const stakeAmount = params.stake || escrow.minStake || "0.5";
24
+
25
+ // Add 3% buffer to cover the 2% fee + gas
26
+ const stakeNum = parseFloat(stakeAmount);
27
+ const withBuffer = (stakeNum * 1.03).toFixed(9);
28
+
29
+ try {
30
+ await joinDisputeOnContract(agent, contractAddress, withBuffer);
31
+
32
+ const txHash = await getLatestTxHash(
33
+ agent.wallet.address.toRawString(),
34
+ agent.network,
35
+ );
36
+
37
+ return {
38
+ joined: true,
39
+ escrowId: params.escrowId,
40
+ contractAddress: escrow.contractAddress,
41
+ friendlyContract: toFriendlyAddress(contractAddress, agent.network),
42
+ arbiter: agent.wallet.address.toRawString(),
43
+ stakeAmount: stakeAmount + " TON",
44
+ stakeTxHash: txHash,
45
+ message: `Joined dispute on escrow ${params.escrowId} with ${stakeAmount} TON stake. Vote with vote_release or vote_refund.`,
46
+ };
47
+ } catch (err: any) {
48
+ return {
49
+ joined: false,
50
+ escrowId: params.escrowId,
51
+ error: err.message,
52
+ message: `Failed to join dispute: ${err.message}`,
53
+ };
54
+ }
55
+ },
56
+ });
@@ -0,0 +1,56 @@
1
+ import { z } from "zod";
2
+ import { Address } from "@ton/core";
3
+ import { defineAction, toFriendlyAddress } from "@ton-agent-kit/core";
4
+ import { loadEscrows, saveEscrows, openDisputeOnContract, getLatestTxHash } from "../utils";
5
+
6
+ export const openDisputeAction = defineAction({
7
+ name: "open_dispute",
8
+ description:
9
+ "Open a dispute on an escrow deal. Freezes the escrow so only arbiter voting (or single-arbiter decision) can settle it. Only the depositor or beneficiary can open a dispute.",
10
+ schema: z.object({
11
+ escrowId: z.string().describe("Escrow ID to open a dispute on"),
12
+ }),
13
+ handler: async (agent, params) => {
14
+ const escrows = loadEscrows();
15
+ const escrow = escrows[params.escrowId];
16
+ if (!escrow) {
17
+ return { disputed: false, message: `Escrow not found: ${params.escrowId}` };
18
+ }
19
+
20
+ const contractAddress = Address.parse(escrow.contractAddress);
21
+
22
+ try {
23
+ await openDisputeOnContract(agent, contractAddress);
24
+
25
+ escrow.status = "disputed";
26
+ saveEscrows(escrows);
27
+
28
+ const txHash = await getLatestTxHash(
29
+ agent.wallet.address.toRawString(),
30
+ agent.network,
31
+ );
32
+
33
+ return {
34
+ disputed: true,
35
+ escrowId: params.escrowId,
36
+ contractAddress: escrow.contractAddress,
37
+ friendlyContract: toFriendlyAddress(contractAddress, agent.network),
38
+ arbiterCount: escrow.arbiterCount || 1,
39
+ arbiters: escrow.arbiters || [escrow.arbiter],
40
+ disputeTxHash: txHash,
41
+ message: `Dispute opened on escrow ${params.escrowId}. ${
42
+ (escrow.arbiterCount || 1) > 1
43
+ ? `Waiting for ${Math.floor((escrow.arbiterCount || 1) / 2) + 1}/${escrow.arbiterCount} arbiter votes.`
44
+ : "Single arbiter can now release or refund."
45
+ }`,
46
+ };
47
+ } catch (err: any) {
48
+ return {
49
+ disputed: false,
50
+ escrowId: params.escrowId,
51
+ error: err.message,
52
+ message: `Failed to open dispute: ${err.message}`,
53
+ };
54
+ }
55
+ },
56
+ });
@@ -44,6 +44,37 @@ export const refundEscrowAction = defineAction<{ escrowId: string }, any>({
44
44
  escrow.status = "refunded";
45
45
  saveEscrows(escrows);
46
46
 
47
+ // Save pending bidirectional ratings (non-critical)
48
+ // Refund = deal failed — negative ratings for both
49
+ try {
50
+ await (agent as any).runAction("save_context", {
51
+ key: `pending_rating_${params.escrowId}_buyer`,
52
+ namespace: "pending_ratings",
53
+ value: JSON.stringify({
54
+ escrowId: params.escrowId,
55
+ raterRole: "buyer",
56
+ targetAddress: escrow.beneficiary,
57
+ suggestedSuccess: false,
58
+ escrowOutcome: "refunded",
59
+ timestamp: Date.now(),
60
+ }),
61
+ });
62
+ } catch {}
63
+ try {
64
+ await (agent as any).runAction("save_context", {
65
+ key: `pending_rating_${params.escrowId}_seller`,
66
+ namespace: "pending_ratings",
67
+ value: JSON.stringify({
68
+ escrowId: params.escrowId,
69
+ raterRole: "seller",
70
+ targetAddress: escrow.depositor,
71
+ suggestedSuccess: false,
72
+ escrowOutcome: "refunded",
73
+ timestamp: Date.now(),
74
+ }),
75
+ });
76
+ } catch {}
77
+
47
78
  return {
48
79
  escrowId: params.escrowId,
49
80
  status: "refunded (on-chain)",
@@ -39,6 +39,36 @@ export const releaseEscrowAction = defineAction<{ escrowId: string }, any>({
39
39
  escrow.status = "released";
40
40
  saveEscrows(escrows);
41
41
 
42
+ // Save pending bidirectional ratings (non-critical)
43
+ try {
44
+ await (agent as any).runAction("save_context", {
45
+ key: `pending_rating_${params.escrowId}_buyer`,
46
+ namespace: "pending_ratings",
47
+ value: JSON.stringify({
48
+ escrowId: params.escrowId,
49
+ raterRole: "buyer",
50
+ targetAddress: escrow.beneficiary,
51
+ suggestedSuccess: true,
52
+ escrowOutcome: "released",
53
+ timestamp: Date.now(),
54
+ }),
55
+ });
56
+ } catch {}
57
+ try {
58
+ await (agent as any).runAction("save_context", {
59
+ key: `pending_rating_${params.escrowId}_seller`,
60
+ namespace: "pending_ratings",
61
+ value: JSON.stringify({
62
+ escrowId: params.escrowId,
63
+ raterRole: "seller",
64
+ targetAddress: escrow.depositor,
65
+ suggestedSuccess: true,
66
+ escrowOutcome: "released",
67
+ timestamp: Date.now(),
68
+ }),
69
+ });
70
+ } catch {}
71
+
42
72
  return {
43
73
  escrowId: params.escrowId,
44
74
  status: "released (on-chain)",