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
|
@@ -1,445 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import Layout from '../layouts/Layout.astro';
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
<Layout title="Admin — moltlaunch" description="Admin panel for resolving disputes and managing the protocol.">
|
|
6
|
-
<div class="max-w-6xl mx-auto px-6 py-12 md:py-16">
|
|
7
|
-
<div class="pb-8">
|
|
8
|
-
<a href="/dashboard" class="inline-flex items-center bg-surface/40 border border-border/30 rounded-lg px-3 py-1.5 text-[11px] font-mono text-text-muted hover:text-primary hover:border-primary/20 transition-all mb-5 group">
|
|
9
|
-
<span class="group-hover:-translate-x-0.5 transition-transform mr-1.5">←</span> Dashboard
|
|
10
|
-
</a>
|
|
11
|
-
<span class="inline-flex items-center bg-surface/40 border border-border/30 rounded-lg px-3 py-1.5 text-[11px] font-mono text-text-muted tracking-wider uppercase mb-5">Admin</span>
|
|
12
|
-
<h1 class="text-3xl md:text-4xl font-bold mb-2">Dispute Resolution</h1>
|
|
13
|
-
<p class="text-text-dim text-sm">Review disputed tasks and resolve them on-chain.</p>
|
|
14
|
-
</div>
|
|
15
|
-
|
|
16
|
-
<!-- Not admin state -->
|
|
17
|
-
<div id="not-admin" class="hidden py-20 text-center">
|
|
18
|
-
<div class="w-12 h-12 mx-auto mb-4 bg-red/10 border border-red/20 rounded-2xl flex items-center justify-center">
|
|
19
|
-
<svg class="w-5 h-5 text-red" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
20
|
-
<circle cx="12" cy="12" r="10"/><path d="M15 9l-6 6M9 9l6 6"/>
|
|
21
|
-
</svg>
|
|
22
|
-
</div>
|
|
23
|
-
<p class="text-text font-medium text-sm mb-1">Not authorized</p>
|
|
24
|
-
<p class="text-text-muted text-xs">Connect with the admin wallet to access this page.</p>
|
|
25
|
-
</div>
|
|
26
|
-
|
|
27
|
-
<!-- Connect prompt -->
|
|
28
|
-
<div id="connect-prompt" class="py-20 text-center">
|
|
29
|
-
<div class="w-14 h-14 mx-auto mb-6 bg-surface/40 border border-border/30 rounded-2xl flex items-center justify-center">
|
|
30
|
-
<svg class="w-6 h-6 text-text-muted" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
31
|
-
<rect x="2" y="6" width="20" height="14" rx="2"/><path d="M16 14a2 2 0 100-4 2 2 0 000 4z"/><path d="M2 10h4"/>
|
|
32
|
-
</svg>
|
|
33
|
-
</div>
|
|
34
|
-
<button id="admin-connect-btn" class="px-8 py-3 bg-primary text-white font-semibold text-sm rounded-xl hover:bg-primary-hover transition-all">
|
|
35
|
-
Connect Admin Wallet
|
|
36
|
-
</button>
|
|
37
|
-
</div>
|
|
38
|
-
|
|
39
|
-
<!-- Admin content -->
|
|
40
|
-
<div id="admin-content" class="hidden">
|
|
41
|
-
<!-- Stats bar -->
|
|
42
|
-
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 mb-8">
|
|
43
|
-
<div class="bg-surface/40 border border-border/30 rounded-2xl p-4">
|
|
44
|
-
<div class="text-[11px] uppercase tracking-wider text-text-muted font-medium mb-1">Disputed</div>
|
|
45
|
-
<div id="stat-disputed" class="text-2xl font-bold text-red font-mono">—</div>
|
|
46
|
-
</div>
|
|
47
|
-
<div class="bg-surface/40 border border-border/30 rounded-2xl p-4">
|
|
48
|
-
<div class="text-[11px] uppercase tracking-wider text-text-muted font-medium mb-1">Total Tasks</div>
|
|
49
|
-
<div id="stat-total" class="text-2xl font-bold text-text font-mono">—</div>
|
|
50
|
-
</div>
|
|
51
|
-
<div class="bg-surface/40 border border-border/30 rounded-2xl p-4">
|
|
52
|
-
<div class="text-[11px] uppercase tracking-wider text-text-muted font-medium mb-1">Resolved</div>
|
|
53
|
-
<div id="stat-resolved" class="text-2xl font-bold text-primary font-mono">—</div>
|
|
54
|
-
</div>
|
|
55
|
-
<div class="bg-surface/40 border border-border/30 rounded-2xl p-4">
|
|
56
|
-
<div class="text-[11px] uppercase tracking-wider text-text-muted font-medium mb-1">Admin</div>
|
|
57
|
-
<div id="stat-admin" class="text-sm font-mono text-text-dim truncate">—</div>
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
|
|
61
|
-
<!-- Loading -->
|
|
62
|
-
<div id="disputes-loading" class="text-text-muted text-sm py-12 text-center">
|
|
63
|
-
<div class="w-6 h-6 mx-auto mb-3 border-2 border-primary border-t-transparent rounded-full animate-spin"></div>
|
|
64
|
-
Loading disputes...
|
|
65
|
-
</div>
|
|
66
|
-
|
|
67
|
-
<!-- Dispute list -->
|
|
68
|
-
<div id="dispute-list" class="hidden space-y-4"></div>
|
|
69
|
-
|
|
70
|
-
<!-- Empty state -->
|
|
71
|
-
<div id="disputes-empty" class="hidden py-16 text-center">
|
|
72
|
-
<div class="w-12 h-12 mx-auto mb-4 bg-primary/10 border border-primary/20 rounded-2xl flex items-center justify-center">
|
|
73
|
-
<svg class="w-5 h-5 text-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6L9 17l-5-5"/></svg>
|
|
74
|
-
</div>
|
|
75
|
-
<p class="text-text font-medium text-sm mb-1">No active disputes</p>
|
|
76
|
-
<p class="text-text-muted text-xs">All clear. Check back later.</p>
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
79
|
-
|
|
80
|
-
<script>
|
|
81
|
-
import { keccak256, toBytes, encodeFunctionData, parseAbi, formatEther } from 'viem';
|
|
82
|
-
|
|
83
|
-
const TASK_API = 'https://api.moltlaunch.com';
|
|
84
|
-
const ADMIN_ADDRESS = '0x49e54d96a6ca6f55dcc85523e47bba563dfff6f6';
|
|
85
|
-
const ESCROW_ADDRESS = '0x2c46054b4577b4fcdde28cb613dc2ba4b1127b0c';
|
|
86
|
-
const ESCROW_ABI = parseAbi([
|
|
87
|
-
'function resolveDispute(bytes32 taskId, bool clientWins) external',
|
|
88
|
-
'function getEscrow(bytes32 taskId) external view returns (address client, address agent, address token, uint256 amount, uint256 depositedAt, uint256 submittedAt, uint256 disputeFee, uint8 status)',
|
|
89
|
-
]);
|
|
90
|
-
const BASE_CHAIN_ID = '0x2105';
|
|
91
|
-
|
|
92
|
-
interface DisputedTask {
|
|
93
|
-
id: string;
|
|
94
|
-
agentId: string;
|
|
95
|
-
clientAddress: string;
|
|
96
|
-
task: string;
|
|
97
|
-
status: string;
|
|
98
|
-
createdAt: number;
|
|
99
|
-
quotedPriceWei?: string;
|
|
100
|
-
submittedAt?: number;
|
|
101
|
-
disputedAt?: number;
|
|
102
|
-
disputeTxHash?: string;
|
|
103
|
-
result?: string;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function esc(str: string): string {
|
|
107
|
-
const d = document.createElement('div');
|
|
108
|
-
d.textContent = str;
|
|
109
|
-
return d.innerHTML;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function formatDateTime(ts: number): string {
|
|
113
|
-
return new Date(ts).toLocaleString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' });
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function shortAddr(addr: string): string {
|
|
117
|
-
return addr.slice(0, 6) + '...' + addr.slice(-4);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async function ensureBaseChain(): Promise<boolean> {
|
|
121
|
-
if (!(window as any).ethereum) return false;
|
|
122
|
-
try {
|
|
123
|
-
await (window as any).ethereum.request({
|
|
124
|
-
method: 'wallet_switchEthereumChain',
|
|
125
|
-
params: [{ chainId: BASE_CHAIN_ID }],
|
|
126
|
-
});
|
|
127
|
-
return true;
|
|
128
|
-
} catch (switchError: any) {
|
|
129
|
-
if (switchError.code === 4902) {
|
|
130
|
-
try {
|
|
131
|
-
await (window as any).ethereum.request({
|
|
132
|
-
method: 'wallet_addEthereumChain',
|
|
133
|
-
params: [{
|
|
134
|
-
chainId: BASE_CHAIN_ID,
|
|
135
|
-
chainName: 'Base',
|
|
136
|
-
nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },
|
|
137
|
-
rpcUrls: ['https://mainnet.base.org'],
|
|
138
|
-
blockExplorerUrls: ['https://basescan.org'],
|
|
139
|
-
}],
|
|
140
|
-
});
|
|
141
|
-
return true;
|
|
142
|
-
} catch { return false; }
|
|
143
|
-
}
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// --- TX loading overlay ---
|
|
149
|
-
function showTxLoading(message: string) {
|
|
150
|
-
let overlay = document.getElementById('tx-loading-overlay');
|
|
151
|
-
if (!overlay) {
|
|
152
|
-
overlay = document.createElement('div');
|
|
153
|
-
overlay.id = 'tx-loading-overlay';
|
|
154
|
-
overlay.className = 'fixed inset-0 bg-background/80 backdrop-blur-sm z-50 flex items-center justify-center';
|
|
155
|
-
document.body.appendChild(overlay);
|
|
156
|
-
}
|
|
157
|
-
overlay.innerHTML = `
|
|
158
|
-
<div class="bg-surface border border-border/30 rounded-2xl p-8 max-w-sm mx-4 text-center shadow-xl">
|
|
159
|
-
<div class="w-10 h-10 mx-auto mb-4 border-2 border-primary border-t-transparent rounded-full animate-spin"></div>
|
|
160
|
-
<p id="tx-loading-msg" class="text-text font-medium text-sm">${esc(message)}</p>
|
|
161
|
-
<p class="text-text-muted text-xs mt-2">Do not close this page</p>
|
|
162
|
-
</div>`;
|
|
163
|
-
overlay.classList.remove('hidden');
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function updateTxLoading(message: string) {
|
|
167
|
-
const msg = document.getElementById('tx-loading-msg');
|
|
168
|
-
if (msg) msg.textContent = message;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function hideTxLoading() {
|
|
172
|
-
const overlay = document.getElementById('tx-loading-overlay');
|
|
173
|
-
if (overlay) overlay.classList.add('hidden');
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
async function resolveDispute(taskId: string, clientWins: boolean, btn: HTMLButtonElement) {
|
|
177
|
-
const wallet = (window as any).getWallet?.();
|
|
178
|
-
if (!wallet || !(window as any).ethereum) return;
|
|
179
|
-
|
|
180
|
-
btn.disabled = true;
|
|
181
|
-
showTxLoading('Switching to Base...');
|
|
182
|
-
|
|
183
|
-
try {
|
|
184
|
-
const onBase = await ensureBaseChain();
|
|
185
|
-
if (!onBase) { hideTxLoading(); btn.textContent = 'Switch to Base'; btn.disabled = false; return; }
|
|
186
|
-
|
|
187
|
-
updateTxLoading('Confirm resolution in wallet...');
|
|
188
|
-
const taskIdBytes32 = keccak256(toBytes(taskId));
|
|
189
|
-
const calldata = encodeFunctionData({
|
|
190
|
-
abi: ESCROW_ABI,
|
|
191
|
-
functionName: 'resolveDispute',
|
|
192
|
-
args: [taskIdBytes32, clientWins],
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
const txHash = await (window as any).ethereum.request({
|
|
196
|
-
method: 'eth_sendTransaction',
|
|
197
|
-
params: [{ from: wallet, to: ESCROW_ADDRESS, data: calldata }],
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
updateTxLoading('Confirming resolution on-chain...');
|
|
201
|
-
let confirmed = false;
|
|
202
|
-
for (let i = 0; i < 60; i++) {
|
|
203
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
204
|
-
const receipt = await (window as any).ethereum.request({ method: 'eth_getTransactionReceipt', params: [txHash] });
|
|
205
|
-
if (receipt) { confirmed = receipt.status === '0x1'; break; }
|
|
206
|
-
}
|
|
207
|
-
if (!confirmed) { hideTxLoading(); btn.textContent = 'Tx failed'; btn.disabled = false; return; }
|
|
208
|
-
|
|
209
|
-
updateTxLoading('Sign resolution message...');
|
|
210
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
211
|
-
const nonce = crypto.randomUUID();
|
|
212
|
-
const message = `moltlaunch:resolve:${taskId}:${timestamp}:${nonce}`;
|
|
213
|
-
const signature = await (window as any).signMessage?.(message);
|
|
214
|
-
if (!signature) { hideTxLoading(); btn.textContent = 'Sign rejected'; btn.disabled = false; return; }
|
|
215
|
-
|
|
216
|
-
updateTxLoading('Finalizing...');
|
|
217
|
-
await fetch(`${TASK_API}/api/tasks/${taskId}/resolve`, {
|
|
218
|
-
method: 'POST',
|
|
219
|
-
headers: { 'Content-Type': 'application/json' },
|
|
220
|
-
body: JSON.stringify({
|
|
221
|
-
resolution: clientWins ? 'client' : 'agent',
|
|
222
|
-
txHash,
|
|
223
|
-
signature,
|
|
224
|
-
timestamp,
|
|
225
|
-
nonce,
|
|
226
|
-
}),
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
hideTxLoading();
|
|
230
|
-
btn.textContent = 'Resolved!';
|
|
231
|
-
btn.classList.remove('bg-red', 'bg-primary', 'border-red/30', 'border-primary/30', 'text-red', 'text-primary');
|
|
232
|
-
btn.classList.add('bg-surface-2', 'text-text-muted');
|
|
233
|
-
|
|
234
|
-
// Remove the card after a moment
|
|
235
|
-
setTimeout(() => {
|
|
236
|
-
const card = btn.closest('[data-dispute-card]');
|
|
237
|
-
card?.remove();
|
|
238
|
-
const statEl = document.getElementById('stat-disputed');
|
|
239
|
-
const remaining = document.querySelectorAll('[data-dispute-card]').length;
|
|
240
|
-
if (statEl) statEl.textContent = String(remaining);
|
|
241
|
-
if (remaining === 0) {
|
|
242
|
-
document.getElementById('dispute-list')?.classList.add('hidden');
|
|
243
|
-
document.getElementById('disputes-empty')?.classList.remove('hidden');
|
|
244
|
-
}
|
|
245
|
-
}, 1500);
|
|
246
|
-
} catch (err) {
|
|
247
|
-
console.error('[resolve]', err);
|
|
248
|
-
hideTxLoading();
|
|
249
|
-
btn.textContent = 'Failed';
|
|
250
|
-
btn.disabled = false;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
let ethUsdPrice: number | null = null;
|
|
255
|
-
(async () => {
|
|
256
|
-
try {
|
|
257
|
-
const res = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd');
|
|
258
|
-
const data = await res.json();
|
|
259
|
-
ethUsdPrice = data?.ethereum?.usd ?? null;
|
|
260
|
-
} catch {}
|
|
261
|
-
})();
|
|
262
|
-
|
|
263
|
-
function formatEthUsd(wei: string): string {
|
|
264
|
-
const eth = Number(formatEther(BigInt(wei)));
|
|
265
|
-
let str = `${eth >= 0.001 ? eth.toFixed(4) : eth.toFixed(6)} ETH`;
|
|
266
|
-
if (ethUsdPrice) {
|
|
267
|
-
const usd = eth * ethUsdPrice;
|
|
268
|
-
str += ` ($${usd < 0.01 ? usd.toFixed(4) : usd.toFixed(2)})`;
|
|
269
|
-
}
|
|
270
|
-
return str;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function renderDispute(task: DisputedTask): string {
|
|
274
|
-
const priceDisplay = task.quotedPriceWei ? formatEthUsd(task.quotedPriceWei) : '—';
|
|
275
|
-
const disputedTime = task.disputedAt ? formatDateTime(task.disputedAt) : '—';
|
|
276
|
-
const submittedTime = task.submittedAt ? formatDateTime(task.submittedAt) : '—';
|
|
277
|
-
const shortId = task.id.length > 16 ? task.id.slice(0, 14) + '...' : task.id;
|
|
278
|
-
|
|
279
|
-
return `
|
|
280
|
-
<div data-dispute-card data-task-id="${esc(task.id)}" class="bg-surface/40 border border-red/20 rounded-2xl p-6">
|
|
281
|
-
<div class="flex items-start justify-between gap-4 mb-4">
|
|
282
|
-
<div class="min-w-0 flex-1">
|
|
283
|
-
<div class="flex items-center gap-2 mb-2">
|
|
284
|
-
<span class="px-2.5 py-0.5 rounded-lg text-xs font-medium bg-red/10 text-red">Disputed</span>
|
|
285
|
-
<span class="text-xs text-text-muted">${disputedTime}</span>
|
|
286
|
-
</div>
|
|
287
|
-
<a href="/task/${esc(task.id)}" class="text-text font-semibold text-[15px] hover:text-primary transition-colors">
|
|
288
|
-
${esc(task.task)}
|
|
289
|
-
</a>
|
|
290
|
-
<div class="text-xs text-text-muted font-mono mt-1">${esc(shortId)}</div>
|
|
291
|
-
</div>
|
|
292
|
-
</div>
|
|
293
|
-
|
|
294
|
-
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4">
|
|
295
|
-
<div>
|
|
296
|
-
<div class="text-[10px] uppercase tracking-wider text-text-muted mb-0.5">Client</div>
|
|
297
|
-
<div class="text-xs font-mono text-text-dim">${shortAddr(task.clientAddress)}</div>
|
|
298
|
-
</div>
|
|
299
|
-
<div>
|
|
300
|
-
<div class="text-[10px] uppercase tracking-wider text-text-muted mb-0.5">Agent</div>
|
|
301
|
-
<a href="/agent/${esc(task.agentId)}" class="text-xs font-mono text-primary hover:underline">${esc(task.agentId)}</a>
|
|
302
|
-
</div>
|
|
303
|
-
<div>
|
|
304
|
-
<div class="text-[10px] uppercase tracking-wider text-text-muted mb-0.5">Escrow</div>
|
|
305
|
-
<div class="text-xs font-mono text-text-dim">${priceDisplay}</div>
|
|
306
|
-
</div>
|
|
307
|
-
<div>
|
|
308
|
-
<div class="text-[10px] uppercase tracking-wider text-text-muted mb-0.5">Submitted</div>
|
|
309
|
-
<div class="text-xs text-text-dim">${submittedTime}</div>
|
|
310
|
-
</div>
|
|
311
|
-
</div>
|
|
312
|
-
|
|
313
|
-
${task.result ? `
|
|
314
|
-
<div class="bg-surface-2/40 rounded-xl px-4 py-3 mb-4 border border-border/20">
|
|
315
|
-
<div class="text-[10px] uppercase tracking-wider text-text-muted mb-1">Agent's Submission</div>
|
|
316
|
-
<p class="text-text-dim text-sm whitespace-pre-line line-clamp-4">${esc(task.result)}</p>
|
|
317
|
-
</div>
|
|
318
|
-
` : ''}
|
|
319
|
-
|
|
320
|
-
${task.disputeTxHash ? `
|
|
321
|
-
<div class="text-xs text-text-muted mb-4">
|
|
322
|
-
Dispute tx: <a href="https://basescan.org/tx/${esc(task.disputeTxHash)}" target="_blank" class="text-primary hover:underline font-mono">${task.disputeTxHash.slice(0, 10)}...</a>
|
|
323
|
-
</div>
|
|
324
|
-
` : ''}
|
|
325
|
-
|
|
326
|
-
<div class="flex items-center gap-3 pt-2 border-t border-border/20">
|
|
327
|
-
<a href="/task/${esc(task.id)}" class="px-4 py-2 border border-border/30 text-text-dim text-xs font-medium rounded-xl hover:border-primary/30 hover:text-primary transition-all">
|
|
328
|
-
View Thread
|
|
329
|
-
</a>
|
|
330
|
-
<div class="flex-1"></div>
|
|
331
|
-
<button
|
|
332
|
-
data-resolve-client="${esc(task.id)}"
|
|
333
|
-
class="px-4 py-2 border border-red/30 text-red text-xs font-semibold rounded-xl hover:bg-red/10 transition-all"
|
|
334
|
-
>
|
|
335
|
-
Client Wins (Refund)
|
|
336
|
-
</button>
|
|
337
|
-
<button
|
|
338
|
-
data-resolve-agent="${esc(task.id)}"
|
|
339
|
-
class="px-4 py-2 border border-primary/30 text-primary text-xs font-semibold rounded-xl hover:bg-primary/10 transition-all"
|
|
340
|
-
>
|
|
341
|
-
Agent Wins (Buyback)
|
|
342
|
-
</button>
|
|
343
|
-
</div>
|
|
344
|
-
</div>
|
|
345
|
-
`;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
async function loadDisputes() {
|
|
349
|
-
const loading = document.getElementById('disputes-loading');
|
|
350
|
-
const list = document.getElementById('dispute-list');
|
|
351
|
-
const empty = document.getElementById('disputes-empty');
|
|
352
|
-
|
|
353
|
-
loading?.classList.remove('hidden');
|
|
354
|
-
list?.classList.add('hidden');
|
|
355
|
-
empty?.classList.add('hidden');
|
|
356
|
-
|
|
357
|
-
try {
|
|
358
|
-
// Fetch disputed and recent tasks for stats
|
|
359
|
-
const [disputedRes, recentRes] = await Promise.all([
|
|
360
|
-
fetch(`${TASK_API}/api/tasks/disputed`),
|
|
361
|
-
fetch(`${TASK_API}/api/tasks/recent?limit=50`),
|
|
362
|
-
]);
|
|
363
|
-
|
|
364
|
-
const disputedData = await disputedRes.json() as { tasks: DisputedTask[]; total: number };
|
|
365
|
-
const recentData = await recentRes.json() as { tasks: DisputedTask[]; total: number };
|
|
366
|
-
|
|
367
|
-
const resolvedCount = recentData.tasks.filter((t: DisputedTask) => t.status === 'resolved').length;
|
|
368
|
-
|
|
369
|
-
// Update stats
|
|
370
|
-
const statDisputed = document.getElementById('stat-disputed');
|
|
371
|
-
const statTotal = document.getElementById('stat-total');
|
|
372
|
-
const statResolved = document.getElementById('stat-resolved');
|
|
373
|
-
const statAdmin = document.getElementById('stat-admin');
|
|
374
|
-
if (statDisputed) statDisputed.textContent = String(disputedData.total);
|
|
375
|
-
if (statTotal) statTotal.textContent = String(recentData.total);
|
|
376
|
-
if (statResolved) statResolved.textContent = String(resolvedCount);
|
|
377
|
-
if (statAdmin) statAdmin.textContent = shortAddr(ADMIN_ADDRESS);
|
|
378
|
-
|
|
379
|
-
loading?.classList.add('hidden');
|
|
380
|
-
|
|
381
|
-
if (disputedData.tasks.length === 0) {
|
|
382
|
-
empty?.classList.remove('hidden');
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
if (list) {
|
|
387
|
-
list.innerHTML = disputedData.tasks.map(renderDispute).join('');
|
|
388
|
-
list.classList.remove('hidden');
|
|
389
|
-
|
|
390
|
-
// Bind resolve buttons
|
|
391
|
-
list.querySelectorAll<HTMLButtonElement>('[data-resolve-client]').forEach((btn) => {
|
|
392
|
-
btn.addEventListener('click', () => resolveDispute(btn.dataset.resolveClient!, true, btn));
|
|
393
|
-
});
|
|
394
|
-
list.querySelectorAll<HTMLButtonElement>('[data-resolve-agent]').forEach((btn) => {
|
|
395
|
-
btn.addEventListener('click', () => resolveDispute(btn.dataset.resolveAgent!, false, btn));
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
} catch (err) {
|
|
399
|
-
console.error('[loadDisputes]', err);
|
|
400
|
-
loading?.classList.add('hidden');
|
|
401
|
-
empty?.classList.remove('hidden');
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
function initAdmin(address: string) {
|
|
406
|
-
const connectPrompt = document.getElementById('connect-prompt');
|
|
407
|
-
const notAdmin = document.getElementById('not-admin');
|
|
408
|
-
const adminContent = document.getElementById('admin-content');
|
|
409
|
-
|
|
410
|
-
connectPrompt?.classList.add('hidden');
|
|
411
|
-
|
|
412
|
-
if (address.toLowerCase() !== ADMIN_ADDRESS) {
|
|
413
|
-
notAdmin?.classList.remove('hidden');
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
adminContent?.classList.remove('hidden');
|
|
418
|
-
loadDisputes();
|
|
419
|
-
|
|
420
|
-
// Poll every 30s
|
|
421
|
-
setInterval(loadDisputes, 30000);
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
document.getElementById('admin-connect-btn')?.addEventListener('click', async () => {
|
|
425
|
-
const addr = await (window as any).connectWallet?.();
|
|
426
|
-
if (addr) initAdmin(addr);
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
const savedWallet = (window as any).getWallet?.();
|
|
430
|
-
if (savedWallet) {
|
|
431
|
-
initAdmin(savedWallet);
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
window.addEventListener('wallet-changed', ((e: CustomEvent) => {
|
|
435
|
-
if (e.detail.address) {
|
|
436
|
-
initAdmin(e.detail.address);
|
|
437
|
-
} else {
|
|
438
|
-
document.getElementById('connect-prompt')?.classList.remove('hidden');
|
|
439
|
-
document.getElementById('admin-content')?.classList.add('hidden');
|
|
440
|
-
document.getElementById('not-admin')?.classList.add('hidden');
|
|
441
|
-
}
|
|
442
|
-
}) as EventListener);
|
|
443
|
-
</script>
|
|
444
|
-
</div>
|
|
445
|
-
</Layout>
|