moltlaunch 2.0.0 → 2.0.2
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 +2 -2
- package/dist/index.js +18 -18
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
- package/.claude/commands/deploy.md +0 -33
- package/.claude/hooks/regenerate-docs.sh +0 -12
- package/.claude/settings.json +0 -15
- package/.env.example +0 -2
- package/.github/workflows/deploy.yml +0 -37
- package/ROADMAP.md +0 -29
- package/contracts/MandateEscrowV4.sol +0 -281
- package/contracts/mocks/MockFlaunchBuyback.sol +0 -24
- package/hardhat.config.cjs +0 -29
- package/scripts/check-deploy-cost.ts +0 -15
- package/scripts/deploy-escrow-v4.ts +0 -81
- package/scripts/deploy-escrow.cjs +0 -22
- package/scripts/generate-docs.ts +0 -309
- package/shared/manifest.json +0 -87
- package/site/.vscode/extensions.json +0 -4
- package/site/.vscode/launch.json +0 -11
- package/site/README.md +0 -43
- package/site/astro.config.mjs +0 -21
- package/site/functions/agent/[[path]].ts +0 -9
- package/site/functions/task/[[path]].ts +0 -9
- package/site/index.html.bak +0 -1755
- package/site/package-lock.json +0 -6165
- package/site/package.json +0 -17
- package/site/public/_redirects +0 -1
- package/site/public/art/hero.webp +0 -0
- package/site/public/favicon.ico +0 -0
- package/site/public/favicon.svg +0 -4
- package/site/public/logo.png +0 -0
- package/site/public/skill.md +0 -276
- package/site/src/components/AgentGridCard.astro +0 -97
- package/site/src/components/AgentRow.astro +0 -75
- package/site/src/components/Footer.astro +0 -71
- package/site/src/components/GigCard.astro +0 -36
- package/site/src/components/Navbar.astro +0 -93
- package/site/src/components/ReviewCard.astro +0 -29
- package/site/src/components/SkillPill.astro +0 -19
- package/site/src/components/StatusBadge.astro +0 -27
- package/site/src/components/TaskEntry.astro +0 -98
- package/site/src/layouts/Layout.astro +0 -268
- package/site/src/lib/api.ts +0 -342
- package/site/src/pages/404.astro +0 -33
- package/site/src/pages/admin.astro +0 -445
- package/site/src/pages/agent/[...id].astro +0 -678
- package/site/src/pages/agents/index.astro +0 -235
- package/site/src/pages/dashboard.astro +0 -244
- package/site/src/pages/docs.astro +0 -191
- package/site/src/pages/how.astro +0 -156
- package/site/src/pages/index.astro +0 -226
- package/site/src/pages/leaderboard.astro +0 -155
- package/site/src/pages/task/[...id].astro +0 -1467
- package/site/src/styles/global.css +0 -159
- package/site/tailwind.config.mjs +0 -94
- package/site/tsconfig.json +0 -5
- package/site/wrangler.toml +0 -5
- package/src/commands/accept.ts +0 -135
- package/src/commands/agents.ts +0 -190
- package/src/commands/approve.ts +0 -127
- package/src/commands/claim.ts +0 -130
- package/src/commands/decline.ts +0 -55
- package/src/commands/dispute.ts +0 -92
- package/src/commands/earnings.ts +0 -86
- package/src/commands/feedback.ts +0 -147
- package/src/commands/gig.ts +0 -141
- package/src/commands/hire.ts +0 -96
- package/src/commands/inbox.ts +0 -135
- package/src/commands/message.ts +0 -97
- package/src/commands/profile.ts +0 -62
- package/src/commands/quote.ts +0 -80
- package/src/commands/refund.ts +0 -82
- package/src/commands/register.ts +0 -250
- package/src/commands/resolve.ts +0 -104
- package/src/commands/reviews.ts +0 -78
- package/src/commands/revise.ts +0 -65
- package/src/commands/submit.ts +0 -123
- package/src/commands/tasks.ts +0 -224
- package/src/commands/view.ts +0 -122
- package/src/commands/wallet.ts +0 -42
- package/src/index.ts +0 -285
- package/src/lib/agent0.ts +0 -158
- package/src/lib/auth.ts +0 -25
- package/src/lib/constants.ts +0 -55
- package/src/lib/escrow.ts +0 -374
- package/src/lib/files.ts +0 -87
- package/src/lib/flaunch.ts +0 -277
- package/src/lib/mandate.ts +0 -623
- package/src/lib/tasks.ts +0 -466
- package/src/lib/types.ts +0 -112
- package/src/lib/wallet.ts +0 -119
- package/src/lib/x402.ts +0 -86
- package/test/MandateEscrowV4.test.cjs +0 -568
- package/tsconfig.json +0 -19
- package/tsup.config.ts +0 -15
- package/worker/package-lock.json +0 -1812
- package/worker/package.json +0 -18
- package/worker/src/agents.ts +0 -755
- package/worker/src/auth.ts +0 -126
- package/worker/src/files.ts +0 -40
- package/worker/src/index.ts +0 -963
- package/worker/src/profiles.ts +0 -85
- package/worker/src/ratelimit.ts +0 -45
- package/worker/src/tasks.ts +0 -498
- package/worker/src/types.ts +0 -95
- package/worker/tsconfig.json +0 -15
- package/worker/wrangler.toml +0 -19
package/src/lib/x402.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
// x402: HTTP 402 Payment Protocol Client
|
|
2
|
-
// Using official @x402 SDK from Coinbase
|
|
3
|
-
|
|
4
|
-
import { wrapFetchWithPayment, x402Client } from "@x402/fetch";
|
|
5
|
-
import { ExactEvmScheme, toClientEvmSigner } from "@x402/evm";
|
|
6
|
-
import { privateKeyToAccount } from "viem/accounts";
|
|
7
|
-
import type { Wallet, HireResult } from "./types.js";
|
|
8
|
-
|
|
9
|
-
// Base mainnet network ID (CAIP-2 format)
|
|
10
|
-
const BASE_NETWORK = "eip155:8453";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Create a payment-enabled fetch function for a wallet
|
|
14
|
-
*/
|
|
15
|
-
export function createPayingFetch(wallet: Wallet) {
|
|
16
|
-
// Create viem account (matches ClientEvmSigner interface)
|
|
17
|
-
const account = privateKeyToAccount(wallet.privateKey);
|
|
18
|
-
|
|
19
|
-
// Create x402 client with EVM scheme for Base
|
|
20
|
-
const client = new x402Client()
|
|
21
|
-
.register(BASE_NETWORK, new ExactEvmScheme(account));
|
|
22
|
-
|
|
23
|
-
// Wrap fetch to automatically handle 402 payments
|
|
24
|
-
return wrapFetchWithPayment(fetch, client);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Hire an agent via x402 protocol using official SDK
|
|
29
|
-
*
|
|
30
|
-
* Flow:
|
|
31
|
-
* 1. POST to agent endpoint with task
|
|
32
|
-
* 2. If 402, SDK automatically handles payment
|
|
33
|
-
* 3. Returns result after payment settles
|
|
34
|
-
*/
|
|
35
|
-
export async function hireAgent(
|
|
36
|
-
wallet: Wallet,
|
|
37
|
-
endpoint: string,
|
|
38
|
-
task: string,
|
|
39
|
-
_maxBudget?: bigint
|
|
40
|
-
): Promise<HireResult> {
|
|
41
|
-
try {
|
|
42
|
-
// Get payment-enabled fetch
|
|
43
|
-
const payingFetch = createPayingFetch(wallet);
|
|
44
|
-
|
|
45
|
-
// Make request - SDK handles 402 automatically
|
|
46
|
-
const response = await payingFetch(endpoint, {
|
|
47
|
-
method: "POST",
|
|
48
|
-
headers: {
|
|
49
|
-
"Content-Type": "application/json",
|
|
50
|
-
},
|
|
51
|
-
body: JSON.stringify({ task }),
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
if (response.ok) {
|
|
55
|
-
const result = await response.json();
|
|
56
|
-
return {
|
|
57
|
-
success: true,
|
|
58
|
-
result: result.result,
|
|
59
|
-
subcontracts: result.subcontracts,
|
|
60
|
-
};
|
|
61
|
-
} else {
|
|
62
|
-
return {
|
|
63
|
-
success: false,
|
|
64
|
-
error: `Request failed: ${response.status} ${response.statusText}`,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
} catch (err) {
|
|
68
|
-
return {
|
|
69
|
-
success: false,
|
|
70
|
-
error: `Hire failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Expose x402Client for advanced usage
|
|
77
|
-
*/
|
|
78
|
-
export function getX402Client(wallet: Wallet): x402Client {
|
|
79
|
-
const account = privateKeyToAccount(wallet.privateKey);
|
|
80
|
-
return new x402Client()
|
|
81
|
-
.register(BASE_NETWORK, new ExactEvmScheme(account));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Re-export types for convenience
|
|
85
|
-
export { x402Client } from "@x402/fetch";
|
|
86
|
-
export { ExactEvmScheme } from "@x402/evm";
|
|
@@ -1,568 +0,0 @@
|
|
|
1
|
-
const { expect } = require("chai");
|
|
2
|
-
const { ethers } = require("hardhat");
|
|
3
|
-
const { time } = require("@nomicfoundation/hardhat-network-helpers");
|
|
4
|
-
|
|
5
|
-
describe("MandateEscrowV4", function () {
|
|
6
|
-
let escrow, mockBuyback, failingBuyback;
|
|
7
|
-
let admin, client, agent, other;
|
|
8
|
-
let token;
|
|
9
|
-
const TASK_ID = ethers.keccak256(ethers.toUtf8Bytes("test-task-v4-1"));
|
|
10
|
-
const TIMEOUT = 24 * 60 * 60; // 24 hours in seconds
|
|
11
|
-
|
|
12
|
-
beforeEach(async function () {
|
|
13
|
-
[admin, client, agent, other] = await ethers.getSigners();
|
|
14
|
-
token = other.address;
|
|
15
|
-
|
|
16
|
-
const MockBuyback = await ethers.getContractFactory("MockFlaunchBuyback");
|
|
17
|
-
mockBuyback = await MockBuyback.deploy();
|
|
18
|
-
|
|
19
|
-
const FailingBuyback = await ethers.getContractFactory("FailingBuyback");
|
|
20
|
-
failingBuyback = await FailingBuyback.deploy();
|
|
21
|
-
|
|
22
|
-
const Escrow = await ethers.getContractFactory("MandateEscrowV4");
|
|
23
|
-
escrow = await Escrow.deploy(await mockBuyback.getAddress());
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
// ─── Deposit ───────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
describe("Deposit", function () {
|
|
29
|
-
it("should deposit successfully", async function () {
|
|
30
|
-
const amount = ethers.parseEther("0.1");
|
|
31
|
-
await expect(
|
|
32
|
-
escrow.connect(client).deposit(TASK_ID, agent.address, token, { value: amount })
|
|
33
|
-
)
|
|
34
|
-
.to.emit(escrow, "Deposited")
|
|
35
|
-
.withArgs(TASK_ID, client.address, token, amount);
|
|
36
|
-
|
|
37
|
-
const e = await escrow.getEscrow(TASK_ID);
|
|
38
|
-
expect(e.client).to.equal(client.address);
|
|
39
|
-
expect(e.agent).to.equal(agent.address);
|
|
40
|
-
expect(e.amount).to.equal(amount);
|
|
41
|
-
expect(e.status).to.equal(0); // Active
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("should revert on zero value", async function () {
|
|
45
|
-
await expect(
|
|
46
|
-
escrow.connect(client).deposit(TASK_ID, agent.address, token, { value: 0 })
|
|
47
|
-
).to.be.revertedWith("No value");
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("should revert on zero-address agent", async function () {
|
|
51
|
-
await expect(
|
|
52
|
-
escrow.connect(client).deposit(TASK_ID, ethers.ZeroAddress, token, {
|
|
53
|
-
value: ethers.parseEther("0.1"),
|
|
54
|
-
})
|
|
55
|
-
).to.be.revertedWith("Invalid agent");
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("should revert on zero-address token", async function () {
|
|
59
|
-
await expect(
|
|
60
|
-
escrow.connect(client).deposit(TASK_ID, agent.address, ethers.ZeroAddress, {
|
|
61
|
-
value: ethers.parseEther("0.1"),
|
|
62
|
-
})
|
|
63
|
-
).to.be.revertedWith("Invalid token");
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it("should revert on duplicate deposit", async function () {
|
|
67
|
-
const amount = ethers.parseEther("0.1");
|
|
68
|
-
await escrow.connect(client).deposit(TASK_ID, agent.address, token, { value: amount });
|
|
69
|
-
|
|
70
|
-
await expect(
|
|
71
|
-
escrow.connect(client).deposit(TASK_ID, agent.address, token, { value: amount })
|
|
72
|
-
).to.be.revertedWith("Task exists");
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// ─── Mark Submitted ────────────────────────────────────────
|
|
77
|
-
|
|
78
|
-
describe("Mark Submitted", function () {
|
|
79
|
-
beforeEach(async function () {
|
|
80
|
-
await escrow.connect(client).deposit(TASK_ID, agent.address, token, {
|
|
81
|
-
value: ethers.parseEther("0.1"),
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it("should mark submitted and emit event", async function () {
|
|
86
|
-
const tx = await escrow.connect(agent).markSubmitted(TASK_ID);
|
|
87
|
-
const receipt = await tx.wait();
|
|
88
|
-
const block = await ethers.provider.getBlock(receipt.blockNumber);
|
|
89
|
-
|
|
90
|
-
await expect(tx)
|
|
91
|
-
.to.emit(escrow, "Submitted")
|
|
92
|
-
.withArgs(TASK_ID, agent.address, block.timestamp + TIMEOUT);
|
|
93
|
-
|
|
94
|
-
expect(await escrow.getStatus(TASK_ID)).to.equal(1); // Submitted
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("should revert if caller is not agent", async function () {
|
|
98
|
-
await expect(
|
|
99
|
-
escrow.connect(other).markSubmitted(TASK_ID)
|
|
100
|
-
).to.be.revertedWith("Not agent");
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it("should revert on double submission", async function () {
|
|
104
|
-
await escrow.connect(agent).markSubmitted(TASK_ID);
|
|
105
|
-
await expect(
|
|
106
|
-
escrow.connect(agent).markSubmitted(TASK_ID)
|
|
107
|
-
).to.be.revertedWith("Wrong status");
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// ─── Release ───────────────────────────────────────────────
|
|
112
|
-
|
|
113
|
-
describe("Release", function () {
|
|
114
|
-
beforeEach(async function () {
|
|
115
|
-
await escrow.connect(client).deposit(TASK_ID, agent.address, token, {
|
|
116
|
-
value: ethers.parseEther("1"),
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it("should release and call buyback", async function () {
|
|
121
|
-
await expect(escrow.connect(client).release(TASK_ID))
|
|
122
|
-
.to.emit(escrow, "BuybackBurned")
|
|
123
|
-
.withArgs(TASK_ID, token, ethers.parseEther("1"));
|
|
124
|
-
|
|
125
|
-
expect(await mockBuyback.callCount()).to.equal(1);
|
|
126
|
-
expect(await mockBuyback.lastToken()).to.equal(token);
|
|
127
|
-
expect(await mockBuyback.lastValue()).to.equal(ethers.parseEther("1"));
|
|
128
|
-
|
|
129
|
-
const e = await escrow.getEscrow(TASK_ID);
|
|
130
|
-
expect(e.status).to.equal(4); // Released
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("should fallback to agent when buyback fails", async function () {
|
|
134
|
-
await escrow.connect(admin).setBuybackHandler(await failingBuyback.getAddress());
|
|
135
|
-
|
|
136
|
-
const agentBalanceBefore = await ethers.provider.getBalance(agent.address);
|
|
137
|
-
|
|
138
|
-
await expect(escrow.connect(client).release(TASK_ID))
|
|
139
|
-
.to.emit(escrow, "FallbackToAgent")
|
|
140
|
-
.withArgs(TASK_ID, agent.address, ethers.parseEther("1"));
|
|
141
|
-
|
|
142
|
-
const agentBalanceAfter = await ethers.provider.getBalance(agent.address);
|
|
143
|
-
expect(agentBalanceAfter - agentBalanceBefore).to.equal(ethers.parseEther("1"));
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it("should revert if caller is not client", async function () {
|
|
147
|
-
await expect(
|
|
148
|
-
escrow.connect(other).release(TASK_ID)
|
|
149
|
-
).to.be.revertedWith("Not client");
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it("should revert on double release", async function () {
|
|
153
|
-
await escrow.connect(client).release(TASK_ID);
|
|
154
|
-
await expect(
|
|
155
|
-
escrow.connect(client).release(TASK_ID)
|
|
156
|
-
).to.be.revertedWith("Wrong status");
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
// ─── Refund ────────────────────────────────────────────────
|
|
161
|
-
|
|
162
|
-
describe("Refund", function () {
|
|
163
|
-
beforeEach(async function () {
|
|
164
|
-
await escrow.connect(client).deposit(TASK_ID, agent.address, token, {
|
|
165
|
-
value: ethers.parseEther("0.5"),
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it("should refund before submission", async function () {
|
|
170
|
-
const clientBalanceBefore = await ethers.provider.getBalance(client.address);
|
|
171
|
-
|
|
172
|
-
const tx = await escrow.connect(client).refund(TASK_ID);
|
|
173
|
-
const receipt = await tx.wait();
|
|
174
|
-
const gasUsed = receipt.gasUsed * receipt.gasPrice;
|
|
175
|
-
|
|
176
|
-
await expect(tx)
|
|
177
|
-
.to.emit(escrow, "Refunded")
|
|
178
|
-
.withArgs(TASK_ID, client.address, ethers.parseEther("0.5"));
|
|
179
|
-
|
|
180
|
-
const clientBalanceAfter = await ethers.provider.getBalance(client.address);
|
|
181
|
-
expect(clientBalanceAfter + gasUsed - clientBalanceBefore).to.equal(
|
|
182
|
-
ethers.parseEther("0.5")
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
const e = await escrow.getEscrow(TASK_ID);
|
|
186
|
-
expect(e.status).to.equal(5); // Refunded
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it("should block refund after submission", async function () {
|
|
190
|
-
await escrow.connect(agent).markSubmitted(TASK_ID);
|
|
191
|
-
await expect(
|
|
192
|
-
escrow.connect(client).refund(TASK_ID)
|
|
193
|
-
).to.be.revertedWith("Work already submitted");
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it("should revert if caller is not client", async function () {
|
|
197
|
-
await expect(
|
|
198
|
-
escrow.connect(other).refund(TASK_ID)
|
|
199
|
-
).to.be.revertedWith("Not client");
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// ─── Timeout ───────────────────────────────────────────────
|
|
204
|
-
|
|
205
|
-
describe("Timeout", function () {
|
|
206
|
-
beforeEach(async function () {
|
|
207
|
-
await escrow.connect(client).deposit(TASK_ID, agent.address, token, {
|
|
208
|
-
value: ethers.parseEther("1"),
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it("should release after 24h timeout", async function () {
|
|
213
|
-
await escrow.connect(agent).markSubmitted(TASK_ID);
|
|
214
|
-
|
|
215
|
-
await time.increase(TIMEOUT);
|
|
216
|
-
|
|
217
|
-
expect(await escrow.isTimedOut(TASK_ID)).to.be.true;
|
|
218
|
-
|
|
219
|
-
await expect(escrow.connect(other).releaseAfterTimeout(TASK_ID))
|
|
220
|
-
.to.emit(escrow, "BuybackBurned");
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it("should block release before timeout", async function () {
|
|
224
|
-
await escrow.connect(agent).markSubmitted(TASK_ID);
|
|
225
|
-
|
|
226
|
-
await expect(
|
|
227
|
-
escrow.connect(other).releaseAfterTimeout(TASK_ID)
|
|
228
|
-
).to.be.revertedWith("Timeout not reached");
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it("should block release if not submitted", async function () {
|
|
232
|
-
await expect(
|
|
233
|
-
escrow.connect(other).releaseAfterTimeout(TASK_ID)
|
|
234
|
-
).to.be.revertedWith("Wrong status");
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
// ─── Admin ─────────────────────────────────────────────────
|
|
239
|
-
|
|
240
|
-
describe("Admin", function () {
|
|
241
|
-
it("should allow admin to set buyback handler", async function () {
|
|
242
|
-
await escrow.connect(admin).setBuybackHandler(await failingBuyback.getAddress());
|
|
243
|
-
expect(await escrow.buybackHandler()).to.equal(await failingBuyback.getAddress());
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it("should allow admin to transfer admin", async function () {
|
|
247
|
-
await escrow.connect(admin).transferAdmin(other.address);
|
|
248
|
-
expect(await escrow.admin()).to.equal(other.address);
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
it("should revert non-admin setBuybackHandler", async function () {
|
|
252
|
-
await expect(
|
|
253
|
-
escrow.connect(other).setBuybackHandler(await failingBuyback.getAddress())
|
|
254
|
-
).to.be.revertedWith("Not admin");
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
it("should allow admin to setDisputeFeeBps", async function () {
|
|
258
|
-
await escrow.connect(admin).setDisputeFeeBps(500);
|
|
259
|
-
expect(await escrow.disputeFeeBps()).to.equal(500);
|
|
260
|
-
|
|
261
|
-
await escrow.connect(admin).setDisputeFeeBps(3000);
|
|
262
|
-
expect(await escrow.disputeFeeBps()).to.equal(3000);
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
it("should revert non-admin setDisputeFeeBps", async function () {
|
|
266
|
-
await expect(
|
|
267
|
-
escrow.connect(other).setDisputeFeeBps(500)
|
|
268
|
-
).to.be.revertedWith("Not admin");
|
|
269
|
-
});
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
// ─── View Functions ────────────────────────────────────────
|
|
273
|
-
|
|
274
|
-
describe("View Functions", function () {
|
|
275
|
-
beforeEach(async function () {
|
|
276
|
-
await escrow.connect(client).deposit(TASK_ID, agent.address, token, {
|
|
277
|
-
value: ethers.parseEther("1"),
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it("isPending should return true for active escrow", async function () {
|
|
282
|
-
expect(await escrow.isPending(TASK_ID)).to.be.true;
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
it("isPending should return false after release", async function () {
|
|
286
|
-
await escrow.connect(client).release(TASK_ID);
|
|
287
|
-
expect(await escrow.isPending(TASK_ID)).to.be.false;
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
it("getStatus should track status changes", async function () {
|
|
291
|
-
expect(await escrow.getStatus(TASK_ID)).to.equal(0); // Active
|
|
292
|
-
await escrow.connect(agent).markSubmitted(TASK_ID);
|
|
293
|
-
expect(await escrow.getStatus(TASK_ID)).to.equal(1); // Submitted
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
it("isTimedOut should return false before submission", async function () {
|
|
297
|
-
expect(await escrow.isTimedOut(TASK_ID)).to.be.false;
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
it("timeUntilTimeout should countdown correctly", async function () {
|
|
301
|
-
await escrow.connect(agent).markSubmitted(TASK_ID);
|
|
302
|
-
const remaining = await escrow.timeUntilTimeout(TASK_ID);
|
|
303
|
-
expect(remaining).to.be.closeTo(TIMEOUT, 10);
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
it("getDisputeFee should return correct fee", async function () {
|
|
307
|
-
const fee = await escrow.getDisputeFee(TASK_ID);
|
|
308
|
-
// 10% of 1 ETH = 0.1 ETH
|
|
309
|
-
expect(fee).to.equal(ethers.parseEther("0.1"));
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
it("isDisputed should return false initially", async function () {
|
|
313
|
-
expect(await escrow.isDisputed(TASK_ID)).to.be.false;
|
|
314
|
-
});
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
// ─── Dispute ───────────────────────────────────────────────
|
|
318
|
-
|
|
319
|
-
describe("Dispute", function () {
|
|
320
|
-
const escrowAmount = ethers.parseEther("1");
|
|
321
|
-
|
|
322
|
-
beforeEach(async function () {
|
|
323
|
-
await escrow.connect(client).deposit(TASK_ID, agent.address, token, {
|
|
324
|
-
value: escrowAmount,
|
|
325
|
-
});
|
|
326
|
-
await escrow.connect(agent).markSubmitted(TASK_ID);
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
it("should allow client to dispute submitted work", async function () {
|
|
330
|
-
const disputeFee = await escrow.getDisputeFee(TASK_ID);
|
|
331
|
-
|
|
332
|
-
await expect(
|
|
333
|
-
escrow.connect(client).dispute(TASK_ID, { value: disputeFee })
|
|
334
|
-
)
|
|
335
|
-
.to.emit(escrow, "Disputed")
|
|
336
|
-
.withArgs(TASK_ID, client.address, disputeFee);
|
|
337
|
-
|
|
338
|
-
expect(await escrow.getStatus(TASK_ID)).to.equal(2); // Disputed
|
|
339
|
-
expect(await escrow.isDisputed(TASK_ID)).to.be.true;
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
it("should revert if not client", async function () {
|
|
343
|
-
const disputeFee = await escrow.getDisputeFee(TASK_ID);
|
|
344
|
-
await expect(
|
|
345
|
-
escrow.connect(other).dispute(TASK_ID, { value: disputeFee })
|
|
346
|
-
).to.be.revertedWith("Not client");
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
it("should revert if not submitted", async function () {
|
|
350
|
-
const taskId2 = ethers.keccak256(ethers.toUtf8Bytes("test-task-v4-2"));
|
|
351
|
-
await escrow.connect(client).deposit(taskId2, agent.address, token, {
|
|
352
|
-
value: escrowAmount,
|
|
353
|
-
});
|
|
354
|
-
// Not submitted yet
|
|
355
|
-
const disputeFee = await escrow.getDisputeFee(taskId2);
|
|
356
|
-
await expect(
|
|
357
|
-
escrow.connect(client).dispute(taskId2, { value: disputeFee })
|
|
358
|
-
).to.be.revertedWith("Not submitted");
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
it("should revert if insufficient fee", async function () {
|
|
362
|
-
const disputeFee = await escrow.getDisputeFee(TASK_ID);
|
|
363
|
-
const insufficientFee = disputeFee - 1n;
|
|
364
|
-
await expect(
|
|
365
|
-
escrow.connect(client).dispute(TASK_ID, { value: insufficientFee })
|
|
366
|
-
).to.be.revertedWith("Insufficient dispute fee");
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
it("should revert if review window closed", async function () {
|
|
370
|
-
await time.increase(TIMEOUT);
|
|
371
|
-
|
|
372
|
-
const disputeFee = await escrow.getDisputeFee(TASK_ID);
|
|
373
|
-
await expect(
|
|
374
|
-
escrow.connect(client).dispute(TASK_ID, { value: disputeFee })
|
|
375
|
-
).to.be.revertedWith("Review window closed");
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
it("releaseAfterTimeout should revert on disputed task", async function () {
|
|
379
|
-
const disputeFee = await escrow.getDisputeFee(TASK_ID);
|
|
380
|
-
await escrow.connect(client).dispute(TASK_ID, { value: disputeFee });
|
|
381
|
-
|
|
382
|
-
await time.increase(TIMEOUT);
|
|
383
|
-
|
|
384
|
-
await expect(
|
|
385
|
-
escrow.connect(other).releaseAfterTimeout(TASK_ID)
|
|
386
|
-
).to.be.revertedWith("Wrong status");
|
|
387
|
-
});
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
// ─── Resolve Dispute ───────────────────────────────────────
|
|
391
|
-
|
|
392
|
-
describe("Resolve Dispute", function () {
|
|
393
|
-
const escrowAmount = ethers.parseEther("1");
|
|
394
|
-
let disputeFee;
|
|
395
|
-
|
|
396
|
-
beforeEach(async function () {
|
|
397
|
-
await escrow.connect(client).deposit(TASK_ID, agent.address, token, {
|
|
398
|
-
value: escrowAmount,
|
|
399
|
-
});
|
|
400
|
-
await escrow.connect(agent).markSubmitted(TASK_ID);
|
|
401
|
-
|
|
402
|
-
disputeFee = await escrow.getDisputeFee(TASK_ID);
|
|
403
|
-
await escrow.connect(client).dispute(TASK_ID, { value: disputeFee });
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
it("client wins: should refund escrow + dispute fee to client", async function () {
|
|
407
|
-
const clientBalanceBefore = await ethers.provider.getBalance(client.address);
|
|
408
|
-
|
|
409
|
-
const totalRefund = escrowAmount + disputeFee;
|
|
410
|
-
|
|
411
|
-
await expect(escrow.connect(admin).resolveDispute(TASK_ID, true))
|
|
412
|
-
.to.emit(escrow, "Refunded")
|
|
413
|
-
.withArgs(TASK_ID, client.address, totalRefund)
|
|
414
|
-
.and.to.emit(escrow, "DisputeResolved")
|
|
415
|
-
.withArgs(TASK_ID, true);
|
|
416
|
-
|
|
417
|
-
const clientBalanceAfter = await ethers.provider.getBalance(client.address);
|
|
418
|
-
expect(clientBalanceAfter - clientBalanceBefore).to.equal(totalRefund);
|
|
419
|
-
|
|
420
|
-
expect(await escrow.getStatus(TASK_ID)).to.equal(3); // Resolved
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
it("agent wins: should buyback escrow + send dispute fee to agent", async function () {
|
|
424
|
-
const agentBalanceBefore = await ethers.provider.getBalance(agent.address);
|
|
425
|
-
|
|
426
|
-
await expect(escrow.connect(admin).resolveDispute(TASK_ID, false))
|
|
427
|
-
.to.emit(escrow, "BuybackBurned")
|
|
428
|
-
.withArgs(TASK_ID, token, escrowAmount)
|
|
429
|
-
.and.to.emit(escrow, "DisputeResolved")
|
|
430
|
-
.withArgs(TASK_ID, false);
|
|
431
|
-
|
|
432
|
-
// Agent receives the dispute fee
|
|
433
|
-
const agentBalanceAfter = await ethers.provider.getBalance(agent.address);
|
|
434
|
-
expect(agentBalanceAfter - agentBalanceBefore).to.equal(disputeFee);
|
|
435
|
-
|
|
436
|
-
// Buyback was called with escrow amount
|
|
437
|
-
expect(await mockBuyback.callCount()).to.equal(1);
|
|
438
|
-
expect(await mockBuyback.lastToken()).to.equal(token);
|
|
439
|
-
expect(await mockBuyback.lastValue()).to.equal(escrowAmount);
|
|
440
|
-
|
|
441
|
-
expect(await escrow.getStatus(TASK_ID)).to.equal(3); // Resolved
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
it("should revert if not admin", async function () {
|
|
445
|
-
await expect(
|
|
446
|
-
escrow.connect(other).resolveDispute(TASK_ID, true)
|
|
447
|
-
).to.be.revertedWith("Not admin");
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
it("should revert if not disputed", async function () {
|
|
451
|
-
const taskId2 = ethers.keccak256(ethers.toUtf8Bytes("test-task-v4-3"));
|
|
452
|
-
await escrow.connect(client).deposit(taskId2, agent.address, token, {
|
|
453
|
-
value: escrowAmount,
|
|
454
|
-
});
|
|
455
|
-
await escrow.connect(agent).markSubmitted(taskId2);
|
|
456
|
-
|
|
457
|
-
await expect(
|
|
458
|
-
escrow.connect(admin).resolveDispute(taskId2, true)
|
|
459
|
-
).to.be.revertedWith("Not disputed");
|
|
460
|
-
});
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
// ─── setDisputeFeeBps ──────────────────────────────────────
|
|
464
|
-
|
|
465
|
-
describe("setDisputeFeeBps", function () {
|
|
466
|
-
it("admin can set to 500 (5%)", async function () {
|
|
467
|
-
await escrow.connect(admin).setDisputeFeeBps(500);
|
|
468
|
-
expect(await escrow.disputeFeeBps()).to.equal(500);
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
it("admin can set to 3000 (30%)", async function () {
|
|
472
|
-
await escrow.connect(admin).setDisputeFeeBps(3000);
|
|
473
|
-
expect(await escrow.disputeFeeBps()).to.equal(3000);
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
it("reverts below 500", async function () {
|
|
477
|
-
await expect(
|
|
478
|
-
escrow.connect(admin).setDisputeFeeBps(499)
|
|
479
|
-
).to.be.revertedWith("Fee 5-30%");
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
it("reverts above 3000", async function () {
|
|
483
|
-
await expect(
|
|
484
|
-
escrow.connect(admin).setDisputeFeeBps(3001)
|
|
485
|
-
).to.be.revertedWith("Fee 5-30%");
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
it("non-admin reverts", async function () {
|
|
489
|
-
await expect(
|
|
490
|
-
escrow.connect(other).setDisputeFeeBps(1000)
|
|
491
|
-
).to.be.revertedWith("Not admin");
|
|
492
|
-
});
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
// ─── Integration ───────────────────────────────────────────
|
|
496
|
-
|
|
497
|
-
describe("Integration", function () {
|
|
498
|
-
const escrowAmount = ethers.parseEther("2");
|
|
499
|
-
|
|
500
|
-
it("full flow: deposit -> submit -> dispute -> client wins", async function () {
|
|
501
|
-
// Deposit
|
|
502
|
-
await escrow.connect(client).deposit(TASK_ID, agent.address, token, {
|
|
503
|
-
value: escrowAmount,
|
|
504
|
-
});
|
|
505
|
-
expect(await escrow.getStatus(TASK_ID)).to.equal(0); // Active
|
|
506
|
-
|
|
507
|
-
// Submit
|
|
508
|
-
await escrow.connect(agent).markSubmitted(TASK_ID);
|
|
509
|
-
expect(await escrow.getStatus(TASK_ID)).to.equal(1); // Submitted
|
|
510
|
-
|
|
511
|
-
// Dispute
|
|
512
|
-
const disputeFee = await escrow.getDisputeFee(TASK_ID);
|
|
513
|
-
await escrow.connect(client).dispute(TASK_ID, { value: disputeFee });
|
|
514
|
-
expect(await escrow.getStatus(TASK_ID)).to.equal(2); // Disputed
|
|
515
|
-
|
|
516
|
-
// Resolve (client wins)
|
|
517
|
-
const clientBalanceBefore = await ethers.provider.getBalance(client.address);
|
|
518
|
-
await escrow.connect(admin).resolveDispute(TASK_ID, true);
|
|
519
|
-
|
|
520
|
-
const clientBalanceAfter = await ethers.provider.getBalance(client.address);
|
|
521
|
-
const totalRefund = escrowAmount + disputeFee;
|
|
522
|
-
expect(clientBalanceAfter - clientBalanceBefore).to.equal(totalRefund);
|
|
523
|
-
|
|
524
|
-
expect(await escrow.getStatus(TASK_ID)).to.equal(3); // Resolved
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
it("full flow: deposit -> submit -> dispute -> agent wins", async function () {
|
|
528
|
-
await escrow.connect(client).deposit(TASK_ID, agent.address, token, {
|
|
529
|
-
value: escrowAmount,
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
await escrow.connect(agent).markSubmitted(TASK_ID);
|
|
533
|
-
|
|
534
|
-
const disputeFee = await escrow.getDisputeFee(TASK_ID);
|
|
535
|
-
await escrow.connect(client).dispute(TASK_ID, { value: disputeFee });
|
|
536
|
-
|
|
537
|
-
const agentBalanceBefore = await ethers.provider.getBalance(agent.address);
|
|
538
|
-
await escrow.connect(admin).resolveDispute(TASK_ID, false);
|
|
539
|
-
|
|
540
|
-
// Agent receives dispute fee
|
|
541
|
-
const agentBalanceAfter = await ethers.provider.getBalance(agent.address);
|
|
542
|
-
expect(agentBalanceAfter - agentBalanceBefore).to.equal(disputeFee);
|
|
543
|
-
|
|
544
|
-
// Buyback was called with escrow amount
|
|
545
|
-
expect(await mockBuyback.callCount()).to.equal(1);
|
|
546
|
-
expect(await mockBuyback.lastValue()).to.equal(escrowAmount);
|
|
547
|
-
|
|
548
|
-
expect(await escrow.getStatus(TASK_ID)).to.equal(3); // Resolved
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
it("deposit -> submit -> timeout (no dispute) -> releaseAfterTimeout", async function () {
|
|
552
|
-
await escrow.connect(client).deposit(TASK_ID, agent.address, token, {
|
|
553
|
-
value: escrowAmount,
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
await escrow.connect(agent).markSubmitted(TASK_ID);
|
|
557
|
-
|
|
558
|
-
// Fast forward past timeout without dispute
|
|
559
|
-
await time.increase(TIMEOUT);
|
|
560
|
-
|
|
561
|
-
await expect(escrow.connect(other).releaseAfterTimeout(TASK_ID))
|
|
562
|
-
.to.emit(escrow, "BuybackBurned")
|
|
563
|
-
.withArgs(TASK_ID, token, escrowAmount);
|
|
564
|
-
|
|
565
|
-
expect(await escrow.getStatus(TASK_ID)).to.equal(4); // Released
|
|
566
|
-
});
|
|
567
|
-
});
|
|
568
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"strict": true,
|
|
7
|
-
"esModuleInterop": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"forceConsistentCasingInFileNames": true,
|
|
10
|
-
"resolveJsonModule": true,
|
|
11
|
-
"declaration": true,
|
|
12
|
-
"declarationMap": true,
|
|
13
|
-
"outDir": "./dist",
|
|
14
|
-
"rootDir": "./src",
|
|
15
|
-
"types": ["node"]
|
|
16
|
-
},
|
|
17
|
-
"include": ["src/**/*"],
|
|
18
|
-
"exclude": ["node_modules", "dist"]
|
|
19
|
-
}
|
package/tsup.config.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from "tsup";
|
|
2
|
-
|
|
3
|
-
export default defineConfig({
|
|
4
|
-
entry: ["src/index.ts"],
|
|
5
|
-
format: ["esm"],
|
|
6
|
-
target: "node18",
|
|
7
|
-
outDir: "dist",
|
|
8
|
-
clean: true,
|
|
9
|
-
splitting: false,
|
|
10
|
-
sourcemap: true,
|
|
11
|
-
dts: true,
|
|
12
|
-
banner: {
|
|
13
|
-
js: "#!/usr/bin/env node",
|
|
14
|
-
},
|
|
15
|
-
});
|