plotlink-ows 0.1.13
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/LICENSE +21 -0
- package/README.md +151 -0
- package/app/db.ts +8 -0
- package/app/lib/llm-client.ts +265 -0
- package/app/lib/paths.ts +11 -0
- package/app/lib/publish.ts +204 -0
- package/app/lib/writer-prompt.ts +44 -0
- package/app/node_modules/.prisma/local-client/client.d.ts +1 -0
- package/app/node_modules/.prisma/local-client/client.js +5 -0
- package/app/node_modules/.prisma/local-client/default.d.ts +1 -0
- package/app/node_modules/.prisma/local-client/default.js +5 -0
- package/app/node_modules/.prisma/local-client/edge.d.ts +1 -0
- package/app/node_modules/.prisma/local-client/edge.js +184 -0
- package/app/node_modules/.prisma/local-client/index-browser.js +173 -0
- package/app/node_modules/.prisma/local-client/index.d.ts +3304 -0
- package/app/node_modules/.prisma/local-client/index.js +207 -0
- package/app/node_modules/.prisma/local-client/libquery_engine-darwin-arm64.dylib.node +0 -0
- package/app/node_modules/.prisma/local-client/package.json +183 -0
- package/app/node_modules/.prisma/local-client/query_engine_bg.js +2 -0
- package/app/node_modules/.prisma/local-client/query_engine_bg.wasm +0 -0
- package/app/node_modules/.prisma/local-client/runtime/edge-esm.js +35 -0
- package/app/node_modules/.prisma/local-client/runtime/edge.js +35 -0
- package/app/node_modules/.prisma/local-client/runtime/index-browser.d.ts +370 -0
- package/app/node_modules/.prisma/local-client/runtime/index-browser.js +17 -0
- package/app/node_modules/.prisma/local-client/runtime/library.d.ts +3982 -0
- package/app/node_modules/.prisma/local-client/runtime/library.js +147 -0
- package/app/node_modules/.prisma/local-client/runtime/react-native.js +84 -0
- package/app/node_modules/.prisma/local-client/runtime/wasm-compiler-edge.js +85 -0
- package/app/node_modules/.prisma/local-client/runtime/wasm-engine-edge.js +38 -0
- package/app/node_modules/.prisma/local-client/schema.prisma +21 -0
- package/app/node_modules/.prisma/local-client/wasm-edge-light-loader.mjs +5 -0
- package/app/node_modules/.prisma/local-client/wasm-worker-loader.mjs +5 -0
- package/app/node_modules/.prisma/local-client/wasm.d.ts +1 -0
- package/app/node_modules/.prisma/local-client/wasm.js +191 -0
- package/app/prisma/schema.prisma +57 -0
- package/app/routes/auth.ts +173 -0
- package/app/routes/chat.ts +135 -0
- package/app/routes/config.ts +210 -0
- package/app/routes/dashboard.ts +186 -0
- package/app/routes/oauth.ts +150 -0
- package/app/routes/publish.ts +112 -0
- package/app/routes/wallet.ts +99 -0
- package/app/server.ts +154 -0
- package/app/vite.config.ts +19 -0
- package/app/web/App.tsx +102 -0
- package/app/web/components/Chat.tsx +272 -0
- package/app/web/components/Dashboard.tsx +222 -0
- package/app/web/components/LLMSetup.tsx +291 -0
- package/app/web/components/Layout.tsx +235 -0
- package/app/web/components/Login.tsx +62 -0
- package/app/web/components/Publish.tsx +245 -0
- package/app/web/components/Settings.tsx +175 -0
- package/app/web/components/Setup.tsx +84 -0
- package/app/web/components/WalletCard.tsx +117 -0
- package/app/web/dist/assets/index-C9kXlYO_.css +2 -0
- package/app/web/dist/assets/index-CJiiaLHs.js +9 -0
- package/app/web/dist/index.html +16 -0
- package/app/web/index.html +15 -0
- package/app/web/main.tsx +10 -0
- package/app/web/plotlink-logo.svg +5 -0
- package/app/web/styles.css +51 -0
- package/bin/plotlink-ows.js +394 -0
- package/lib/ows/index.ts +3 -0
- package/lib/ows/policy.ts +68 -0
- package/lib/ows/types.ts +14 -0
- package/lib/ows/wallet.ts +70 -0
- package/package.json +79 -0
- package/packages/cli/node_modules/commander/LICENSE +22 -0
- package/packages/cli/node_modules/commander/Readme.md +1149 -0
- package/packages/cli/node_modules/commander/esm.mjs +16 -0
- package/packages/cli/node_modules/commander/index.js +24 -0
- package/packages/cli/node_modules/commander/lib/argument.js +149 -0
- package/packages/cli/node_modules/commander/lib/command.js +2662 -0
- package/packages/cli/node_modules/commander/lib/error.js +39 -0
- package/packages/cli/node_modules/commander/lib/help.js +709 -0
- package/packages/cli/node_modules/commander/lib/option.js +367 -0
- package/packages/cli/node_modules/commander/lib/suggestSimilar.js +101 -0
- package/packages/cli/node_modules/commander/package-support.json +16 -0
- package/packages/cli/node_modules/commander/package.json +82 -0
- package/packages/cli/node_modules/commander/typings/esm.d.mts +3 -0
- package/packages/cli/node_modules/commander/typings/index.d.ts +1045 -0
- package/packages/cli/node_modules/resolve-from/index.d.ts +31 -0
- package/packages/cli/node_modules/resolve-from/index.js +47 -0
- package/packages/cli/node_modules/resolve-from/license +9 -0
- package/packages/cli/node_modules/resolve-from/package.json +36 -0
- package/packages/cli/node_modules/resolve-from/readme.md +72 -0
- package/packages/cli/node_modules/tsup/LICENSE +21 -0
- package/packages/cli/node_modules/tsup/README.md +75 -0
- package/packages/cli/node_modules/tsup/assets/cjs_shims.js +13 -0
- package/packages/cli/node_modules/tsup/assets/esm_shims.js +9 -0
- package/packages/cli/node_modules/tsup/assets/package.json +3 -0
- package/packages/cli/node_modules/tsup/dist/chunk-DI5BO6XE.js +153 -0
- package/packages/cli/node_modules/tsup/dist/chunk-JZ25TPTY.js +42 -0
- package/packages/cli/node_modules/tsup/dist/chunk-PEEXUWMS.js +6 -0
- package/packages/cli/node_modules/tsup/dist/chunk-TWFEYLU4.js +352 -0
- package/packages/cli/node_modules/tsup/dist/chunk-VGC3FXLU.js +203 -0
- package/packages/cli/node_modules/tsup/dist/cli-default.js +12 -0
- package/packages/cli/node_modules/tsup/dist/cli-main.js +8 -0
- package/packages/cli/node_modules/tsup/dist/cli-node.js +14 -0
- package/packages/cli/node_modules/tsup/dist/index.d.ts +511 -0
- package/packages/cli/node_modules/tsup/dist/index.js +1711 -0
- package/packages/cli/node_modules/tsup/dist/rollup.js +6949 -0
- package/packages/cli/node_modules/tsup/package.json +99 -0
- package/packages/cli/node_modules/tsup/schema.json +362 -0
- package/packages/cli/package.json +35 -0
- package/packages/cli/src/commands/agent-register.ts +77 -0
- package/packages/cli/src/commands/chain.ts +29 -0
- package/packages/cli/src/commands/claim.ts +70 -0
- package/packages/cli/src/commands/create.ts +34 -0
- package/packages/cli/src/commands/status.ts +201 -0
- package/packages/cli/src/config.ts +103 -0
- package/packages/cli/src/index.ts +21 -0
- package/packages/cli/src/sdk/abi.ts +222 -0
- package/packages/cli/src/sdk/client.ts +713 -0
- package/packages/cli/src/sdk/constants.ts +56 -0
- package/packages/cli/src/sdk/index.ts +46 -0
- package/packages/cli/src/sdk/ipfs.ts +88 -0
- package/packages/cli/src/sdk.ts +36 -0
- package/packages/cli/tsconfig.json +20 -0
- package/packages/cli/tsup.config.ts +14 -0
- package/public/.well-known/farcaster.json +38 -0
- package/public/basescan-icon.svg +4 -0
- package/public/embed-image.png +0 -0
- package/public/favicon.png +0 -0
- package/public/hunt-token.svg +11 -0
- package/public/icon-192.png +0 -0
- package/public/icon.png +0 -0
- package/public/manifest.json +26 -0
- package/public/mc-icon-light.svg +12 -0
- package/public/og-image.png +0 -0
- package/public/plotlink-logo-symbol.svg +5 -0
- package/public/plotlink-logo.svg +5 -0
- package/public/screenshot-1.png +0 -0
- package/public/screenshot-2.png +0 -0
- package/public/screenshot-3.png +0 -0
- package/public/splash.png +0 -0
- package/public/wide-banner.png +0 -0
- package/scripts/backfill-trade-prices.ts +97 -0
- package/scripts/backfill-usd-rates.ts +220 -0
- package/scripts/e2e-verify.ts +1100 -0
- package/scripts/ows-smoke-test.ts +37 -0
- package/scripts/score-users.mjs +203 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { WalletCard } from "./WalletCard";
|
|
3
|
+
|
|
4
|
+
const API_BASE = "http://localhost:7777";
|
|
5
|
+
|
|
6
|
+
export function Settings({ token, onLogout, onChangeLLM }: { token: string; onLogout: () => void; onChangeLLM: () => void }) {
|
|
7
|
+
const [llmConfig, setLlmConfig] = useState<{ llm: Record<string, unknown>; configured: string[] } | null>(null);
|
|
8
|
+
const [spendCap, setSpendCap] = useState<string>("10");
|
|
9
|
+
const [savingCap, setSavingCap] = useState(false);
|
|
10
|
+
const [capSaved, setCapSaved] = useState(false);
|
|
11
|
+
const [newPassphrase, setNewPassphrase] = useState("");
|
|
12
|
+
const [confirmPassphrase, setConfirmPassphrase] = useState("");
|
|
13
|
+
const [passphraseError, setPassphraseError] = useState<string | null>(null);
|
|
14
|
+
const [passphraseSuccess, setPassphraseSuccess] = useState(false);
|
|
15
|
+
const [savingPassphrase, setSavingPassphrase] = useState(false);
|
|
16
|
+
|
|
17
|
+
const authFetch = (url: string, opts?: RequestInit) =>
|
|
18
|
+
fetch(url, { ...opts, headers: { ...opts?.headers, Authorization: `Bearer ${token}`, "Content-Type": "application/json" } });
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
authFetch(`${API_BASE}/api/config/llm`)
|
|
22
|
+
.then((r) => r.json())
|
|
23
|
+
.then((data) => setLlmConfig(data));
|
|
24
|
+
}, []);
|
|
25
|
+
|
|
26
|
+
const activeProvider = (llmConfig?.llm as Record<string, unknown>)?.activeProvider as string | undefined;
|
|
27
|
+
const activeModel = (llmConfig?.llm as Record<string, unknown>)?.activeModel as string | undefined;
|
|
28
|
+
|
|
29
|
+
const handleSaveSpendCap = async () => {
|
|
30
|
+
setSavingCap(true);
|
|
31
|
+
setCapSaved(false);
|
|
32
|
+
// Persist spending cap to agent.config.json
|
|
33
|
+
await authFetch(`${API_BASE}/api/config/llm`, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
body: JSON.stringify({ provider: activeProvider || "anthropic", model: activeModel || "", spendCap: Number(spendCap) }),
|
|
36
|
+
});
|
|
37
|
+
setSavingCap(false);
|
|
38
|
+
setCapSaved(true);
|
|
39
|
+
setTimeout(() => setCapSaved(false), 2000);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const handleResetPassphrase = async () => {
|
|
43
|
+
setPassphraseError(null);
|
|
44
|
+
setPassphraseSuccess(false);
|
|
45
|
+
if (!newPassphrase || newPassphrase.length < 4) {
|
|
46
|
+
setPassphraseError("Passphrase must be at least 4 characters");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (newPassphrase !== confirmPassphrase) {
|
|
50
|
+
setPassphraseError("Passphrases do not match");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
setSavingPassphrase(true);
|
|
54
|
+
try {
|
|
55
|
+
const res = await authFetch(`${API_BASE}/api/auth/reset-passphrase`, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
body: JSON.stringify({ passphrase: newPassphrase }),
|
|
58
|
+
});
|
|
59
|
+
if (!res.ok) {
|
|
60
|
+
const data = await res.json();
|
|
61
|
+
throw new Error(data.error || "Reset failed");
|
|
62
|
+
}
|
|
63
|
+
setPassphraseSuccess(true);
|
|
64
|
+
setNewPassphrase("");
|
|
65
|
+
setConfirmPassphrase("");
|
|
66
|
+
setTimeout(() => setPassphraseSuccess(false), 3000);
|
|
67
|
+
} catch (err: unknown) {
|
|
68
|
+
setPassphraseError(err instanceof Error ? err.message : "Reset failed");
|
|
69
|
+
}
|
|
70
|
+
setSavingPassphrase(false);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div className="mx-auto max-w-lg space-y-6 p-6">
|
|
75
|
+
<h2 className="text-accent text-lg font-bold">Settings</h2>
|
|
76
|
+
|
|
77
|
+
{/* LLM Config */}
|
|
78
|
+
<div className="border-border rounded border p-4">
|
|
79
|
+
<h3 className="text-accent mb-3 text-xs font-bold uppercase tracking-wider">LLM Provider</h3>
|
|
80
|
+
{llmConfig ? (
|
|
81
|
+
<div className="space-y-3">
|
|
82
|
+
<div className="flex justify-between text-xs">
|
|
83
|
+
<span className="text-muted">Provider</span>
|
|
84
|
+
<span className="text-foreground font-medium">{activeProvider || "not configured"}</span>
|
|
85
|
+
</div>
|
|
86
|
+
<div className="flex justify-between text-xs">
|
|
87
|
+
<span className="text-muted">Model</span>
|
|
88
|
+
<span className="text-foreground font-medium">{activeModel || "—"}</span>
|
|
89
|
+
</div>
|
|
90
|
+
<div className="flex justify-between text-xs">
|
|
91
|
+
<span className="text-muted">Configured</span>
|
|
92
|
+
<span className="text-foreground font-medium">{llmConfig.configured.join(", ") || "none"}</span>
|
|
93
|
+
</div>
|
|
94
|
+
<button
|
|
95
|
+
onClick={onChangeLLM}
|
|
96
|
+
className="border-accent text-accent hover:bg-accent/10 w-full rounded border px-4 py-2 text-xs font-medium transition-colors"
|
|
97
|
+
>
|
|
98
|
+
change provider / model
|
|
99
|
+
</button>
|
|
100
|
+
</div>
|
|
101
|
+
) : (
|
|
102
|
+
<p className="text-muted text-xs">loading...</p>
|
|
103
|
+
)}
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
{/* Wallet */}
|
|
107
|
+
<WalletCard token={token} />
|
|
108
|
+
|
|
109
|
+
{/* Spending Cap */}
|
|
110
|
+
<div className="border-border rounded border p-4">
|
|
111
|
+
<h3 className="text-accent mb-3 text-xs font-bold uppercase tracking-wider">Spending Cap</h3>
|
|
112
|
+
<div className="flex items-center gap-2">
|
|
113
|
+
<span className="text-muted text-xs">$</span>
|
|
114
|
+
<input
|
|
115
|
+
type="number"
|
|
116
|
+
value={spendCap}
|
|
117
|
+
onChange={(e) => setSpendCap(e.target.value)}
|
|
118
|
+
min="0"
|
|
119
|
+
step="1"
|
|
120
|
+
className="bg-surface border-border text-foreground w-24 rounded border px-2 py-1.5 text-sm outline-none focus:border-accent"
|
|
121
|
+
/>
|
|
122
|
+
<span className="text-muted text-xs">USDC per session</span>
|
|
123
|
+
<button
|
|
124
|
+
onClick={handleSaveSpendCap}
|
|
125
|
+
disabled={savingCap}
|
|
126
|
+
className="border-accent text-accent hover:bg-accent/10 disabled:opacity-40 ml-auto rounded border px-3 py-1.5 text-xs font-medium transition-colors"
|
|
127
|
+
>
|
|
128
|
+
{savingCap ? "saving..." : capSaved ? "saved" : "save"}
|
|
129
|
+
</button>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
{/* Reset Passphrase */}
|
|
134
|
+
<div className="border-border rounded border p-4">
|
|
135
|
+
<h3 className="text-accent mb-3 text-xs font-bold uppercase tracking-wider">Reset Passphrase</h3>
|
|
136
|
+
<div className="space-y-3">
|
|
137
|
+
<input
|
|
138
|
+
type="password"
|
|
139
|
+
value={newPassphrase}
|
|
140
|
+
onChange={(e) => setNewPassphrase(e.target.value)}
|
|
141
|
+
placeholder="new passphrase"
|
|
142
|
+
className="bg-surface border-border text-foreground placeholder:text-muted/50 w-full rounded border px-3 py-2 text-sm outline-none focus:border-accent"
|
|
143
|
+
/>
|
|
144
|
+
<input
|
|
145
|
+
type="password"
|
|
146
|
+
value={confirmPassphrase}
|
|
147
|
+
onChange={(e) => setConfirmPassphrase(e.target.value)}
|
|
148
|
+
placeholder="confirm passphrase"
|
|
149
|
+
className="bg-surface border-border text-foreground placeholder:text-muted/50 w-full rounded border px-3 py-2 text-sm outline-none focus:border-accent"
|
|
150
|
+
/>
|
|
151
|
+
{passphraseError && <p className="text-error text-xs">{passphraseError}</p>}
|
|
152
|
+
{passphraseSuccess && <p className="text-xs text-accent">passphrase updated</p>}
|
|
153
|
+
<button
|
|
154
|
+
onClick={handleResetPassphrase}
|
|
155
|
+
disabled={savingPassphrase || !newPassphrase.trim()}
|
|
156
|
+
className="border-border text-muted hover:border-accent hover:text-accent disabled:opacity-40 w-full rounded border px-4 py-2 text-xs font-medium transition-colors"
|
|
157
|
+
>
|
|
158
|
+
{savingPassphrase ? "updating..." : "update passphrase"}
|
|
159
|
+
</button>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
{/* Session */}
|
|
164
|
+
<div className="border-border rounded border p-4">
|
|
165
|
+
<h3 className="text-accent mb-3 text-xs font-bold uppercase tracking-wider">Session</h3>
|
|
166
|
+
<button
|
|
167
|
+
onClick={onLogout}
|
|
168
|
+
className="border-border text-muted hover:border-error hover:text-error rounded border px-4 py-2 text-xs font-medium transition-colors"
|
|
169
|
+
>
|
|
170
|
+
logout
|
|
171
|
+
</button>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
|
|
3
|
+
export function Setup({ onSetup }: { onSetup: (passphrase: string) => Promise<string | null> }) {
|
|
4
|
+
const [passphrase, setPassphrase] = useState("");
|
|
5
|
+
const [confirm, setConfirm] = useState("");
|
|
6
|
+
const [error, setError] = useState<string | null>(null);
|
|
7
|
+
const [loading, setLoading] = useState(false);
|
|
8
|
+
|
|
9
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
10
|
+
e.preventDefault();
|
|
11
|
+
if (!passphrase.trim() || passphrase.length < 4) {
|
|
12
|
+
setError("Passphrase must be at least 4 characters");
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (passphrase !== confirm) {
|
|
16
|
+
setError("Passphrases do not match");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
setLoading(true);
|
|
20
|
+
setError(null);
|
|
21
|
+
const err = await onSetup(passphrase);
|
|
22
|
+
if (err) setError(err);
|
|
23
|
+
setLoading(false);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className="flex h-screen items-center justify-center p-4">
|
|
28
|
+
<div className="w-full max-w-sm">
|
|
29
|
+
<div className="border-border rounded border p-6">
|
|
30
|
+
<div className="mb-6 text-center">
|
|
31
|
+
<h1 className="text-accent text-lg font-bold tracking-tight">PlotLink OWS</h1>
|
|
32
|
+
<p className="text-muted mt-1 text-xs">first-time setup</p>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<p className="text-muted mb-4 text-xs leading-relaxed">
|
|
36
|
+
Choose a passphrase to protect your local writer agent.
|
|
37
|
+
This will be used to unlock the app and secure your OWS wallet.
|
|
38
|
+
</p>
|
|
39
|
+
|
|
40
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
41
|
+
<div>
|
|
42
|
+
<label className="text-muted mb-1.5 block text-xs uppercase tracking-wider">
|
|
43
|
+
Passphrase
|
|
44
|
+
</label>
|
|
45
|
+
<input
|
|
46
|
+
type="password"
|
|
47
|
+
value={passphrase}
|
|
48
|
+
onChange={(e) => setPassphrase(e.target.value)}
|
|
49
|
+
placeholder="choose a passphrase"
|
|
50
|
+
autoFocus
|
|
51
|
+
className="bg-surface border-border text-foreground placeholder:text-muted/50 w-full rounded border px-3 py-2 text-sm outline-none focus:border-accent"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div>
|
|
56
|
+
<label className="text-muted mb-1.5 block text-xs uppercase tracking-wider">
|
|
57
|
+
Confirm
|
|
58
|
+
</label>
|
|
59
|
+
<input
|
|
60
|
+
type="password"
|
|
61
|
+
value={confirm}
|
|
62
|
+
onChange={(e) => setConfirm(e.target.value)}
|
|
63
|
+
placeholder="repeat passphrase"
|
|
64
|
+
className="bg-surface border-border text-foreground placeholder:text-muted/50 w-full rounded border px-3 py-2 text-sm outline-none focus:border-accent"
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
{error && (
|
|
69
|
+
<p className="text-error text-xs">{error}</p>
|
|
70
|
+
)}
|
|
71
|
+
|
|
72
|
+
<button
|
|
73
|
+
type="submit"
|
|
74
|
+
disabled={loading || !passphrase.trim() || !confirm.trim()}
|
|
75
|
+
className="border-accent text-accent hover:bg-accent/10 disabled:opacity-40 w-full rounded border px-4 py-2 text-sm font-medium transition-colors"
|
|
76
|
+
>
|
|
77
|
+
{loading ? "setting up..." : "create passphrase"}
|
|
78
|
+
</button>
|
|
79
|
+
</form>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
const API_BASE = "http://localhost:7777";
|
|
4
|
+
|
|
5
|
+
interface WalletInfo {
|
|
6
|
+
exists: boolean;
|
|
7
|
+
walletId?: string;
|
|
8
|
+
name?: string;
|
|
9
|
+
address?: string;
|
|
10
|
+
usdcBalance?: string;
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function WalletCard({ token }: { token: string }) {
|
|
15
|
+
const [wallet, setWallet] = useState<WalletInfo | null>(null);
|
|
16
|
+
const [creating, setCreating] = useState(false);
|
|
17
|
+
const [copied, setCopied] = useState(false);
|
|
18
|
+
const [error, setError] = useState<string | null>(null);
|
|
19
|
+
|
|
20
|
+
const authFetch = (url: string, opts?: RequestInit) =>
|
|
21
|
+
fetch(url, { ...opts, headers: { ...opts?.headers, Authorization: `Bearer ${token}`, "Content-Type": "application/json" } });
|
|
22
|
+
|
|
23
|
+
const loadWallet = () => {
|
|
24
|
+
authFetch(`${API_BASE}/api/wallet`)
|
|
25
|
+
.then((r) => r.json())
|
|
26
|
+
.then((data) => setWallet(data))
|
|
27
|
+
.catch(() => setWallet({ exists: false, error: "Failed to load wallet" }));
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
useEffect(() => { loadWallet(); }, []);
|
|
31
|
+
|
|
32
|
+
const handleCreate = async () => {
|
|
33
|
+
setCreating(true);
|
|
34
|
+
setError(null);
|
|
35
|
+
try {
|
|
36
|
+
const res = await authFetch(`${API_BASE}/api/wallet/create`, { method: "POST" });
|
|
37
|
+
const data = await res.json();
|
|
38
|
+
if (!res.ok) throw new Error(data.error || "Creation failed");
|
|
39
|
+
loadWallet();
|
|
40
|
+
} catch (err: unknown) {
|
|
41
|
+
setError(err instanceof Error ? err.message : "Failed to create wallet");
|
|
42
|
+
}
|
|
43
|
+
setCreating(false);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const copyAddress = () => {
|
|
47
|
+
if (wallet?.address) {
|
|
48
|
+
navigator.clipboard.writeText(wallet.address);
|
|
49
|
+
setCopied(true);
|
|
50
|
+
setTimeout(() => setCopied(false), 2000);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const truncate = (addr: string) => `${addr.slice(0, 6)}...${addr.slice(-4)}`;
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className="border-border rounded border p-4">
|
|
58
|
+
<h3 className="text-accent mb-3 text-xs font-bold uppercase tracking-wider">OWS Wallet</h3>
|
|
59
|
+
|
|
60
|
+
{!wallet && <p className="text-muted text-xs">loading...</p>}
|
|
61
|
+
|
|
62
|
+
{wallet && !wallet.exists && (
|
|
63
|
+
<div className="space-y-3">
|
|
64
|
+
<p className="text-muted text-xs">No wallet created yet. Create one to enable autonomous transactions.</p>
|
|
65
|
+
{error && <p className="text-error text-xs">{error}</p>}
|
|
66
|
+
<button
|
|
67
|
+
onClick={handleCreate}
|
|
68
|
+
disabled={creating}
|
|
69
|
+
className="border-accent text-accent hover:bg-accent/10 disabled:opacity-40 rounded border px-4 py-2 text-xs font-medium transition-colors"
|
|
70
|
+
>
|
|
71
|
+
{creating ? "creating..." : "create wallet"}
|
|
72
|
+
</button>
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
|
|
76
|
+
{wallet && wallet.exists && wallet.address && (
|
|
77
|
+
<div className="space-y-3">
|
|
78
|
+
<div className="flex items-center justify-between">
|
|
79
|
+
<span className="text-muted text-[10px] uppercase tracking-wider">Address (Base)</span>
|
|
80
|
+
<span className={`rounded border px-1.5 py-0.5 text-[9px] ${wallet.usdcBalance && parseFloat(wallet.usdcBalance) > 0 ? "border-accent/30 text-accent" : "border-accent-dim/30 text-accent-dim"}`}>
|
|
81
|
+
{wallet.usdcBalance && parseFloat(wallet.usdcBalance) > 0 ? "active" : "no balance"}
|
|
82
|
+
</span>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div className="flex items-center gap-2">
|
|
86
|
+
<code className="text-foreground bg-surface rounded px-2 py-1 text-xs font-mono">{truncate(wallet.address)}</code>
|
|
87
|
+
<button onClick={copyAddress} className="text-muted hover:text-accent text-xs transition-colors">
|
|
88
|
+
{copied ? "copied" : "copy"}
|
|
89
|
+
</button>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div className="border-border space-y-1 border-t pt-3">
|
|
93
|
+
<div className="flex justify-between text-xs">
|
|
94
|
+
<span className="text-muted">USDC Balance</span>
|
|
95
|
+
<span className="text-foreground font-medium">${wallet.usdcBalance || "0.00"}</span>
|
|
96
|
+
</div>
|
|
97
|
+
<div className="flex justify-between text-xs">
|
|
98
|
+
<span className="text-muted">Network</span>
|
|
99
|
+
<span className="text-foreground">Base</span>
|
|
100
|
+
</div>
|
|
101
|
+
<div className="flex justify-between text-xs">
|
|
102
|
+
<span className="text-muted">Wallet ID</span>
|
|
103
|
+
<span className="text-foreground font-mono text-[10px]">{wallet.walletId?.slice(0, 12)}...</span>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
{/* Fund wallet */}
|
|
108
|
+
<div className="border-border border-t pt-3">
|
|
109
|
+
<p className="text-muted mb-2 text-[10px] font-medium uppercase tracking-wider">Fund Wallet</p>
|
|
110
|
+
<p className="text-muted text-[10px]">Send USDC on Base to:</p>
|
|
111
|
+
<code className="text-foreground bg-surface mt-1 block break-all rounded px-2 py-1.5 text-[10px] font-mono">{wallet.address}</code>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial}}}@layer theme{:root,:host{--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--container-sm:24rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-medium:500;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wider:.05em;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent;font-family:Geist Mono,ui-monospace,monospace;line-height:1.5}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.block{display:block}.flex{display:flex}.h-screen{height:100vh}.w-full{width:100%}.max-w-sm{max-width:var(--container-sm)}.flex-1{flex:1}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-3{gap:calc(var(--spacing) * 3)}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}.rounded{border-radius:.25rem}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-accent{border-color:var(--accent)}.border-border{border-color:var(--border)}.bg-surface{background-color:var(--bg-surface)}.p-4{padding:calc(var(--spacing) * 4)}.p-6{padding:calc(var(--spacing) * 6)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.text-center{text-align:center}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.text-accent{color:var(--accent)}.text-error{color:var(--error)}.text-foreground{color:var(--text)}.text-muted{color:var(--text-muted)}.uppercase{text-transform:uppercase}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.placeholder\:text-muted\/50::placeholder{color:var(--text-muted)}@supports (color:color-mix(in lab, red, red)){.placeholder\:text-muted\/50::placeholder{color:color-mix(in oklab, var(--text-muted) 50%, transparent)}}@media (hover:hover){.hover\:bg-accent\/10:hover{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-accent\/10:hover{background-color:color-mix(in oklab, var(--accent) 10%, transparent)}}.hover\:text-foreground:hover{color:var(--text)}}.focus\:border-accent:focus{border-color:var(--accent)}.disabled\:opacity-40:disabled{opacity:.4}}:root{--bg:#0a0a0a;--bg-surface:#141414;--text:#e0e0e0;--text-muted:#666;--accent:#0f8;--accent-dim:#00cc6a;--border:#2a2a2a;--error:#f44}body{background:var(--bg);color:var(--text);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:Geist Mono,ui-monospace,monospace}::selection{background:var(--accent);color:var(--bg)}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}
|