@elizaos/autonomous 2.0.0-alpha.10

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.
Files changed (241) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +270 -0
  3. package/src/actions/emote.ts +101 -0
  4. package/src/actions/restart.ts +101 -0
  5. package/src/actions/send-message.ts +168 -0
  6. package/src/actions/stream-control.ts +439 -0
  7. package/src/actions/switch-stream-source.ts +126 -0
  8. package/src/actions/terminal.ts +186 -0
  9. package/src/api/agent-admin-routes.ts +178 -0
  10. package/src/api/agent-lifecycle-routes.ts +129 -0
  11. package/src/api/agent-model.ts +143 -0
  12. package/src/api/agent-transfer-routes.ts +211 -0
  13. package/src/api/apps-routes.ts +210 -0
  14. package/src/api/auth-routes.ts +90 -0
  15. package/src/api/bsc-trade.ts +736 -0
  16. package/src/api/bug-report-routes.ts +161 -0
  17. package/src/api/character-routes.ts +421 -0
  18. package/src/api/cloud-billing-routes.ts +598 -0
  19. package/src/api/cloud-compat-routes.ts +192 -0
  20. package/src/api/cloud-routes.ts +529 -0
  21. package/src/api/cloud-status-routes.ts +234 -0
  22. package/src/api/compat-utils.ts +154 -0
  23. package/src/api/connector-health.ts +135 -0
  24. package/src/api/coordinator-wiring.ts +179 -0
  25. package/src/api/credit-detection.ts +47 -0
  26. package/src/api/database.ts +1357 -0
  27. package/src/api/diagnostics-routes.ts +389 -0
  28. package/src/api/drop-service.ts +205 -0
  29. package/src/api/early-logs.ts +111 -0
  30. package/src/api/http-helpers.ts +252 -0
  31. package/src/api/index.ts +85 -0
  32. package/src/api/knowledge-routes.ts +1189 -0
  33. package/src/api/knowledge-service-loader.ts +92 -0
  34. package/src/api/memory-bounds.ts +121 -0
  35. package/src/api/memory-routes.ts +349 -0
  36. package/src/api/merkle-tree.ts +239 -0
  37. package/src/api/models-routes.ts +72 -0
  38. package/src/api/nfa-routes.ts +169 -0
  39. package/src/api/nft-verify.ts +188 -0
  40. package/src/api/og-tracker.ts +72 -0
  41. package/src/api/parse-action-block.ts +145 -0
  42. package/src/api/permissions-routes.ts +222 -0
  43. package/src/api/plugin-validation.ts +355 -0
  44. package/src/api/provider-switch-config.ts +455 -0
  45. package/src/api/registry-routes.ts +165 -0
  46. package/src/api/registry-service.ts +292 -0
  47. package/src/api/route-helpers.ts +21 -0
  48. package/src/api/sandbox-routes.ts +1480 -0
  49. package/src/api/server.ts +17674 -0
  50. package/src/api/signal-routes.ts +265 -0
  51. package/src/api/stream-persistence.ts +297 -0
  52. package/src/api/stream-route-state.ts +48 -0
  53. package/src/api/stream-routes.ts +1046 -0
  54. package/src/api/stream-voice-routes.ts +208 -0
  55. package/src/api/streaming-text.ts +129 -0
  56. package/src/api/streaming-types.ts +23 -0
  57. package/src/api/subscription-routes.ts +283 -0
  58. package/src/api/terminal-run-limits.ts +31 -0
  59. package/src/api/training-backend-check.ts +40 -0
  60. package/src/api/training-routes.ts +314 -0
  61. package/src/api/training-service-like.ts +46 -0
  62. package/src/api/trajectory-routes.ts +714 -0
  63. package/src/api/trigger-routes.ts +438 -0
  64. package/src/api/twitter-verify.ts +226 -0
  65. package/src/api/tx-service.ts +193 -0
  66. package/src/api/wallet-dex-prices.ts +206 -0
  67. package/src/api/wallet-evm-balance.ts +989 -0
  68. package/src/api/wallet-routes.ts +505 -0
  69. package/src/api/wallet-rpc.ts +523 -0
  70. package/src/api/wallet-trading-profile.ts +694 -0
  71. package/src/api/wallet.ts +745 -0
  72. package/src/api/whatsapp-routes.ts +282 -0
  73. package/src/api/zip-utils.ts +130 -0
  74. package/src/auth/anthropic.ts +63 -0
  75. package/src/auth/apply-stealth.ts +38 -0
  76. package/src/auth/claude-code-stealth.ts +141 -0
  77. package/src/auth/credentials.ts +226 -0
  78. package/src/auth/index.ts +18 -0
  79. package/src/auth/openai-codex.ts +94 -0
  80. package/src/auth/types.ts +24 -0
  81. package/src/awareness/registry.ts +220 -0
  82. package/src/bin.ts +10 -0
  83. package/src/cli/index.ts +36 -0
  84. package/src/cli/parse-duration.ts +43 -0
  85. package/src/cloud/auth.test.ts +370 -0
  86. package/src/cloud/auth.ts +176 -0
  87. package/src/cloud/backup.test.ts +150 -0
  88. package/src/cloud/backup.ts +50 -0
  89. package/src/cloud/base-url.ts +45 -0
  90. package/src/cloud/bridge-client.test.ts +481 -0
  91. package/src/cloud/bridge-client.ts +307 -0
  92. package/src/cloud/cloud-manager.test.ts +223 -0
  93. package/src/cloud/cloud-manager.ts +151 -0
  94. package/src/cloud/cloud-proxy.test.ts +122 -0
  95. package/src/cloud/cloud-proxy.ts +52 -0
  96. package/src/cloud/index.ts +23 -0
  97. package/src/cloud/reconnect.test.ts +178 -0
  98. package/src/cloud/reconnect.ts +108 -0
  99. package/src/cloud/validate-url.test.ts +147 -0
  100. package/src/cloud/validate-url.ts +176 -0
  101. package/src/config/character-schema.ts +44 -0
  102. package/src/config/config.ts +149 -0
  103. package/src/config/env-vars.ts +86 -0
  104. package/src/config/includes.ts +196 -0
  105. package/src/config/index.ts +15 -0
  106. package/src/config/object-utils.ts +10 -0
  107. package/src/config/paths.ts +92 -0
  108. package/src/config/plugin-auto-enable.ts +520 -0
  109. package/src/config/schema.ts +1342 -0
  110. package/src/config/telegram-custom-commands.ts +99 -0
  111. package/src/config/types.agent-defaults.ts +342 -0
  112. package/src/config/types.agents.ts +112 -0
  113. package/src/config/types.gateway.ts +243 -0
  114. package/src/config/types.hooks.ts +124 -0
  115. package/src/config/types.messages.ts +201 -0
  116. package/src/config/types.milady.ts +791 -0
  117. package/src/config/types.tools.ts +416 -0
  118. package/src/config/types.ts +7 -0
  119. package/src/config/zod-schema.agent-runtime.ts +777 -0
  120. package/src/config/zod-schema.core.ts +778 -0
  121. package/src/config/zod-schema.hooks.ts +139 -0
  122. package/src/config/zod-schema.providers-core.ts +1126 -0
  123. package/src/config/zod-schema.session.ts +98 -0
  124. package/src/config/zod-schema.ts +865 -0
  125. package/src/contracts/apps.ts +46 -0
  126. package/src/contracts/awareness.ts +56 -0
  127. package/src/contracts/config.ts +172 -0
  128. package/src/contracts/drop.ts +21 -0
  129. package/src/contracts/index.ts +8 -0
  130. package/src/contracts/onboarding.ts +592 -0
  131. package/src/contracts/permissions.ts +52 -0
  132. package/src/contracts/verification.ts +9 -0
  133. package/src/contracts/wallet.ts +503 -0
  134. package/src/diagnostics/integration-observability.ts +132 -0
  135. package/src/emotes/catalog.ts +655 -0
  136. package/src/external-modules.d.ts +7 -0
  137. package/src/hooks/discovery.test.ts +357 -0
  138. package/src/hooks/discovery.ts +231 -0
  139. package/src/hooks/eligibility.ts +146 -0
  140. package/src/hooks/hooks.test.ts +320 -0
  141. package/src/hooks/index.ts +8 -0
  142. package/src/hooks/loader.test.ts +418 -0
  143. package/src/hooks/loader.ts +256 -0
  144. package/src/hooks/registry.test.ts +168 -0
  145. package/src/hooks/registry.ts +74 -0
  146. package/src/hooks/types.ts +121 -0
  147. package/src/index.ts +19 -0
  148. package/src/onboarding-presets.ts +828 -0
  149. package/src/plugins/custom-rtmp/index.ts +40 -0
  150. package/src/providers/admin-trust.ts +76 -0
  151. package/src/providers/session-bridge.ts +143 -0
  152. package/src/providers/session-utils.ts +42 -0
  153. package/src/providers/simple-mode.ts +113 -0
  154. package/src/providers/ui-catalog.ts +135 -0
  155. package/src/providers/workspace-provider.ts +213 -0
  156. package/src/providers/workspace.ts +497 -0
  157. package/src/runtime/agent-event-service.ts +57 -0
  158. package/src/runtime/cloud-onboarding.test.ts +489 -0
  159. package/src/runtime/cloud-onboarding.ts +408 -0
  160. package/src/runtime/core-plugins.ts +53 -0
  161. package/src/runtime/custom-actions.ts +605 -0
  162. package/src/runtime/eliza.ts +4941 -0
  163. package/src/runtime/embedding-presets.ts +73 -0
  164. package/src/runtime/index.ts +8 -0
  165. package/src/runtime/milady-plugin.ts +180 -0
  166. package/src/runtime/onboarding-names.ts +76 -0
  167. package/src/runtime/release-plugin-policy.ts +119 -0
  168. package/src/runtime/restart.ts +59 -0
  169. package/src/runtime/trajectory-persistence.ts +2584 -0
  170. package/src/runtime/version.ts +6 -0
  171. package/src/security/audit-log.ts +222 -0
  172. package/src/security/network-policy.ts +91 -0
  173. package/src/server/index.ts +6 -0
  174. package/src/services/agent-export.ts +976 -0
  175. package/src/services/app-manager.ts +755 -0
  176. package/src/services/browser-capture.ts +215 -0
  177. package/src/services/coding-agent-context.ts +355 -0
  178. package/src/services/fallback-training-service.ts +196 -0
  179. package/src/services/index.ts +17 -0
  180. package/src/services/mcp-marketplace.ts +327 -0
  181. package/src/services/plugin-manager-types.ts +185 -0
  182. package/src/services/privy-wallets.ts +352 -0
  183. package/src/services/registry-client-app-meta.ts +201 -0
  184. package/src/services/registry-client-endpoints.ts +253 -0
  185. package/src/services/registry-client-local.ts +485 -0
  186. package/src/services/registry-client-network.ts +173 -0
  187. package/src/services/registry-client-queries.ts +176 -0
  188. package/src/services/registry-client-types.ts +104 -0
  189. package/src/services/registry-client.ts +366 -0
  190. package/src/services/remote-signing-service.ts +261 -0
  191. package/src/services/sandbox-engine.ts +753 -0
  192. package/src/services/sandbox-manager.ts +503 -0
  193. package/src/services/self-updater.ts +213 -0
  194. package/src/services/signal-pairing.ts +189 -0
  195. package/src/services/signing-policy.ts +230 -0
  196. package/src/services/skill-catalog-client.ts +195 -0
  197. package/src/services/skill-marketplace.ts +909 -0
  198. package/src/services/stream-manager.ts +707 -0
  199. package/src/services/tts-stream-bridge.ts +465 -0
  200. package/src/services/update-checker.ts +163 -0
  201. package/src/services/version-compat.ts +367 -0
  202. package/src/services/whatsapp-pairing.ts +279 -0
  203. package/src/shared/ui-catalog-prompt.ts +1158 -0
  204. package/src/test-support/process-helpers.ts +35 -0
  205. package/src/test-support/route-test-helpers.ts +113 -0
  206. package/src/test-support/test-helpers.ts +304 -0
  207. package/src/testing/index.ts +3 -0
  208. package/src/triggers/action.ts +342 -0
  209. package/src/triggers/runtime.ts +432 -0
  210. package/src/triggers/scheduling.ts +472 -0
  211. package/src/triggers/types.ts +133 -0
  212. package/src/types/app-hyperscape-routes-shim.d.ts +29 -0
  213. package/src/types/external-modules.d.ts +7 -0
  214. package/src/utils/exec-safety.ts +23 -0
  215. package/src/utils/number-parsing.ts +112 -0
  216. package/src/utils/spoken-text.ts +65 -0
  217. package/src/version-resolver.ts +60 -0
  218. package/test/api/agent-admin-routes.test.ts +160 -0
  219. package/test/api/agent-lifecycle-routes.test.ts +164 -0
  220. package/test/api/agent-transfer-routes.test.ts +136 -0
  221. package/test/api/apps-routes.test.ts +140 -0
  222. package/test/api/auth-routes.test.ts +160 -0
  223. package/test/api/bug-report-routes.test.ts +88 -0
  224. package/test/api/knowledge-routes.test.ts +73 -0
  225. package/test/api/lifecycle.test.ts +342 -0
  226. package/test/api/memory-routes.test.ts +74 -0
  227. package/test/api/models-routes.test.ts +112 -0
  228. package/test/api/nfa-routes.test.ts +78 -0
  229. package/test/api/permissions-routes.test.ts +185 -0
  230. package/test/api/registry-routes.test.ts +157 -0
  231. package/test/api/signal-routes.test.ts +113 -0
  232. package/test/api/subscription-routes.test.ts +90 -0
  233. package/test/api/trigger-routes.test.ts +87 -0
  234. package/test/api/wallet-routes.observability.test.ts +191 -0
  235. package/test/api/wallet-routes.test.ts +502 -0
  236. package/test/diagnostics/integration-observability.test.ts +135 -0
  237. package/test/security/audit-log.test.ts +229 -0
  238. package/test/security/network-policy.test.ts +143 -0
  239. package/test/services/version-compat.test.ts +127 -0
  240. package/tsconfig.build.json +21 -0
  241. package/tsconfig.json +19 -0
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Merkle tree service for whitelist proof generation.
3
+ *
4
+ * Builds a Merkle tree from verified addresses in whitelist.json and
5
+ * generates proofs that can be submitted to the smart contract's
6
+ * `mintWhitelist(name, endpoint, capHash, proof[])` function.
7
+ *
8
+ * This is the missing bridge between:
9
+ * whitelist.json → Merkle tree → on-chain mintWhitelist()
10
+ *
11
+ * The contract stores a `merkleRoot()` and verifies each mint against
12
+ * a proof derived from the caller's address.
13
+ *
14
+ * Standard: leaves = keccak256(abi.encodePacked(address))
15
+ * sorted pairs to ensure deterministic tree construction.
16
+ *
17
+ * @see drop-service.ts — mintWithWhitelist() consumer
18
+ * @see twitter-verify.ts — one source of whitelist addresses
19
+ * @see nft-verify.ts — another source of whitelist addresses
20
+ */
21
+
22
+ import { logger } from "@elizaos/core";
23
+ import { ethers } from "ethers";
24
+ import { getVerifiedAddresses } from "./twitter-verify";
25
+
26
+ // ── Types ────────────────────────────────────────────────────────────────
27
+
28
+ export interface MerkleProofResult {
29
+ /** The proof array (bytes32[]) to submit to mintWhitelist(). */
30
+ proof: string[];
31
+ /** The leaf hash for this address. */
32
+ leaf: string;
33
+ /** The root of the current tree. */
34
+ root: string;
35
+ /** Whether this address is in the tree. */
36
+ isWhitelisted: boolean;
37
+ }
38
+
39
+ export interface MerkleTreeInfo {
40
+ /** The root hash of the current tree. */
41
+ root: string;
42
+ /** Total number of addresses in the tree. */
43
+ addressCount: number;
44
+ /** All leaf hashes (sorted). */
45
+ leaves: string[];
46
+ }
47
+
48
+ // ── Leaf hashing ─────────────────────────────────────────────────────────
49
+
50
+ /**
51
+ * Hash an address into a Merkle leaf.
52
+ *
53
+ * Uses `keccak256(abi.encodePacked(address))` — the standard Solidity
54
+ * pattern for OpenZeppelin MerkleProof verification.
55
+ */
56
+ export function hashLeaf(address: string): string {
57
+ return ethers.solidityPackedKeccak256(
58
+ ["address"],
59
+ [ethers.getAddress(address)], // checksummed
60
+ );
61
+ }
62
+
63
+ // ── Merkle tree construction ─────────────────────────────────────────────
64
+
65
+ /**
66
+ * Sort a pair of hashes for deterministic tree construction.
67
+ * This ensures the tree is the same regardless of insertion order.
68
+ */
69
+ function sortPair(a: string, b: string): [string, string] {
70
+ return a.toLowerCase() < b.toLowerCase() ? [a, b] : [b, a];
71
+ }
72
+
73
+ /**
74
+ * Hash two nodes together (internal node).
75
+ */
76
+ function hashPair(a: string, b: string): string {
77
+ const [left, right] = sortPair(a, b);
78
+ return ethers.solidityPackedKeccak256(["bytes32", "bytes32"], [left, right]);
79
+ }
80
+
81
+ /**
82
+ * Build a Merkle tree from an array of leaf hashes.
83
+ *
84
+ * Returns the tree as a 2D array where:
85
+ * tree[0] = leaves (sorted)
86
+ * tree[1] = first level of internal nodes
87
+ * ...
88
+ * tree[tree.length - 1] = [root]
89
+ *
90
+ * Leaves are sorted before building to ensure determinism.
91
+ */
92
+ export function buildTree(leaves: string[]): string[][] {
93
+ if (leaves.length === 0) {
94
+ return [[`0x${"0".repeat(64)}`]]; // empty tree → zero root
95
+ }
96
+
97
+ // Sort leaves for deterministic construction
98
+ const sorted = [...leaves].sort((a, b) =>
99
+ a.toLowerCase().localeCompare(b.toLowerCase()),
100
+ );
101
+
102
+ const tree: string[][] = [sorted];
103
+ let currentLevel = sorted;
104
+
105
+ while (currentLevel.length > 1) {
106
+ const nextLevel: string[] = [];
107
+ for (let i = 0; i < currentLevel.length; i += 2) {
108
+ if (i + 1 < currentLevel.length) {
109
+ nextLevel.push(hashPair(currentLevel[i], currentLevel[i + 1]));
110
+ } else {
111
+ // Odd node: promote to next level (no sibling)
112
+ nextLevel.push(currentLevel[i]);
113
+ }
114
+ }
115
+ tree.push(nextLevel);
116
+ currentLevel = nextLevel;
117
+ }
118
+
119
+ return tree;
120
+ }
121
+
122
+ /**
123
+ * Get the proof (sibling path) for a leaf in the tree.
124
+ */
125
+ export function getProof(tree: string[][], leaf: string): string[] {
126
+ const proof: string[] = [];
127
+ let index = tree[0].indexOf(leaf);
128
+
129
+ if (index === -1) return []; // leaf not in tree
130
+
131
+ for (let level = 0; level < tree.length - 1; level++) {
132
+ const currentLevel = tree[level];
133
+ const isRightNode = index % 2 === 1;
134
+ const siblingIndex = isRightNode ? index - 1 : index + 1;
135
+
136
+ if (siblingIndex < currentLevel.length) {
137
+ proof.push(currentLevel[siblingIndex]);
138
+ }
139
+
140
+ index = Math.floor(index / 2);
141
+ }
142
+
143
+ return proof;
144
+ }
145
+
146
+ /**
147
+ * Get the root of the tree.
148
+ */
149
+ export function getRoot(tree: string[][]): string {
150
+ return tree[tree.length - 1][0];
151
+ }
152
+
153
+ /**
154
+ * Verify a proof against a root.
155
+ *
156
+ * Recomputes the root from the leaf + proof path and checks
157
+ * if it matches the expected root.
158
+ */
159
+ export function verifyProof(
160
+ leaf: string,
161
+ proof: string[],
162
+ expectedRoot: string,
163
+ ): boolean {
164
+ let computed = leaf;
165
+ for (const sibling of proof) {
166
+ computed = hashPair(computed, sibling);
167
+ }
168
+ return computed.toLowerCase() === expectedRoot.toLowerCase();
169
+ }
170
+
171
+ // ── High-level API ───────────────────────────────────────────────────────
172
+
173
+ /**
174
+ * Build a Merkle tree from the current whitelist.
175
+ *
176
+ * Reads all verified addresses from whitelist.json, hashes them into
177
+ * leaves, and builds the tree.
178
+ */
179
+ export function buildWhitelistTree(): {
180
+ tree: string[][];
181
+ info: MerkleTreeInfo;
182
+ } {
183
+ const addresses = getVerifiedAddresses();
184
+ const leaves = addresses.map((addr) => hashLeaf(addr));
185
+
186
+ const tree = buildTree(leaves);
187
+ const root = getRoot(tree);
188
+
189
+ logger.info(
190
+ `[merkle] Built whitelist tree with ${addresses.length} addresses (root: ${root.slice(0, 10)}...)`,
191
+ );
192
+
193
+ return {
194
+ tree,
195
+ info: {
196
+ root,
197
+ addressCount: addresses.length,
198
+ leaves: tree[0],
199
+ },
200
+ };
201
+ }
202
+
203
+ /**
204
+ * Generate a Merkle proof for a specific address.
205
+ *
206
+ * Returns the proof, leaf, root, and whether the address is whitelisted.
207
+ * The proof can be passed directly to `mintWhitelist()` on the contract.
208
+ */
209
+ export function generateProof(walletAddress: string): MerkleProofResult {
210
+ const { tree, info } = buildWhitelistTree();
211
+
212
+ let leaf: string;
213
+ try {
214
+ leaf = hashLeaf(walletAddress);
215
+ } catch {
216
+ return {
217
+ proof: [],
218
+ leaf: `0x${"0".repeat(64)}`,
219
+ root: info.root,
220
+ isWhitelisted: false,
221
+ };
222
+ }
223
+
224
+ const proof = getProof(tree, leaf);
225
+ const isWhitelisted =
226
+ proof.length > 0 || (info.addressCount === 1 && tree[0][0] === leaf);
227
+
228
+ if (isWhitelisted) {
229
+ const valid = verifyProof(leaf, proof, info.root);
230
+ if (!valid) {
231
+ logger.warn(
232
+ `[merkle] Proof verification failed for ${walletAddress} — tree may be corrupted`,
233
+ );
234
+ return { proof: [], leaf, root: info.root, isWhitelisted: false };
235
+ }
236
+ }
237
+
238
+ return { proof, leaf, root: info.root, isWhitelisted };
239
+ }
@@ -0,0 +1,72 @@
1
+ import type { RouteHelpers, RouteRequestMeta } from "./route-helpers";
2
+
3
+ export interface ModelsRouteContext
4
+ extends RouteRequestMeta,
5
+ Pick<RouteHelpers, "json"> {
6
+ url: URL;
7
+ providerCachePath: (provider: string) => string;
8
+ getOrFetchProvider: (provider: string, force: boolean) => Promise<unknown[]>;
9
+ getOrFetchAllProviders: (
10
+ force: boolean,
11
+ ) => Promise<Record<string, unknown[]>>;
12
+ resolveModelsCacheDir: () => string;
13
+ pathExists: (targetPath: string) => boolean;
14
+ readDir: (targetPath: string) => string[];
15
+ unlinkFile: (targetPath: string) => void;
16
+ joinPath: (left: string, right: string) => string;
17
+ }
18
+
19
+ export async function handleModelsRoutes(
20
+ ctx: ModelsRouteContext,
21
+ ): Promise<boolean> {
22
+ const {
23
+ res,
24
+ method,
25
+ pathname,
26
+ url,
27
+ json,
28
+ providerCachePath,
29
+ getOrFetchProvider,
30
+ getOrFetchAllProviders,
31
+ resolveModelsCacheDir,
32
+ pathExists,
33
+ readDir,
34
+ unlinkFile,
35
+ joinPath,
36
+ } = ctx;
37
+
38
+ if (method !== "GET" || pathname !== "/api/models") return false;
39
+
40
+ const force = url.searchParams.get("refresh") === "true";
41
+ const specificProvider = url.searchParams.get("provider");
42
+
43
+ if (specificProvider) {
44
+ if (force) {
45
+ try {
46
+ unlinkFile(providerCachePath(specificProvider));
47
+ } catch {
48
+ // Ignore cache-bust errors and continue with a fresh fetch.
49
+ }
50
+ }
51
+ const models = await getOrFetchProvider(specificProvider, force);
52
+ json(res, { provider: specificProvider, models });
53
+ return true;
54
+ }
55
+
56
+ if (force) {
57
+ try {
58
+ const dir = resolveModelsCacheDir();
59
+ if (pathExists(dir)) {
60
+ for (const file of readDir(dir)) {
61
+ if (file.endsWith(".json")) unlinkFile(joinPath(dir, file));
62
+ }
63
+ }
64
+ } catch {
65
+ // Ignore cache-bust errors and continue with a fresh fetch.
66
+ }
67
+ }
68
+
69
+ const all = await getOrFetchAllProviders(force);
70
+ json(res, { providers: all });
71
+ return true;
72
+ }
@@ -0,0 +1,169 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import type { RouteHelpers, RouteRequestMeta } from "./route-helpers";
6
+
7
+ function emptyMerkleRoot(): string {
8
+ return createHash("sha256").update("", "utf8").digest("hex");
9
+ }
10
+
11
+ type NfaPlugin = {
12
+ buildMerkleRoot: (leafHashes: string[]) => string;
13
+ parseLearnings: (markdown: string) => Array<{ hash: string }>;
14
+ sha256: (data: string) => string;
15
+ };
16
+
17
+ let nfaPlugin: NfaPlugin | null | undefined;
18
+
19
+ async function getNfaPlugin(): Promise<NfaPlugin | null> {
20
+ if (nfaPlugin !== undefined) return nfaPlugin;
21
+ try {
22
+ const pkgName = "@elizaos/plugin-bnb-identity";
23
+ const mod = await import(/* @vite-ignore */ pkgName);
24
+ nfaPlugin =
25
+ typeof mod?.buildMerkleRoot === "function" &&
26
+ typeof mod?.parseLearnings === "function" &&
27
+ typeof mod?.sha256 === "function"
28
+ ? {
29
+ buildMerkleRoot: mod.buildMerkleRoot,
30
+ parseLearnings: mod.parseLearnings,
31
+ sha256: mod.sha256,
32
+ }
33
+ : null;
34
+ } catch {
35
+ nfaPlugin = null;
36
+ }
37
+ return nfaPlugin;
38
+ }
39
+
40
+ export interface NfaRouteContext
41
+ extends RouteRequestMeta,
42
+ Pick<RouteHelpers, "json" | "error"> {}
43
+
44
+ interface NfaRecord {
45
+ tokenId: string;
46
+ contractAddress: string;
47
+ network: string;
48
+ ownerAddress: string;
49
+ mintTxHash: string;
50
+ merkleRoot: string;
51
+ mintedAt: string;
52
+ lastUpdatedAt: string;
53
+ }
54
+
55
+ interface IdentityRecord {
56
+ agentId: string;
57
+ network: string;
58
+ txHash: string;
59
+ ownerAddress: string;
60
+ agentURI: string;
61
+ registeredAt: string;
62
+ lastUpdatedAt: string;
63
+ }
64
+
65
+ async function readJsonFile<T>(filePath: string): Promise<T | null> {
66
+ try {
67
+ const raw = await readFile(filePath, "utf8");
68
+ return JSON.parse(raw) as T;
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+
74
+ export async function handleNfaRoutes(ctx: NfaRouteContext): Promise<boolean> {
75
+ const { res, method, pathname, json } = ctx;
76
+
77
+ if (method === "GET" && pathname === "/api/nfa/status") {
78
+ const miladyDir = join(homedir(), ".milady");
79
+ const [nfaRecord, identityRecord] = await Promise.all([
80
+ readJsonFile<NfaRecord>(join(miladyDir, "bap578-nfa.json")),
81
+ readJsonFile<IdentityRecord>(join(miladyDir, "bnb-identity.json")),
82
+ ]);
83
+
84
+ const bscscanBase =
85
+ (nfaRecord?.network ?? identityRecord?.network ?? "bsc-testnet") === "bsc"
86
+ ? "https://bscscan.com"
87
+ : "https://testnet.bscscan.com";
88
+
89
+ json(res, {
90
+ nfa: nfaRecord
91
+ ? {
92
+ tokenId: nfaRecord.tokenId,
93
+ contractAddress: nfaRecord.contractAddress,
94
+ network: nfaRecord.network,
95
+ ownerAddress: nfaRecord.ownerAddress,
96
+ merkleRoot: nfaRecord.merkleRoot,
97
+ mintTxHash: nfaRecord.mintTxHash,
98
+ mintedAt: nfaRecord.mintedAt,
99
+ lastUpdatedAt: nfaRecord.lastUpdatedAt,
100
+ bscscanUrl: `${bscscanBase}/tx/${nfaRecord.mintTxHash}`,
101
+ }
102
+ : null,
103
+ identity: identityRecord
104
+ ? {
105
+ agentId: identityRecord.agentId,
106
+ network: identityRecord.network,
107
+ ownerAddress: identityRecord.ownerAddress,
108
+ agentURI: identityRecord.agentURI,
109
+ registeredAt: identityRecord.registeredAt,
110
+ scanUrl: `https://${identityRecord.network === "bsc" ? "www" : "testnet"}.8004scan.io/agent/${identityRecord.agentId}`,
111
+ }
112
+ : null,
113
+ configured: !!(nfaRecord || identityRecord),
114
+ });
115
+ return true;
116
+ }
117
+
118
+ if (method === "GET" && pathname === "/api/nfa/learnings") {
119
+ const learningsPaths = [
120
+ join(homedir(), ".milady", "LEARNINGS.md"),
121
+ join(process.cwd(), "LEARNINGS.md"),
122
+ ];
123
+
124
+ let markdown: string | null = null;
125
+ let resolvedSource: string | null = null;
126
+ for (const path of learningsPaths) {
127
+ try {
128
+ markdown = await readFile(path, "utf8");
129
+ resolvedSource = path;
130
+ break;
131
+ } catch {}
132
+ }
133
+
134
+ if (!markdown) {
135
+ json(res, {
136
+ entries: [],
137
+ merkleRoot: emptyMerkleRoot(),
138
+ totalEntries: 0,
139
+ source: null,
140
+ });
141
+ return true;
142
+ }
143
+
144
+ const plugin = await getNfaPlugin();
145
+ if (!plugin) {
146
+ json(res, {
147
+ entries: [],
148
+ merkleRoot: emptyMerkleRoot(),
149
+ totalEntries: 0,
150
+ source: null,
151
+ });
152
+ return true;
153
+ }
154
+
155
+ const entries = plugin.parseLearnings(markdown);
156
+ const leafHashes = entries.map((entry) => entry.hash);
157
+ const merkleRoot = plugin.buildMerkleRoot(leafHashes);
158
+
159
+ json(res, {
160
+ entries,
161
+ merkleRoot,
162
+ totalEntries: entries.length,
163
+ source: resolvedSource,
164
+ });
165
+ return true;
166
+ }
167
+
168
+ return false;
169
+ }
@@ -0,0 +1,188 @@
1
+ /**
2
+ * NFT holder verification for whitelist eligibility.
3
+ *
4
+ * Checks on-chain Milady NFT ownership (ERC-721 balanceOf) to determine
5
+ * whitelist eligibility. Verified addresses are stored in the same
6
+ * whitelist.json used by twitter-verify.ts, so both paths feed the
7
+ * same Merkle tree for on-chain whitelist proofs.
8
+ *
9
+ * OG Milady Maker: 0x5Af0D9827E0c53E4799BB226655A1de152A425a5 (Ethereum mainnet)
10
+ *
11
+ * @see twitter-verify.ts — parallel verificatio path via Twitter
12
+ * @see drop-service.ts — mintWithWhitelist() consumer
13
+ */
14
+
15
+ import { logger } from "@elizaos/core";
16
+ import { ethers } from "ethers";
17
+ import { isAddressWhitelisted, markAddressVerified } from "./twitter-verify";
18
+
19
+ // ── Constants ────────────────────────────────────────────────────────────
20
+
21
+ /** OG Milady Maker contract on Ethereum mainnet. */
22
+ const MILADY_CONTRACT = "0x5Af0D9827E0c53E4799BB226655A1de152A425a5";
23
+
24
+ /** Minimal ERC-721 ABI — only what we need. */
25
+ const ERC721_BALANCE_ABI = [
26
+ "function balanceOf(address owner) view returns (uint256)",
27
+ ] as const;
28
+
29
+ /** Default public Ethereum RPC endpoints (fallback chain). */
30
+ const DEFAULT_RPC_ENDPOINTS = [
31
+ "https://cloudflare-eth.com",
32
+ "https://eth.llamarpc.com",
33
+ "https://rpc.ankr.com/eth",
34
+ ];
35
+
36
+ /** Timeout for RPC calls (ms). */
37
+ const RPC_TIMEOUT_MS = 15_000;
38
+
39
+ // ── Types ────────────────────────────────────────────────────────────────
40
+
41
+ export interface NftVerificationResult {
42
+ verified: boolean;
43
+ balance: number;
44
+ contractAddress: string;
45
+ error: string | null;
46
+ }
47
+
48
+ // ── Provider ─────────────────────────────────────────────────────────────
49
+
50
+ /**
51
+ * Get an Ethereum JSON-RPC provider.
52
+ * Prefers ETHEREUM_RPC_URL env var, falls back to public endpoints.
53
+ */
54
+ function getProvider(): ethers.JsonRpcProvider {
55
+ const customRpc = process.env.ETHEREUM_RPC_URL?.trim();
56
+ const rpcUrl = customRpc || DEFAULT_RPC_ENDPOINTS[0];
57
+
58
+ return new ethers.JsonRpcProvider(rpcUrl, 1, {
59
+ staticNetwork: true,
60
+ });
61
+ }
62
+
63
+ // ── Core Verification ────────────────────────────────────────────────────
64
+
65
+ /**
66
+ * Check if a wallet address holds at least one Milady NFT.
67
+ *
68
+ * Makes a single `balanceOf()` call to the Milady contract on Ethereum
69
+ * mainnet. This is a read-only view call — no gas, no signing required.
70
+ */
71
+ export async function verifyMiladyHolder(
72
+ walletAddress: string,
73
+ ): Promise<NftVerificationResult> {
74
+ const contractAddress =
75
+ process.env.MILADY_NFT_CONTRACT?.trim() || MILADY_CONTRACT;
76
+
77
+ // ── Input validation ───────────────────────────────────────────────
78
+ if (!walletAddress || typeof walletAddress !== "string") {
79
+ return {
80
+ verified: false,
81
+ balance: 0,
82
+ contractAddress,
83
+ error: "Wallet address is required.",
84
+ };
85
+ }
86
+
87
+ if (!ethers.isAddress(walletAddress)) {
88
+ return {
89
+ verified: false,
90
+ balance: 0,
91
+ contractAddress,
92
+ error: "Invalid Ethereum address format.",
93
+ };
94
+ }
95
+
96
+ // ── On-chain balance check ─────────────────────────────────────────
97
+ const provider = getProvider();
98
+
99
+ try {
100
+ const contract = new ethers.Contract(
101
+ contractAddress,
102
+ ERC721_BALANCE_ABI,
103
+ provider,
104
+ );
105
+
106
+ const balanceBN = (await Promise.race([
107
+ contract.balanceOf(walletAddress),
108
+ new Promise((_, reject) =>
109
+ setTimeout(
110
+ () => reject(new Error("RPC request timed out")),
111
+ RPC_TIMEOUT_MS,
112
+ ),
113
+ ),
114
+ ])) as bigint;
115
+
116
+ const balance = Number(balanceBN);
117
+
118
+ if (balance > 0) {
119
+ logger.info(
120
+ `[nft-verify] Address ${walletAddress} holds ${balance} Milady NFT(s) — verified ✓`,
121
+ );
122
+ return { verified: true, balance, contractAddress, error: null };
123
+ }
124
+
125
+ logger.info(
126
+ `[nft-verify] Address ${walletAddress} holds 0 Milady NFTs — not eligible`,
127
+ );
128
+ return {
129
+ verified: false,
130
+ balance: 0,
131
+ contractAddress,
132
+ error: "Wallet does not hold any Milady NFTs.",
133
+ };
134
+ } catch (err) {
135
+ const message = err instanceof Error ? err.message : "Unknown RPC error";
136
+ logger.warn(`[nft-verify] Balance check failed: ${message}`);
137
+ return {
138
+ verified: false,
139
+ balance: 0,
140
+ contractAddress,
141
+ error: `NFT verification failed: ${message}`,
142
+ };
143
+ } finally {
144
+ provider.destroy();
145
+ }
146
+ }
147
+
148
+ // ── Whitelist Integration ────────────────────────────────────────────────
149
+
150
+ /**
151
+ * Verify NFT ownership and add to whitelist if eligible.
152
+ *
153
+ * This is the main entry point called by the API route. It performs the
154
+ * on-chain check and, if successful, writes the address into whitelist.json
155
+ * alongside any Twitter-verified addresses.
156
+ */
157
+ export async function verifyAndWhitelistHolder(
158
+ walletAddress: string,
159
+ ): Promise<NftVerificationResult> {
160
+ // Skip the on-chain call if already whitelisted
161
+ if (isAddressWhitelisted(walletAddress)) {
162
+ logger.info(
163
+ `[nft-verify] Address ${walletAddress} already whitelisted — skipping RPC call`,
164
+ );
165
+ return {
166
+ verified: true,
167
+ balance: -1, // -1 indicates "already verified, balance not re-checked"
168
+ contractAddress:
169
+ process.env.MILADY_NFT_CONTRACT?.trim() || MILADY_CONTRACT,
170
+ error: null,
171
+ };
172
+ }
173
+
174
+ const result = await verifyMiladyHolder(walletAddress);
175
+
176
+ if (result.verified) {
177
+ markAddressVerified(
178
+ walletAddress,
179
+ `nft:milady:${result.contractAddress}`,
180
+ `milady-holder:${result.balance}`,
181
+ );
182
+ logger.info(
183
+ `[nft-verify] Address ${walletAddress} added to whitelist via NFT verification`,
184
+ );
185
+ }
186
+
187
+ return result;
188
+ }