@ton-agent-kit/plugin-escrow 1.0.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 +17 -0
- package/src/actions/create-escrow.ts +112 -0
- package/src/actions/deposit-to-escrow.ts +54 -0
- package/src/actions/get-escrow-info.ts +83 -0
- package/src/actions/refund-escrow.ts +59 -0
- package/src/actions/release-escrow.ts +53 -0
- package/src/index.ts +37 -0
- package/src/utils.ts +177 -0
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ton-agent-kit/plugin-escrow",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Escrow plugin for TON Agent Kit — create, fund, release, and refund escrow deals",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"types": "src/index.ts",
|
|
7
|
+
"scripts": { "build": "tsc" },
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@ton-agent-kit/core": "^1.0.0",
|
|
10
|
+
"@ton/ton": "^16.2.0",
|
|
11
|
+
"@ton/core": "^0.63.1"
|
|
12
|
+
},
|
|
13
|
+
"keywords": ["ton", "blockchain", "ai", "agent", "sdk"],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"publishConfig": { "access": "public" },
|
|
16
|
+
"repository": { "type": "git", "url": "https://github.com/Andy00L/ton-agent-kit.git" }
|
|
17
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { Address } from "@ton/core";
|
|
3
|
+
import { defineAction, toFriendlyAddress } from "@ton-agent-kit/core";
|
|
4
|
+
import {
|
|
5
|
+
loadEscrows,
|
|
6
|
+
saveEscrows,
|
|
7
|
+
deployEscrowContract,
|
|
8
|
+
type EscrowRecord,
|
|
9
|
+
} from "../utils";
|
|
10
|
+
|
|
11
|
+
export const createEscrowAction = defineAction<
|
|
12
|
+
{
|
|
13
|
+
beneficiary: string;
|
|
14
|
+
amount: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
deadlineTimestamp?: number;
|
|
17
|
+
deadlineDays?: number;
|
|
18
|
+
deadlineHours?: number;
|
|
19
|
+
deadlineMinutes?: number;
|
|
20
|
+
},
|
|
21
|
+
any
|
|
22
|
+
>({
|
|
23
|
+
name: "create_escrow",
|
|
24
|
+
description:
|
|
25
|
+
"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
|
+
schema: z.object({
|
|
30
|
+
beneficiary: z
|
|
31
|
+
.string()
|
|
32
|
+
.describe("Address of the beneficiary (who receives funds)"),
|
|
33
|
+
amount: z.string().describe("Amount of TON to escrow"),
|
|
34
|
+
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."),
|
|
51
|
+
}) as any,
|
|
52
|
+
handler: async (agent, params) => {
|
|
53
|
+
const escrowId = `escrow_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
54
|
+
const now = Math.floor(Date.now() / 1000);
|
|
55
|
+
|
|
56
|
+
let deadline: number;
|
|
57
|
+
if (params.deadlineTimestamp) {
|
|
58
|
+
deadline = params.deadlineTimestamp;
|
|
59
|
+
} else if (params.deadlineDays) {
|
|
60
|
+
deadline = now + params.deadlineDays * 86400;
|
|
61
|
+
} else if (params.deadlineHours) {
|
|
62
|
+
deadline = now + params.deadlineHours * 3600;
|
|
63
|
+
} else {
|
|
64
|
+
deadline = now + (params.deadlineMinutes || 60) * 60;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const depositor = agent.wallet.address;
|
|
68
|
+
const beneficiary = Address.parse(params.beneficiary);
|
|
69
|
+
const arbiter = agent.wallet.address; // Agent is arbiter by default
|
|
70
|
+
|
|
71
|
+
// Deploy a new Escrow contract on-chain
|
|
72
|
+
const contractAddress = await deployEscrowContract(
|
|
73
|
+
agent,
|
|
74
|
+
depositor,
|
|
75
|
+
beneficiary,
|
|
76
|
+
arbiter,
|
|
77
|
+
BigInt(deadline),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// Save to JSON index (escrowId → contract address mapping)
|
|
81
|
+
const escrow: EscrowRecord = {
|
|
82
|
+
id: escrowId,
|
|
83
|
+
contractAddress: contractAddress.toRawString(),
|
|
84
|
+
depositor: depositor.toRawString(),
|
|
85
|
+
beneficiary: params.beneficiary,
|
|
86
|
+
arbiter: arbiter.toRawString(),
|
|
87
|
+
amount: params.amount,
|
|
88
|
+
deadline,
|
|
89
|
+
deadlineISO: new Date(deadline * 1000).toISOString(),
|
|
90
|
+
description: params.description || "",
|
|
91
|
+
status: "created",
|
|
92
|
+
createdAt: new Date().toISOString(),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const escrows = loadEscrows();
|
|
96
|
+
escrows[escrowId] = escrow;
|
|
97
|
+
saveEscrows(escrows);
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
escrowId,
|
|
101
|
+
status: "created (contract deployed on-chain)",
|
|
102
|
+
contractAddress: contractAddress.toRawString(),
|
|
103
|
+
friendlyContract: toFriendlyAddress(contractAddress, agent.network),
|
|
104
|
+
beneficiary: params.beneficiary,
|
|
105
|
+
friendlyBeneficiary: toFriendlyAddress(beneficiary, agent.network),
|
|
106
|
+
amount: params.amount + " TON",
|
|
107
|
+
deadline: escrow.deadlineISO,
|
|
108
|
+
description: params.description || "",
|
|
109
|
+
nextStep: `Deposit ${params.amount} TON using deposit_to_escrow with escrowId: ${escrowId}`,
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { Address } from "@ton/core";
|
|
3
|
+
import { defineAction, toFriendlyAddress } from "@ton-agent-kit/core";
|
|
4
|
+
import {
|
|
5
|
+
loadEscrows,
|
|
6
|
+
saveEscrows,
|
|
7
|
+
depositToContract,
|
|
8
|
+
getLatestTxHash,
|
|
9
|
+
} from "../utils";
|
|
10
|
+
|
|
11
|
+
export const depositToEscrowAction = defineAction<{ escrowId: string }, any>({
|
|
12
|
+
name: "deposit_to_escrow",
|
|
13
|
+
description:
|
|
14
|
+
"Deposit TON into an on-chain escrow contract. Sends TON to the deployed Escrow smart contract.",
|
|
15
|
+
schema: z.object({
|
|
16
|
+
escrowId: z.string().describe("Escrow ID from create_escrow"),
|
|
17
|
+
}) as any,
|
|
18
|
+
handler: async (agent, params) => {
|
|
19
|
+
const escrows = loadEscrows();
|
|
20
|
+
const escrow = escrows[params.escrowId];
|
|
21
|
+
if (!escrow) throw new Error(`Escrow not found: ${params.escrowId}`);
|
|
22
|
+
if (escrow.status !== "created")
|
|
23
|
+
throw new Error(`Escrow already ${escrow.status}`);
|
|
24
|
+
|
|
25
|
+
const contractAddress = Address.parse(escrow.contractAddress);
|
|
26
|
+
|
|
27
|
+
// Send Deposit message with TON to the escrow contract
|
|
28
|
+
await depositToContract(agent, contractAddress, escrow.amount);
|
|
29
|
+
|
|
30
|
+
// Wait for confirmation
|
|
31
|
+
await new Promise((r) => setTimeout(r, 10000));
|
|
32
|
+
|
|
33
|
+
const txHash = await getLatestTxHash(
|
|
34
|
+
agent.wallet.address.toRawString(),
|
|
35
|
+
agent.network,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
// Update index
|
|
39
|
+
escrow.status = "funded";
|
|
40
|
+
saveEscrows(escrows);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
escrowId: params.escrowId,
|
|
44
|
+
status: "funded (on-chain)",
|
|
45
|
+
contractAddress: escrow.contractAddress,
|
|
46
|
+
friendlyContract: toFriendlyAddress(contractAddress, agent.network),
|
|
47
|
+
amount: escrow.amount + " TON",
|
|
48
|
+
depositTxHash: txHash,
|
|
49
|
+
beneficiary: escrow.beneficiary,
|
|
50
|
+
friendlyBeneficiary: toFriendlyAddress(Address.parse(escrow.beneficiary), agent.network),
|
|
51
|
+
deadline: escrow.deadlineISO,
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { Address, fromNano } from "@ton/core";
|
|
3
|
+
import { defineAction, toFriendlyAddress } from "@ton-agent-kit/core";
|
|
4
|
+
import { loadEscrows, getContractState } from "../utils";
|
|
5
|
+
|
|
6
|
+
export const getEscrowInfoAction = defineAction<{ escrowId?: string }, any>({
|
|
7
|
+
name: "get_escrow_info",
|
|
8
|
+
description:
|
|
9
|
+
"Get escrow details. Reads on-chain contract state if available. " +
|
|
10
|
+
"If escrowId is provided, returns that escrow. If no escrowId, lists ALL escrows.",
|
|
11
|
+
schema: z.object({
|
|
12
|
+
escrowId: z
|
|
13
|
+
.string()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("Escrow ID. If not provided, lists all escrows."),
|
|
16
|
+
}) as any,
|
|
17
|
+
handler: async (agent, params) => {
|
|
18
|
+
const escrows = loadEscrows();
|
|
19
|
+
|
|
20
|
+
if (params.escrowId) {
|
|
21
|
+
const escrow = escrows[params.escrowId];
|
|
22
|
+
if (!escrow) throw new Error(`Escrow not found: ${params.escrowId}`);
|
|
23
|
+
|
|
24
|
+
// Try to read on-chain state
|
|
25
|
+
try {
|
|
26
|
+
const contractAddress = Address.parse(escrow.contractAddress);
|
|
27
|
+
const onChain = await getContractState(agent, contractAddress);
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
id: escrow.id,
|
|
31
|
+
contractAddress: escrow.contractAddress,
|
|
32
|
+
friendlyContract: toFriendlyAddress(Address.parse(escrow.contractAddress), agent.network),
|
|
33
|
+
description: escrow.description,
|
|
34
|
+
createdAt: escrow.createdAt,
|
|
35
|
+
onChain: {
|
|
36
|
+
depositor: toFriendlyAddress(onChain.depositor, agent.network),
|
|
37
|
+
beneficiary: toFriendlyAddress(onChain.beneficiary, agent.network),
|
|
38
|
+
arbiter: toFriendlyAddress(onChain.arbiter, agent.network),
|
|
39
|
+
amount: fromNano(onChain.amount) + " TON",
|
|
40
|
+
balance: fromNano(onChain.balance) + " TON",
|
|
41
|
+
deadline: new Date(Number(onChain.deadline) * 1000).toISOString(),
|
|
42
|
+
released: onChain.released,
|
|
43
|
+
refunded: onChain.refunded,
|
|
44
|
+
status: onChain.released
|
|
45
|
+
? "released"
|
|
46
|
+
: onChain.refunded
|
|
47
|
+
? "refunded"
|
|
48
|
+
: onChain.amount > BigInt(0)
|
|
49
|
+
? "funded"
|
|
50
|
+
: "created",
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
} catch (e: any) {
|
|
54
|
+
// Contract not yet active or network error — fall back to index
|
|
55
|
+
return {
|
|
56
|
+
...escrow,
|
|
57
|
+
onChainError: e.message || "Could not read contract state (may not be deployed yet)",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// List all escrows from index
|
|
63
|
+
const list = Object.values(escrows);
|
|
64
|
+
return {
|
|
65
|
+
count: list.length,
|
|
66
|
+
escrows: list.map((e) => ({
|
|
67
|
+
id: e.id,
|
|
68
|
+
status: e.status,
|
|
69
|
+
contractAddress: e.contractAddress || null,
|
|
70
|
+
friendlyContract: e.contractAddress
|
|
71
|
+
? toFriendlyAddress(Address.parse(e.contractAddress), agent.network)
|
|
72
|
+
: null,
|
|
73
|
+
amount: e.amount + " TON",
|
|
74
|
+
beneficiary: e.beneficiary,
|
|
75
|
+
friendlyBeneficiary: e.beneficiary
|
|
76
|
+
? toFriendlyAddress(Address.parse(e.beneficiary), agent.network)
|
|
77
|
+
: null,
|
|
78
|
+
deadline: e.deadlineISO,
|
|
79
|
+
description: e.description,
|
|
80
|
+
})),
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { Address } from "@ton/core";
|
|
3
|
+
import { defineAction, toFriendlyAddress } from "@ton-agent-kit/core";
|
|
4
|
+
import {
|
|
5
|
+
loadEscrows,
|
|
6
|
+
saveEscrows,
|
|
7
|
+
refundContract,
|
|
8
|
+
getLatestTxHash,
|
|
9
|
+
} from "../utils";
|
|
10
|
+
|
|
11
|
+
export const refundEscrowAction = defineAction<{ escrowId: string }, any>({
|
|
12
|
+
name: "refund_escrow",
|
|
13
|
+
description:
|
|
14
|
+
"Refund escrowed funds back to the depositor via the on-chain Escrow contract. " +
|
|
15
|
+
"Can refund if authorized (depositor/arbiter) or after deadline has passed.",
|
|
16
|
+
schema: z.object({
|
|
17
|
+
escrowId: z.string().describe("Escrow ID to refund"),
|
|
18
|
+
}) as any,
|
|
19
|
+
handler: async (agent, params) => {
|
|
20
|
+
const escrows = loadEscrows();
|
|
21
|
+
const escrow = escrows[params.escrowId];
|
|
22
|
+
if (!escrow) throw new Error(`Escrow not found: ${params.escrowId}`);
|
|
23
|
+
if (escrow.status !== "funded")
|
|
24
|
+
throw new Error(`Escrow is ${escrow.status}, must be funded`);
|
|
25
|
+
|
|
26
|
+
const contractAddress = Address.parse(escrow.contractAddress);
|
|
27
|
+
|
|
28
|
+
// Send Refund message to the escrow contract
|
|
29
|
+
// The contract itself enforces authorization (depositor/arbiter/past deadline)
|
|
30
|
+
await refundContract(agent, contractAddress);
|
|
31
|
+
|
|
32
|
+
// Wait for confirmation
|
|
33
|
+
await new Promise((r) => setTimeout(r, 10000));
|
|
34
|
+
|
|
35
|
+
const txHash = await getLatestTxHash(
|
|
36
|
+
agent.wallet.address.toRawString(),
|
|
37
|
+
agent.network,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const now = Math.floor(Date.now() / 1000);
|
|
41
|
+
const pastDeadline = now > escrow.deadline;
|
|
42
|
+
|
|
43
|
+
// Update index
|
|
44
|
+
escrow.status = "refunded";
|
|
45
|
+
saveEscrows(escrows);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
escrowId: params.escrowId,
|
|
49
|
+
status: "refunded (on-chain)",
|
|
50
|
+
contractAddress: escrow.contractAddress,
|
|
51
|
+
friendlyContract: toFriendlyAddress(contractAddress, agent.network),
|
|
52
|
+
amount: escrow.amount + " TON",
|
|
53
|
+
depositor: escrow.depositor,
|
|
54
|
+
friendlyDepositor: toFriendlyAddress(Address.parse(escrow.depositor), agent.network),
|
|
55
|
+
reason: pastDeadline ? "Deadline passed" : "Authorized refund",
|
|
56
|
+
refundTxHash: txHash,
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { Address } from "@ton/core";
|
|
3
|
+
import { defineAction, toFriendlyAddress } from "@ton-agent-kit/core";
|
|
4
|
+
import {
|
|
5
|
+
loadEscrows,
|
|
6
|
+
saveEscrows,
|
|
7
|
+
releaseContract,
|
|
8
|
+
getLatestTxHash,
|
|
9
|
+
} from "../utils";
|
|
10
|
+
|
|
11
|
+
export const releaseEscrowAction = defineAction<{ escrowId: string }, any>({
|
|
12
|
+
name: "release_escrow",
|
|
13
|
+
description:
|
|
14
|
+
"Release escrowed funds to the beneficiary via the on-chain Escrow contract. Only the depositor or arbiter can release.",
|
|
15
|
+
schema: z.object({
|
|
16
|
+
escrowId: z.string().describe("Escrow ID to release"),
|
|
17
|
+
}) as any,
|
|
18
|
+
handler: async (agent, params) => {
|
|
19
|
+
const escrows = loadEscrows();
|
|
20
|
+
const escrow = escrows[params.escrowId];
|
|
21
|
+
if (!escrow) throw new Error(`Escrow not found: ${params.escrowId}`);
|
|
22
|
+
if (escrow.status !== "funded")
|
|
23
|
+
throw new Error(`Escrow is ${escrow.status}, must be funded`);
|
|
24
|
+
|
|
25
|
+
const contractAddress = Address.parse(escrow.contractAddress);
|
|
26
|
+
|
|
27
|
+
// Send Release message to the escrow contract
|
|
28
|
+
await releaseContract(agent, contractAddress);
|
|
29
|
+
|
|
30
|
+
// Wait for confirmation
|
|
31
|
+
await new Promise((r) => setTimeout(r, 10000));
|
|
32
|
+
|
|
33
|
+
const txHash = await getLatestTxHash(
|
|
34
|
+
agent.wallet.address.toRawString(),
|
|
35
|
+
agent.network,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
// Update index
|
|
39
|
+
escrow.status = "released";
|
|
40
|
+
saveEscrows(escrows);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
escrowId: params.escrowId,
|
|
44
|
+
status: "released (on-chain)",
|
|
45
|
+
contractAddress: escrow.contractAddress,
|
|
46
|
+
friendlyContract: toFriendlyAddress(contractAddress, agent.network),
|
|
47
|
+
amount: escrow.amount + " TON",
|
|
48
|
+
beneficiary: escrow.beneficiary,
|
|
49
|
+
friendlyBeneficiary: toFriendlyAddress(Address.parse(escrow.beneficiary), agent.network),
|
|
50
|
+
releaseTxHash: txHash,
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { definePlugin } from "@ton-agent-kit/core";
|
|
2
|
+
import { createEscrowAction } from "./actions/create-escrow";
|
|
3
|
+
import { depositToEscrowAction } from "./actions/deposit-to-escrow";
|
|
4
|
+
import { releaseEscrowAction } from "./actions/release-escrow";
|
|
5
|
+
import { refundEscrowAction } from "./actions/refund-escrow";
|
|
6
|
+
import { getEscrowInfoAction } from "./actions/get-escrow-info";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Escrow Plugin — TON escrow deal management
|
|
10
|
+
*
|
|
11
|
+
* Actions:
|
|
12
|
+
* - create_escrow: Create a new escrow deal
|
|
13
|
+
* - deposit_to_escrow: Fund an escrow with TON
|
|
14
|
+
* - release_escrow: Release funds to beneficiary
|
|
15
|
+
* - refund_escrow: Refund funds to depositor
|
|
16
|
+
* - get_escrow_info: Get escrow details or list all
|
|
17
|
+
*/
|
|
18
|
+
const EscrowPlugin = definePlugin({
|
|
19
|
+
name: "escrow",
|
|
20
|
+
actions: [
|
|
21
|
+
createEscrowAction,
|
|
22
|
+
depositToEscrowAction,
|
|
23
|
+
releaseEscrowAction,
|
|
24
|
+
refundEscrowAction,
|
|
25
|
+
getEscrowInfoAction,
|
|
26
|
+
],
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export default EscrowPlugin;
|
|
30
|
+
|
|
31
|
+
export {
|
|
32
|
+
createEscrowAction,
|
|
33
|
+
depositToEscrowAction,
|
|
34
|
+
releaseEscrowAction,
|
|
35
|
+
refundEscrowAction,
|
|
36
|
+
getEscrowInfoAction,
|
|
37
|
+
};
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import { Address, internal, toNano, beginCell } from "@ton/core";
|
|
3
|
+
import { TonClient4 } from "@ton/ton";
|
|
4
|
+
import type { AgentContext } from "@ton-agent-kit/core";
|
|
5
|
+
import {
|
|
6
|
+
Escrow,
|
|
7
|
+
storeDeploy,
|
|
8
|
+
storeDeposit,
|
|
9
|
+
storeRelease,
|
|
10
|
+
storeRefund,
|
|
11
|
+
} from "../../../contracts/output/Escrow_Escrow";
|
|
12
|
+
import { sendTransaction } from "@ton-agent-kit/core";
|
|
13
|
+
|
|
14
|
+
const ESCROW_FILE = ".escrow-store.json";
|
|
15
|
+
|
|
16
|
+
// ── JSON index (escrowId → metadata + contract address) ───────────────────
|
|
17
|
+
|
|
18
|
+
export interface EscrowRecord {
|
|
19
|
+
id: string;
|
|
20
|
+
contractAddress: string;
|
|
21
|
+
depositor: string;
|
|
22
|
+
beneficiary: string;
|
|
23
|
+
arbiter: string;
|
|
24
|
+
amount: string;
|
|
25
|
+
deadline: number;
|
|
26
|
+
deadlineISO: string;
|
|
27
|
+
description: string;
|
|
28
|
+
status: string;
|
|
29
|
+
createdAt: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function loadEscrows(): Record<string, EscrowRecord> {
|
|
33
|
+
try {
|
|
34
|
+
if (existsSync(ESCROW_FILE)) {
|
|
35
|
+
return JSON.parse(readFileSync(ESCROW_FILE, "utf-8"));
|
|
36
|
+
}
|
|
37
|
+
} catch (err: any) {
|
|
38
|
+
console.error(`Failed to load escrow store: ${err.message}`);
|
|
39
|
+
}
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function saveEscrows(escrows: Record<string, EscrowRecord>): void {
|
|
44
|
+
try {
|
|
45
|
+
writeFileSync(ESCROW_FILE, JSON.stringify(escrows, null, 2), "utf-8");
|
|
46
|
+
} catch (err: any) {
|
|
47
|
+
console.error(`Failed to save escrow store: ${err.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── On-chain helpers ──────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Deploy a new Escrow contract instance and return the contract address.
|
|
55
|
+
*/
|
|
56
|
+
export async function deployEscrowContract(
|
|
57
|
+
agent: AgentContext,
|
|
58
|
+
depositor: Address,
|
|
59
|
+
beneficiary: Address,
|
|
60
|
+
arbiter: Address,
|
|
61
|
+
deadline: bigint,
|
|
62
|
+
): Promise<Address> {
|
|
63
|
+
const escrow = await Escrow.fromInit(depositor, beneficiary, arbiter, deadline);
|
|
64
|
+
|
|
65
|
+
const deployBody = beginCell()
|
|
66
|
+
.store(storeDeploy({ $$type: "Deploy", queryId: BigInt(0) }))
|
|
67
|
+
.endCell();
|
|
68
|
+
|
|
69
|
+
await sendTransaction(agent, [
|
|
70
|
+
internal({
|
|
71
|
+
to: escrow.address,
|
|
72
|
+
value: toNano("0.05"),
|
|
73
|
+
bounce: false,
|
|
74
|
+
init: escrow.init!,
|
|
75
|
+
body: deployBody,
|
|
76
|
+
}),
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
return escrow.address;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Send a Deposit message (with TON attached) to an escrow contract.
|
|
84
|
+
*/
|
|
85
|
+
export async function depositToContract(
|
|
86
|
+
agent: AgentContext,
|
|
87
|
+
contractAddress: Address,
|
|
88
|
+
amount: string,
|
|
89
|
+
): Promise<void> {
|
|
90
|
+
const body = beginCell()
|
|
91
|
+
.store(storeDeposit({ $$type: "Deposit", queryId: BigInt(0) }))
|
|
92
|
+
.endCell();
|
|
93
|
+
|
|
94
|
+
await sendTransaction(agent, [
|
|
95
|
+
internal({
|
|
96
|
+
to: contractAddress,
|
|
97
|
+
value: toNano(amount),
|
|
98
|
+
bounce: true,
|
|
99
|
+
body,
|
|
100
|
+
}),
|
|
101
|
+
]);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Send a Release message to an escrow contract.
|
|
106
|
+
*/
|
|
107
|
+
export async function releaseContract(
|
|
108
|
+
agent: AgentContext,
|
|
109
|
+
contractAddress: Address,
|
|
110
|
+
): Promise<void> {
|
|
111
|
+
const body = beginCell()
|
|
112
|
+
.store(storeRelease({ $$type: "Release", queryId: BigInt(0) }))
|
|
113
|
+
.endCell();
|
|
114
|
+
|
|
115
|
+
await sendTransaction(agent, [
|
|
116
|
+
internal({
|
|
117
|
+
to: contractAddress,
|
|
118
|
+
value: toNano("0.02"),
|
|
119
|
+
bounce: true,
|
|
120
|
+
body,
|
|
121
|
+
}),
|
|
122
|
+
]);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Send a Refund message to an escrow contract.
|
|
127
|
+
*/
|
|
128
|
+
export async function refundContract(
|
|
129
|
+
agent: AgentContext,
|
|
130
|
+
contractAddress: Address,
|
|
131
|
+
): Promise<void> {
|
|
132
|
+
const body = beginCell()
|
|
133
|
+
.store(storeRefund({ $$type: "Refund", queryId: BigInt(0) }))
|
|
134
|
+
.endCell();
|
|
135
|
+
|
|
136
|
+
await sendTransaction(agent, [
|
|
137
|
+
internal({
|
|
138
|
+
to: contractAddress,
|
|
139
|
+
value: toNano("0.02"),
|
|
140
|
+
bounce: true,
|
|
141
|
+
body,
|
|
142
|
+
}),
|
|
143
|
+
]);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Read on-chain escrow state via get method.
|
|
148
|
+
*/
|
|
149
|
+
export async function getContractState(agent: AgentContext, contractAddress: Address) {
|
|
150
|
+
const client = new TonClient4({ endpoint: agent.rpcUrl });
|
|
151
|
+
const contract = client.open(Escrow.fromAddress(contractAddress));
|
|
152
|
+
const data = await contract.getEscrowData();
|
|
153
|
+
const balance = await contract.getBalance();
|
|
154
|
+
return { ...data, balance };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Look up the latest tx hash for an address via TonAPI.
|
|
159
|
+
*/
|
|
160
|
+
export async function getLatestTxHash(
|
|
161
|
+
address: string,
|
|
162
|
+
network: "testnet" | "mainnet",
|
|
163
|
+
): Promise<string> {
|
|
164
|
+
const apiBase =
|
|
165
|
+
network === "testnet"
|
|
166
|
+
? "https://testnet.tonapi.io/v2"
|
|
167
|
+
: "https://tonapi.io/v2";
|
|
168
|
+
try {
|
|
169
|
+
const res = await fetch(
|
|
170
|
+
`${apiBase}/accounts/${encodeURIComponent(address)}/events?limit=1`,
|
|
171
|
+
);
|
|
172
|
+
const data = await res.json();
|
|
173
|
+
return data.events?.[0]?.event_id || "pending";
|
|
174
|
+
} catch {
|
|
175
|
+
return "pending";
|
|
176
|
+
}
|
|
177
|
+
}
|