plotlink-ows 1.0.16 → 1.0.19

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.
@@ -8,6 +8,21 @@ import { db } from "../db";
8
8
  import {
9
9
  signMessage as owsSignMsg,
10
10
  } from "@open-wallet-standard/core";
11
+ import { CONFIG_DIR } from "../lib/paths";
12
+ import fs from "fs";
13
+ import path from "path";
14
+
15
+ const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
16
+
17
+ function readConfig(): Record<string, unknown> {
18
+ try { return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8")); } catch { return {}; }
19
+ }
20
+
21
+ function writeConfig(updates: Record<string, unknown>) {
22
+ const config = readConfig();
23
+ Object.assign(config, updates);
24
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
25
+ }
11
26
 
12
27
  const ERC_8004 = "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432" as const;
13
28
  const rpcUrl = process.env.NEXT_PUBLIC_RPC_URL || "https://mainnet.base.org";
@@ -41,10 +56,15 @@ settings.post("/generate-binding", async (c) => {
41
56
  const result = owsSignMsg(wallet.name, "eip155:8453", message, passphrase);
42
57
  const signature = result.signature.startsWith("0x") ? result.signature : `0x${result.signature}`;
43
58
 
59
+ // Include agentId from config.json if available
60
+ const config = readConfig();
61
+ const agentId = config.agentId ? Number(config.agentId) : undefined;
62
+
44
63
  return c.json({
45
64
  message,
46
65
  signature,
47
66
  owsWallet,
67
+ agentId,
48
68
  });
49
69
  } catch (err: unknown) {
50
70
  const msg = err instanceof Error ? err.message : "Failed to generate binding proof";
@@ -133,12 +153,8 @@ settings.post("/register-agent", async (c) => {
133
153
  return c.json({ error: "Transaction succeeded but Registered event not found" }, 500);
134
154
  }
135
155
 
136
- // Store agentId locally
137
- await db.setting.upsert({
138
- where: { key: "agent_id" },
139
- update: { value: String(agentId) },
140
- create: { key: "agent_id", value: String(agentId) },
141
- });
156
+ // Cache agentId in config.json (survives npx reinstalls, no Prisma dependency)
157
+ writeConfig({ agentId });
142
158
 
143
159
  return c.json({
144
160
  agentId,
@@ -161,6 +177,13 @@ settings.get("/link-status", async (c) => {
161
177
  const address = getBaseAddress(wallet);
162
178
  if (!address) return c.json({ linked: false, error: "No EVM address" });
163
179
 
180
+ // Check config.json cache first (survives npx reinstalls + RPC rate limits)
181
+ const config = readConfig();
182
+ if (config.agentId) {
183
+ return c.json({ linked: true, agentId: Number(config.agentId), owsWallet: address });
184
+ }
185
+
186
+ // RPC: try agentIdByWallet (for bound wallets)
164
187
  try {
165
188
  const agentId = await publicClient.readContract({
166
189
  address: ERC_8004,
@@ -170,21 +193,12 @@ settings.get("/link-status", async (c) => {
170
193
  }) as bigint;
171
194
 
172
195
  if (agentId > 0n) {
173
- // Fetch NFT owner (ERC-721 ownerOf)
174
- let owner: string | undefined;
175
- try {
176
- owner = await publicClient.readContract({
177
- address: ERC_8004,
178
- abi: [{ type: "function", name: "ownerOf", stateMutability: "view", inputs: [{ name: "tokenId", type: "uint256" }], outputs: [{ name: "", type: "address" }] }] as const,
179
- functionName: "ownerOf",
180
- args: [agentId],
181
- }) as string;
182
- } catch { /* best effort */ }
183
- return c.json({ linked: true, agentId: Number(agentId), owsWallet: address, owner });
196
+ writeConfig({ agentId: Number(agentId) });
197
+ return c.json({ linked: true, agentId: Number(agentId), owsWallet: address });
184
198
  }
185
199
  } catch { /* agentIdByWallet may revert if not bound */ }
186
200
 
187
- // Fallback: check if wallet owns an agent NFT (register() creates NFT but doesn't bind via setAgentWallet)
201
+ // RPC fallback: check balanceOf (for owned but unbound NFTs)
188
202
  try {
189
203
  const balance = await publicClient.readContract({
190
204
  address: ERC_8004,
@@ -194,7 +208,7 @@ settings.get("/link-status", async (c) => {
194
208
  }) as bigint;
195
209
 
196
210
  if (balance > 0n) {
197
- // Try to get token ID (ERC-721 Enumerable — may not be supported)
211
+ // Try to get token ID
198
212
  let agentId: number | undefined;
199
213
  try {
200
214
  const tokenId = await publicClient.readContract({
@@ -204,11 +218,14 @@ settings.get("/link-status", async (c) => {
204
218
  args: [address as `0x${string}`, 0n],
205
219
  }) as bigint;
206
220
  agentId = Number(tokenId);
207
- } catch { /* ERC-721 Enumerable not supported — agent ID unknown */ }
221
+ } catch { /* ERC-721 Enumerable not supported */ }
208
222
 
223
+ if (agentId !== undefined) {
224
+ writeConfig({ agentId });
225
+ }
209
226
  return c.json({ linked: true, agentId, owsWallet: address });
210
227
  }
211
- } catch { /* balanceOf failed */ }
228
+ } catch { /* RPC failed — rate limited or unavailable */ }
212
229
 
213
230
  return c.json({ linked: false, owsWallet: address });
214
231
  } catch (err: unknown) {
@@ -18,7 +18,7 @@ export function Settings({ token, onLogout }: { token: string; onLogout: () => v
18
18
 
19
19
  // Link to PlotLink (binding proof for DB link)
20
20
  const [humanWallet, setHumanWallet] = useState("");
21
- const [bindingResult, setBindingResult] = useState<{ message: string; signature: string; owsWallet: string } | null>(null);
21
+ const [bindingResult, setBindingResult] = useState<{ message: string; signature: string; owsWallet: string; agentId?: number } | null>(null);
22
22
  const [generating, setGenerating] = useState(false);
23
23
  const [bindingError, setBindingError] = useState<string | null>(null);
24
24
  const [copied, setCopied] = useState<"signature" | "wallet" | null>(null);
@@ -268,8 +268,24 @@ export function Settings({ token, onLogout }: { token: string; onLogout: () => v
268
268
  </button>
269
269
  </div>
270
270
  </div>
271
+ {bindingResult.agentId && (
272
+ <div>
273
+ <label className="text-muted text-xs block mb-1">Agent ID</label>
274
+ <div className="relative">
275
+ <div className="bg-surface border-border rounded border p-2 text-xs font-mono text-foreground pr-16">
276
+ {bindingResult.agentId}
277
+ </div>
278
+ <button
279
+ onClick={() => copyToClipboard(String(bindingResult.agentId), "agentId")}
280
+ className="absolute top-1 right-1 text-xs px-2 py-1 rounded border border-border text-muted hover:text-accent hover:border-accent transition-colors"
281
+ >
282
+ {copied === "agentId" ? "Copied!" : "Copy"}
283
+ </button>
284
+ </div>
285
+ </div>
286
+ )}
271
287
  <p className="text-xs text-accent">
272
- Now go to plotlink.xyz/agents and paste both values in the &quot;Link AI Writer&quot; section.
288
+ Now go to plotlink.xyz/agents and paste the values in the &quot;Link AI Writer&quot; section.
273
289
  </p>
274
290
  </div>
275
291
  )}