auramaxx 1.0.0-alpha.4

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 (363) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +112 -0
  3. package/bin/aurawallet.js +121 -0
  4. package/docs/ADAPTERS.md +467 -0
  5. package/docs/API.md +2679 -0
  6. package/docs/APPS.md +198 -0
  7. package/docs/ARCHITECTURE.md +350 -0
  8. package/docs/AUTH.md +698 -0
  9. package/docs/BEST-PRACTICES.md +121 -0
  10. package/docs/CLI.md +61 -0
  11. package/docs/DEVELOPING-APPS.md +452 -0
  12. package/docs/EXTENSION.md +97 -0
  13. package/docs/JOBS.md +33 -0
  14. package/docs/MCP.md +76 -0
  15. package/docs/PROTOCOL.md +142 -0
  16. package/docs/SETUP.md +219 -0
  17. package/docs/WORKSPACE.md +672 -0
  18. package/docs/agent-auth.md +63 -0
  19. package/docs/aura-file.md +48 -0
  20. package/docs/credentials.md +53 -0
  21. package/docs/external/getting-started.md +65 -0
  22. package/docs/external/overview.md +45 -0
  23. package/docs/external/use-cases.md +48 -0
  24. package/docs/external/why-aura.md +35 -0
  25. package/docs/jobs/connect-agent.md +77 -0
  26. package/docs/jobs/migrate-from-dotenv.md +79 -0
  27. package/docs/jobs/recover-from-lockout.md +72 -0
  28. package/docs/jobs/secure-ci.md +63 -0
  29. package/docs/oauth2.md +42 -0
  30. package/docs/passkeys.md +60 -0
  31. package/docs/security.md +540 -0
  32. package/docs/specs/aura-open-protocol.md +61 -0
  33. package/docs/specs/aura-provider-plugin.md +24 -0
  34. package/docs/specs/aura-registry-model.md +31 -0
  35. package/docs/specs/fixtures/invalid-bad-key.aura +1 -0
  36. package/docs/specs/fixtures/invalid-bad-unicode-escape.aura +1 -0
  37. package/docs/specs/fixtures/invalid-duplicate-key.aura +2 -0
  38. package/docs/specs/fixtures/valid-basic.aura +4 -0
  39. package/docs/specs/fixtures/valid-provider-ref.aura +1 -0
  40. package/docs/specs/fixtures/valid-quoted-escapes.aura +2 -0
  41. package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
  42. package/docs/totp.md +40 -0
  43. package/docs/wallet/AI.md +508 -0
  44. package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
  45. package/docs/wallet/README.md +47 -0
  46. package/docs/wallet/STRATEGY.md +89 -0
  47. package/next.config.ts +21 -0
  48. package/package.json +151 -0
  49. package/postcss.config.mjs +8 -0
  50. package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
  51. package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
  52. package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
  53. package/prisma/migrations/migration_lock.toml +3 -0
  54. package/prisma/schema.prisma +447 -0
  55. package/public/logo-chevron.svg +31 -0
  56. package/public/logo-concentric.svg +31 -0
  57. package/public/logo-crosshatch.svg +39 -0
  58. package/public/logo-dashed.svg +39 -0
  59. package/public/logo-horizontal.svg +31 -0
  60. package/public/logo-m56.svg +64 -0
  61. package/public/logo.webp +0 -0
  62. package/scripts/add-app.js +245 -0
  63. package/scripts/init.sh +57 -0
  64. package/scripts/migrate-apikeys-to-credentials.ts +35 -0
  65. package/scripts/sandbox-agent-flow.sh +235 -0
  66. package/scripts/sandbox.sh +175 -0
  67. package/scripts/validate-job-docs.mjs +125 -0
  68. package/server/abi/SwapHelper.json +438 -0
  69. package/server/cli/approval.ts +447 -0
  70. package/server/cli/commands/app.ts +204 -0
  71. package/server/cli/commands/cron.ts +24 -0
  72. package/server/cli/commands/doctor.ts +1007 -0
  73. package/server/cli/commands/env.ts +456 -0
  74. package/server/cli/commands/init.ts +752 -0
  75. package/server/cli/commands/mcp.ts +125 -0
  76. package/server/cli/commands/restore.ts +314 -0
  77. package/server/cli/commands/shell-hook.ts +468 -0
  78. package/server/cli/commands/start.ts +62 -0
  79. package/server/cli/commands/status.ts +59 -0
  80. package/server/cli/commands/stop.ts +14 -0
  81. package/server/cli/commands/token.ts +180 -0
  82. package/server/cli/commands/unlock.ts +49 -0
  83. package/server/cli/commands/vault.ts +417 -0
  84. package/server/cli/index.ts +328 -0
  85. package/server/cli/lib/aura-parser.ts +64 -0
  86. package/server/cli/lib/credential-create.ts +74 -0
  87. package/server/cli/lib/credential-resolve.ts +254 -0
  88. package/server/cli/lib/dotenv-migrate.ts +116 -0
  89. package/server/cli/lib/dotenv-parser.ts +146 -0
  90. package/server/cli/lib/http.ts +91 -0
  91. package/server/cli/lib/init-steps.ts +76 -0
  92. package/server/cli/lib/local-agent-trust.ts +45 -0
  93. package/server/cli/lib/process.ts +136 -0
  94. package/server/cli/lib/prompt.ts +85 -0
  95. package/server/cli/lib/theme.ts +240 -0
  96. package/server/cli/socket.ts +570 -0
  97. package/server/cli/transport-client.ts +50 -0
  98. package/server/cron/index.ts +137 -0
  99. package/server/cron/job.ts +31 -0
  100. package/server/cron/jobs/balance-sync.ts +436 -0
  101. package/server/cron/jobs/incoming-scan.ts +506 -0
  102. package/server/cron/jobs/native-price.ts +70 -0
  103. package/server/cron/jobs/orphan-cleanup.ts +40 -0
  104. package/server/cron/jobs/strategy-runner.ts +175 -0
  105. package/server/cron/scheduler.ts +125 -0
  106. package/server/index.ts +406 -0
  107. package/server/lib/adapters/factory.ts +110 -0
  108. package/server/lib/adapters/index.ts +19 -0
  109. package/server/lib/adapters/router.ts +297 -0
  110. package/server/lib/adapters/telegram.ts +645 -0
  111. package/server/lib/adapters/types.ts +89 -0
  112. package/server/lib/adapters/webhook.ts +95 -0
  113. package/server/lib/address.ts +49 -0
  114. package/server/lib/agent-auth/contracts.ts +1194 -0
  115. package/server/lib/agent-profiles.ts +328 -0
  116. package/server/lib/ai.ts +285 -0
  117. package/server/lib/api-registry/contracts.ts +86 -0
  118. package/server/lib/api-registry/validation.ts +172 -0
  119. package/server/lib/apikey-migration.ts +189 -0
  120. package/server/lib/app-installer.ts +505 -0
  121. package/server/lib/app-tokens.ts +247 -0
  122. package/server/lib/auth.ts +314 -0
  123. package/server/lib/batch.ts +242 -0
  124. package/server/lib/cold.ts +874 -0
  125. package/server/lib/config.ts +381 -0
  126. package/server/lib/credential-access-audit.ts +85 -0
  127. package/server/lib/credential-access-policy.ts +110 -0
  128. package/server/lib/credential-health.ts +343 -0
  129. package/server/lib/credential-import.ts +487 -0
  130. package/server/lib/credential-scope.ts +87 -0
  131. package/server/lib/credential-shares.ts +190 -0
  132. package/server/lib/credential-transport.ts +342 -0
  133. package/server/lib/credential-vault.ts +77 -0
  134. package/server/lib/credentials.ts +333 -0
  135. package/server/lib/crypto.ts +8 -0
  136. package/server/lib/db.ts +15 -0
  137. package/server/lib/defaults.ts +366 -0
  138. package/server/lib/dex/index.ts +80 -0
  139. package/server/lib/dex/relay.ts +235 -0
  140. package/server/lib/dex/types.ts +59 -0
  141. package/server/lib/dex/uniswap.ts +370 -0
  142. package/server/lib/e2e-agent/artifacts.ts +36 -0
  143. package/server/lib/e2e-agent/contracts.ts +112 -0
  144. package/server/lib/e2e-agent/validation.ts +135 -0
  145. package/server/lib/encrypt.ts +128 -0
  146. package/server/lib/error.ts +20 -0
  147. package/server/lib/events.ts +205 -0
  148. package/server/lib/hot.ts +357 -0
  149. package/server/lib/key-fingerprint.ts +28 -0
  150. package/server/lib/logger.ts +331 -0
  151. package/server/lib/network.ts +137 -0
  152. package/server/lib/notifications.ts +219 -0
  153. package/server/lib/oauth2-refresh.ts +241 -0
  154. package/server/lib/oursecret.ts +54 -0
  155. package/server/lib/passkey-credential.ts +360 -0
  156. package/server/lib/passkey.ts +68 -0
  157. package/server/lib/permissions.ts +248 -0
  158. package/server/lib/pino.ts +24 -0
  159. package/server/lib/policy-preview.ts +138 -0
  160. package/server/lib/price.ts +338 -0
  161. package/server/lib/prices.ts +34 -0
  162. package/server/lib/project-scope.ts +239 -0
  163. package/server/lib/resolve-action.ts +427 -0
  164. package/server/lib/resolve.ts +36 -0
  165. package/server/lib/sessions.ts +632 -0
  166. package/server/lib/solana/connection.ts +26 -0
  167. package/server/lib/solana/jupiter.ts +128 -0
  168. package/server/lib/solana/transfer.ts +108 -0
  169. package/server/lib/solana/wallet.ts +136 -0
  170. package/server/lib/strategy/emits.ts +21 -0
  171. package/server/lib/strategy/engine.ts +1305 -0
  172. package/server/lib/strategy/executor.ts +115 -0
  173. package/server/lib/strategy/hook-context.ts +158 -0
  174. package/server/lib/strategy/hooks.ts +990 -0
  175. package/server/lib/strategy/index.ts +28 -0
  176. package/server/lib/strategy/installer.ts +305 -0
  177. package/server/lib/strategy/loader.ts +256 -0
  178. package/server/lib/strategy/message.ts +235 -0
  179. package/server/lib/strategy/repository.ts +218 -0
  180. package/server/lib/strategy/session-logger.ts +693 -0
  181. package/server/lib/strategy/sources.ts +288 -0
  182. package/server/lib/strategy/state.ts +189 -0
  183. package/server/lib/strategy/templates.ts +403 -0
  184. package/server/lib/strategy/tick.ts +404 -0
  185. package/server/lib/strategy/types.ts +230 -0
  186. package/server/lib/swap.ts +3 -0
  187. package/server/lib/temp.ts +86 -0
  188. package/server/lib/token-metadata.ts +86 -0
  189. package/server/lib/token-safety.ts +200 -0
  190. package/server/lib/token-search.ts +444 -0
  191. package/server/lib/totp.ts +194 -0
  192. package/server/lib/transactions.ts +123 -0
  193. package/server/lib/transport.ts +75 -0
  194. package/server/lib/txhistory/decoder.ts +262 -0
  195. package/server/lib/txhistory/enricher.ts +652 -0
  196. package/server/lib/txhistory/index.ts +391 -0
  197. package/server/lib/txhistory/signatures.ts +59 -0
  198. package/server/lib/verified-summary.ts +421 -0
  199. package/server/mcp/profile-policy.ts +30 -0
  200. package/server/mcp/server.ts +619 -0
  201. package/server/mcp/tools.ts +523 -0
  202. package/server/middleware/auth.ts +119 -0
  203. package/server/middleware/requestLogger.ts +84 -0
  204. package/server/routes/actions.ts +459 -0
  205. package/server/routes/adapters.ts +703 -0
  206. package/server/routes/addressbook.ts +113 -0
  207. package/server/routes/ai.ts +34 -0
  208. package/server/routes/apikeys.ts +295 -0
  209. package/server/routes/apps.ts +601 -0
  210. package/server/routes/auth.ts +457 -0
  211. package/server/routes/backup.ts +340 -0
  212. package/server/routes/batch.ts +270 -0
  213. package/server/routes/bookmarks.ts +162 -0
  214. package/server/routes/credential-shares.ts +198 -0
  215. package/server/routes/credential-vaults.ts +154 -0
  216. package/server/routes/credentials.ts +1290 -0
  217. package/server/routes/dashboard.ts +71 -0
  218. package/server/routes/defaults.ts +124 -0
  219. package/server/routes/fund.ts +229 -0
  220. package/server/routes/import.ts +352 -0
  221. package/server/routes/launch.ts +665 -0
  222. package/server/routes/lock.ts +54 -0
  223. package/server/routes/logs.ts +68 -0
  224. package/server/routes/nuke.ts +111 -0
  225. package/server/routes/passkey-credentials.ts +99 -0
  226. package/server/routes/passkey.ts +346 -0
  227. package/server/routes/portfolio.ts +217 -0
  228. package/server/routes/price.ts +63 -0
  229. package/server/routes/resolve.ts +31 -0
  230. package/server/routes/security.ts +45 -0
  231. package/server/routes/send-evm.ts +241 -0
  232. package/server/routes/send-solana.ts +281 -0
  233. package/server/routes/send.ts +178 -0
  234. package/server/routes/setup.ts +210 -0
  235. package/server/routes/strategy.ts +894 -0
  236. package/server/routes/swap-evm.ts +353 -0
  237. package/server/routes/swap-solana.ts +177 -0
  238. package/server/routes/swap.ts +356 -0
  239. package/server/routes/token.ts +247 -0
  240. package/server/routes/unlock.ts +403 -0
  241. package/server/routes/wallet-assets.ts +361 -0
  242. package/server/routes/wallet-transactions.ts +515 -0
  243. package/server/routes/wallet.ts +710 -0
  244. package/server/types.ts +146 -0
  245. package/skills/aurawallet/SKILL.md +739 -0
  246. package/skills/aurawallet-setup/SKILL.md +74 -0
  247. package/skills/security-review/SKILL.md +148 -0
  248. package/src/app/api/agent-requests/route.ts +30 -0
  249. package/src/app/api/apps/install/route.ts +126 -0
  250. package/src/app/api/apps/manifests/route.ts +16 -0
  251. package/src/app/api/apps/static/[...path]/route.ts +57 -0
  252. package/src/app/api/events/route.ts +92 -0
  253. package/src/app/api/page.tsx +212 -0
  254. package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
  255. package/src/app/api/workspace/[id]/apps/route.ts +81 -0
  256. package/src/app/api/workspace/[id]/export/route.ts +67 -0
  257. package/src/app/api/workspace/[id]/route.ts +168 -0
  258. package/src/app/api/workspace/auth.ts +34 -0
  259. package/src/app/api/workspace/config/route.ts +106 -0
  260. package/src/app/api/workspace/import/route.ts +127 -0
  261. package/src/app/api/workspace/route.ts +116 -0
  262. package/src/app/app/page.tsx +2122 -0
  263. package/src/app/apple-icon.png +0 -0
  264. package/src/app/docs/page.tsx +178 -0
  265. package/src/app/favicon.ico +0 -0
  266. package/src/app/globals.css +572 -0
  267. package/src/app/health/page.tsx +5 -0
  268. package/src/app/hello/page.tsx +15 -0
  269. package/src/app/icon.png +0 -0
  270. package/src/app/layout.tsx +34 -0
  271. package/src/app/page.tsx +986 -0
  272. package/src/app/providers.tsx +90 -0
  273. package/src/app/share/[token]/page.tsx +295 -0
  274. package/src/components/ChainSelector.tsx +144 -0
  275. package/src/components/HumanActionBar.tsx +695 -0
  276. package/src/components/NotificationDrawer.tsx +129 -0
  277. package/src/components/apps/AgentKeysApp.tsx +490 -0
  278. package/src/components/apps/App.tsx +153 -0
  279. package/src/components/apps/AppGrid.tsx +15 -0
  280. package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
  281. package/src/components/apps/DraggableApp.tsx +562 -0
  282. package/src/components/apps/IFrameApp.tsx +73 -0
  283. package/src/components/apps/LogsApp.tsx +360 -0
  284. package/src/components/apps/SendApp.tsx +394 -0
  285. package/src/components/apps/SetupWizardApp.tsx +1004 -0
  286. package/src/components/apps/SystemDefaultsApp.tsx +845 -0
  287. package/src/components/apps/ThirdPartyApp.tsx +428 -0
  288. package/src/components/apps/TokenApp.tsx +319 -0
  289. package/src/components/apps/TransactionsApp.tsx +438 -0
  290. package/src/components/apps/WalletDetailApp.tsx +1505 -0
  291. package/src/components/apps/index.ts +13 -0
  292. package/src/components/design-system/Button.tsx +53 -0
  293. package/src/components/design-system/ChainIndicator.tsx +65 -0
  294. package/src/components/design-system/ChainSelector.tsx +137 -0
  295. package/src/components/design-system/ConfirmationModal.tsx +106 -0
  296. package/src/components/design-system/ConfirmationPopover.tsx +81 -0
  297. package/src/components/design-system/Drawer.tsx +123 -0
  298. package/src/components/design-system/FilterDropdown.tsx +72 -0
  299. package/src/components/design-system/Modal.tsx +206 -0
  300. package/src/components/design-system/Popover.tsx +142 -0
  301. package/src/components/design-system/TextInput.tsx +85 -0
  302. package/src/components/design-system/Toggle.tsx +58 -0
  303. package/src/components/design-system/index.ts +11 -0
  304. package/src/components/docs/DocsThemeToggle.tsx +49 -0
  305. package/src/components/health/CredentialHealthDashboard.tsx +214 -0
  306. package/src/components/icons/ChainIcons.tsx +72 -0
  307. package/src/components/layout/AppStoreDrawer.tsx +369 -0
  308. package/src/components/layout/ContentArea.tsx +21 -0
  309. package/src/components/layout/TabBar.tsx +278 -0
  310. package/src/components/layout/WalletSidebar.tsx +1033 -0
  311. package/src/components/layout/index.ts +4 -0
  312. package/src/components/marketing/AuraWalletSpecOverlay.tsx +635 -0
  313. package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
  314. package/src/components/vault/ApiKeysConsole.tsx +1080 -0
  315. package/src/components/vault/AuditConsole.tsx +584 -0
  316. package/src/components/vault/CredentialDetail.tsx +455 -0
  317. package/src/components/vault/CredentialEmpty.tsx +55 -0
  318. package/src/components/vault/CredentialField.tsx +361 -0
  319. package/src/components/vault/CredentialForm.tsx +1212 -0
  320. package/src/components/vault/CredentialList.tsx +165 -0
  321. package/src/components/vault/CredentialRow.tsx +97 -0
  322. package/src/components/vault/CredentialShareModal.tsx +178 -0
  323. package/src/components/vault/CredentialVault.tsx +754 -0
  324. package/src/components/vault/CredentialWalletWidget.tsx +103 -0
  325. package/src/components/vault/ImportCredentialsModal.tsx +515 -0
  326. package/src/components/vault/LargeTypeModal.tsx +64 -0
  327. package/src/components/vault/PasswordGenerator.tsx +224 -0
  328. package/src/components/vault/TOTPDisplay.tsx +123 -0
  329. package/src/components/vault/VaultSidebar.tsx +413 -0
  330. package/src/components/vault/types.ts +54 -0
  331. package/src/context/AuthContext.tsx +337 -0
  332. package/src/context/PriceContext.tsx +113 -0
  333. package/src/context/ThemeContext.tsx +164 -0
  334. package/src/context/WebSocketContext.tsx +269 -0
  335. package/src/context/WorkspaceContext.tsx +668 -0
  336. package/src/hooks/index.ts +3 -0
  337. package/src/hooks/useAgentActions.ts +368 -0
  338. package/src/hooks/useBalance.ts +103 -0
  339. package/src/hooks/useBalances.ts +129 -0
  340. package/src/instrumentation.ts +12 -0
  341. package/src/lib/api.ts +449 -0
  342. package/src/lib/app-loader.ts +148 -0
  343. package/src/lib/app-registry.ts +178 -0
  344. package/src/lib/app-sdk.ts +157 -0
  345. package/src/lib/audit-console-adapter.ts +151 -0
  346. package/src/lib/auth-client.ts +75 -0
  347. package/src/lib/config.ts +74 -0
  348. package/src/lib/crypto.ts +112 -0
  349. package/src/lib/db.ts +21 -0
  350. package/src/lib/docs.ts +390 -0
  351. package/src/lib/events.ts +361 -0
  352. package/src/lib/pino.ts +24 -0
  353. package/src/lib/theme-handlers.ts +168 -0
  354. package/src/lib/theme.ts +351 -0
  355. package/src/lib/tokenData.ts +378 -0
  356. package/src/lib/vault-crypto.ts +129 -0
  357. package/src/lib/websocket-server.ts +302 -0
  358. package/src/lib/websocket-setup.ts +79 -0
  359. package/src/lib/wordlist.ts +2050 -0
  360. package/src/lib/workspace-handlers.ts +285 -0
  361. package/start.sh +80 -0
  362. package/tailwind.config.ts +99 -0
  363. package/tsconfig.json +42 -0
@@ -0,0 +1,239 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { createHash } from 'crypto';
4
+ import { execFileSync } from 'child_process';
5
+
6
+ export type ProjectScopeCode =
7
+ | 'PROJECT_SCOPE_MISSING_AURA'
8
+ | 'PROJECT_SCOPE_INVALID_AURA'
9
+ | 'PROJECT_SCOPE_DENIED'
10
+ | 'PROJECT_SCOPE_OVERRIDE_USED';
11
+
12
+ export interface ScopeCandidate {
13
+ id?: string;
14
+ name: string;
15
+ vaultName: string | null;
16
+ }
17
+
18
+ export interface ScopeDecision {
19
+ allowed: boolean;
20
+ code: ProjectScopeCode | null;
21
+ remediation: string;
22
+ normalizedIdentity: { vaultName: string | null; credentialName: string };
23
+ projectRoot: string | null;
24
+ auraFingerprint: string | null;
25
+ allowedCandidates: ScopeCandidate[];
26
+ overrideUsed?: boolean;
27
+ }
28
+
29
+ interface ParsedRef {
30
+ vaultName: string | null;
31
+ credentialName: string;
32
+ }
33
+
34
+ function normalize(str: string): string {
35
+ return str.trim().toLowerCase();
36
+ }
37
+
38
+ function parseReference(ref: string): ParsedRef {
39
+ if (ref.startsWith('@')) {
40
+ const parts = ref.slice(1).split('/');
41
+ if (parts.length < 3 || !parts[0] || !parts[1]) {
42
+ throw new Error(`Invalid vault reference: ${ref}`);
43
+ }
44
+ return { vaultName: parts[0], credentialName: parts[1] };
45
+ }
46
+ const parts = ref.split('/');
47
+ if (parts.length < 2 || !parts[0]) {
48
+ throw new Error(`Invalid reference: ${ref}`);
49
+ }
50
+ return { vaultName: null, credentialName: parts[0] };
51
+ }
52
+
53
+ function parseAuraAllowlist(auraPath: string): ParsedRef[] {
54
+ const content = fs.readFileSync(auraPath, 'utf-8');
55
+ const refs: ParsedRef[] = [];
56
+
57
+ for (const rawLine of content.split('\n')) {
58
+ const line = rawLine.trim();
59
+ if (!line || line.startsWith('#')) continue;
60
+ const idx = line.indexOf('=');
61
+ if (idx === -1) throw new Error(`Invalid line: ${line}`);
62
+ const ref = line.slice(idx + 1).trim();
63
+ refs.push(parseReference(ref));
64
+ }
65
+
66
+ return refs;
67
+ }
68
+
69
+ function findNearestAura(startDir: string): string | null {
70
+ let current = startDir;
71
+ while (true) {
72
+ const candidate = path.join(current, '.aura');
73
+ if (fs.existsSync(candidate)) return candidate;
74
+ const parent = path.dirname(current);
75
+ if (parent === current) return null;
76
+ current = parent;
77
+ }
78
+ }
79
+
80
+ function resolveGitRoot(startDir: string): string | null {
81
+ try {
82
+ const root = execFileSync('git', ['rev-parse', '--show-toplevel'], {
83
+ cwd: startDir,
84
+ encoding: 'utf-8',
85
+ stdio: ['ignore', 'pipe', 'ignore'],
86
+ }).trim();
87
+ return root || null;
88
+ } catch {
89
+ return null;
90
+ }
91
+ }
92
+
93
+ function resolveAuraPath(opts: { cwd: string; projectRootOverride?: string }): { projectRoot: string | null; auraPath: string | null } {
94
+ const explicit = opts.projectRootOverride || process.env.AURA_PROJECT_ROOT;
95
+ if (explicit) {
96
+ const root = path.resolve(explicit);
97
+ return { projectRoot: root, auraPath: path.join(root, '.aura') };
98
+ }
99
+
100
+ const gitRoot = resolveGitRoot(opts.cwd);
101
+ if (gitRoot) {
102
+ return { projectRoot: gitRoot, auraPath: path.join(gitRoot, '.aura') };
103
+ }
104
+
105
+ const nearestAura = findNearestAura(opts.cwd);
106
+ if (nearestAura) {
107
+ return { projectRoot: path.dirname(nearestAura), auraPath: nearestAura };
108
+ }
109
+
110
+ return { projectRoot: null, auraPath: null };
111
+ }
112
+
113
+ function fingerprint(content: string): string {
114
+ return createHash('sha256').update(content).digest('hex').slice(0, 16);
115
+ }
116
+
117
+ function isAllowed(candidate: ScopeCandidate, refs: ParsedRef[]): boolean {
118
+ const candName = normalize(candidate.name);
119
+ const candVault = candidate.vaultName ? normalize(candidate.vaultName) : null;
120
+ return refs.some((ref) => {
121
+ if (normalize(ref.credentialName) !== candName) return false;
122
+ if (!ref.vaultName) return true;
123
+ return candVault === normalize(ref.vaultName);
124
+ });
125
+ }
126
+
127
+ export function evaluateProjectScopeAccess(input: {
128
+ surface: 'cli_vault_get' | 'cli_env' | 'mcp_get_secret';
129
+ requested: { vaultName: string | null; credentialName: string };
130
+ candidates: ScopeCandidate[];
131
+ actor?: string;
132
+ cwd?: string;
133
+ projectRootOverride?: string;
134
+ }): ScopeDecision {
135
+ const normalizedIdentity = {
136
+ vaultName: input.requested.vaultName,
137
+ credentialName: input.requested.credentialName,
138
+ };
139
+ const bypass = process.env.AURA_PROJECT_SCOPE_BYPASS === '1';
140
+
141
+ if (bypass) {
142
+ return {
143
+ allowed: true,
144
+ code: 'PROJECT_SCOPE_OVERRIDE_USED',
145
+ remediation: 'Unset AURA_PROJECT_SCOPE_BYPASS to restore strict project scoping.',
146
+ normalizedIdentity,
147
+ projectRoot: null,
148
+ auraFingerprint: null,
149
+ allowedCandidates: input.candidates,
150
+ overrideUsed: true,
151
+ };
152
+ }
153
+
154
+ const cwd = input.cwd || process.cwd();
155
+ const { projectRoot, auraPath } = resolveAuraPath({ cwd, projectRootOverride: input.projectRootOverride });
156
+ if (!auraPath || !fs.existsSync(auraPath)) {
157
+ return {
158
+ allowed: false,
159
+ code: 'PROJECT_SCOPE_MISSING_AURA',
160
+ remediation: 'Add a .aura file in the project root (or set AURA_PROJECT_ROOT / --project-root).',
161
+ normalizedIdentity,
162
+ projectRoot,
163
+ auraFingerprint: null,
164
+ allowedCandidates: [],
165
+ };
166
+ }
167
+
168
+ let refs: ParsedRef[];
169
+ let auraFingerprint: string;
170
+ try {
171
+ const content = fs.readFileSync(auraPath, 'utf-8');
172
+ auraFingerprint = fingerprint(content);
173
+ refs = parseAuraAllowlist(auraPath);
174
+ } catch {
175
+ return {
176
+ allowed: false,
177
+ code: 'PROJECT_SCOPE_INVALID_AURA',
178
+ remediation: 'Fix .aura syntax (ENV=@vault/name/field or ENV=name/field) and retry.',
179
+ normalizedIdentity,
180
+ projectRoot,
181
+ auraFingerprint: null,
182
+ allowedCandidates: [],
183
+ };
184
+ }
185
+
186
+ const allowedCandidates = input.candidates.filter((candidate) => isAllowed(candidate, refs));
187
+ if (allowedCandidates.length === 0) {
188
+ return {
189
+ allowed: false,
190
+ code: 'PROJECT_SCOPE_DENIED',
191
+ remediation: `Add '${input.requested.credentialName}' to .aura (or use an allowed credential).`,
192
+ normalizedIdentity,
193
+ projectRoot,
194
+ auraFingerprint,
195
+ allowedCandidates: [],
196
+ };
197
+ }
198
+
199
+ if (!input.requested.vaultName && allowedCandidates.length > 1) {
200
+ return {
201
+ allowed: false,
202
+ code: 'PROJECT_SCOPE_DENIED',
203
+ remediation: `Credential '${input.requested.credentialName}' is mapped in multiple vaults. Re-run with explicit --vault.`,
204
+ normalizedIdentity,
205
+ projectRoot,
206
+ auraFingerprint,
207
+ allowedCandidates: [],
208
+ };
209
+ }
210
+
211
+ return {
212
+ allowed: true,
213
+ code: null,
214
+ remediation: '',
215
+ normalizedIdentity,
216
+ projectRoot,
217
+ auraFingerprint,
218
+ allowedCandidates,
219
+ };
220
+ }
221
+
222
+ export function emitProjectScopeEvent(input: {
223
+ actor?: string;
224
+ surface: 'cli_vault_get' | 'cli_env' | 'mcp_get_secret';
225
+ requestedCredential: { vaultName: string | null; credentialName: string };
226
+ decision: ScopeDecision;
227
+ }): void {
228
+ const event = {
229
+ actor: input.actor || 'unknown',
230
+ surface: input.surface,
231
+ projectRoot: input.decision.projectRoot,
232
+ auraFingerprint: input.decision.auraFingerprint,
233
+ requestedCredential: input.requestedCredential,
234
+ code: input.decision.code,
235
+ timestamp: new Date().toISOString(),
236
+ overrideUsed: Boolean(input.decision.overrideUsed),
237
+ };
238
+ console.warn(`[project-scope] ${JSON.stringify(event)}`);
239
+ }
@@ -0,0 +1,427 @@
1
+ /**
2
+ * Extracted resolve logic from `POST /actions/:id/resolve`.
3
+ *
4
+ * Called by both the HTTP route handler and the ApprovalRouter (direct in-process).
5
+ */
6
+
7
+ import { prisma } from './db';
8
+ import { events, emitWalletEvent } from './events';
9
+ import { handleAppMessage } from './strategy/engine';
10
+ import { createToken, getTokenHash, escrowToken, type AgentTokenPayload } from './auth';
11
+ import { isValidAgentPubkey, normalizeAgentPubkey } from './credential-transport';
12
+ import { isUnlocked, getColdWalletAddress } from './cold';
13
+ import { normalizeAddress } from './address';
14
+ import { getDefault, getDefaultSync, parseRateLimit } from './defaults';
15
+ import { logger } from './logger';
16
+ import { getErrorMessage } from './error';
17
+
18
+ export interface ResolveActionOptions {
19
+ walletAccess?: string[];
20
+ limits?: Record<string, number>;
21
+ }
22
+
23
+ export interface ResolveActionResult {
24
+ success: boolean;
25
+ statusCode: 200 | 400 | 401 | 404;
26
+ data: Record<string, unknown>;
27
+ }
28
+
29
+ /** Track auto-execute callback depth per app to prevent infinite loops */
30
+ const callbackCounts = new Map<string, number[]>();
31
+
32
+ function canFireCallback(appId: string): boolean {
33
+ const { max: MAX_CALLBACKS, windowMs: CALLBACK_WINDOW_MS } = parseRateLimit(getDefaultSync('rate.app_callback', '3,120000'));
34
+ const now = Date.now();
35
+ const timestamps = callbackCounts.get(appId) || [];
36
+ const recent = timestamps.filter(t => now - t < CALLBACK_WINDOW_MS);
37
+ if (recent.length >= MAX_CALLBACKS) return false;
38
+ recent.push(now);
39
+ callbackCounts.set(appId, recent);
40
+ return true;
41
+ }
42
+
43
+ export async function resolveAction(
44
+ actionId: string,
45
+ approved: boolean,
46
+ opts?: ResolveActionOptions,
47
+ ): Promise<ResolveActionResult> {
48
+ const id = actionId;
49
+ const walletAccess = opts?.walletAccess;
50
+ const overrideLimits = opts?.limits;
51
+
52
+ if (typeof approved !== 'boolean') {
53
+ return { success: false, statusCode: 400, data: { success: false, error: 'approved (boolean) is required' } };
54
+ }
55
+
56
+ const request = await prisma.humanAction.findUnique({ where: { id } });
57
+ if (!request || request.status !== 'pending') {
58
+ return { success: false, statusCode: 404, data: { success: false, error: 'Action not found or already resolved' } };
59
+ }
60
+
61
+ if (request.type === 'strategy:message') {
62
+ return { success: false, statusCode: 400, data: { success: false, error: 'Internal message jobs are not manually resolvable' } };
63
+ }
64
+
65
+ // Handle rejection for all types
66
+ if (!approved) {
67
+ await prisma.humanAction.update({
68
+ where: { id },
69
+ data: { status: 'rejected', resolvedAt: new Date() },
70
+ });
71
+
72
+ events.actionResolved({ id, type: request.type, approved: false, resolvedBy: 'dashboard' });
73
+ logger.actionResolved(id, request.type, false, 'dashboard');
74
+
75
+ // Notify app of rejection via app:emit
76
+ if (request.type === 'action') {
77
+ let meta: { agentId?: string } = {};
78
+ try { meta = JSON.parse(request.metadata || '{}'); } catch {}
79
+ emitWalletEvent('app:emit', {
80
+ strategyId: (meta.agentId || '').replace(/^app:/, ''),
81
+ channel: 'action:resolved',
82
+ data: { requestId: id, approved: false },
83
+ });
84
+ }
85
+
86
+ return { success: true, statusCode: 200, data: { success: true, approved: false } };
87
+ }
88
+
89
+ // === APPROVAL ===
90
+
91
+ // Strategy approvals
92
+ if (request.type === 'strategy:approve') {
93
+ await prisma.humanAction.update({
94
+ where: { id },
95
+ data: { status: 'approved', resolvedAt: new Date() },
96
+ });
97
+
98
+ events.actionResolved({ id, type: request.type, approved: true, resolvedBy: 'dashboard' });
99
+ return { success: true, statusCode: 200, data: { success: true, approved: true } };
100
+ }
101
+
102
+ // Auth / agent_access / action approvals — generate token
103
+ if (request.type === 'auth' || request.type === 'agent_access' || request.type === 'action') {
104
+ if (!isUnlocked()) {
105
+ return { success: false, statusCode: 401, data: { success: false, error: 'Wallet is locked. Unlock first.' } };
106
+ }
107
+
108
+ let metadata: {
109
+ agentId?: string;
110
+ limit?: number;
111
+ permissions?: string[];
112
+ ttl?: number;
113
+ secretHash?: string;
114
+ limits?: { fund?: number; send?: number; swap?: number };
115
+ walletAccess?: string[];
116
+ credentialAccess?: AgentTokenPayload['credentialAccess'];
117
+ pubkey?: string;
118
+ strategyId?: string;
119
+ summary?: string;
120
+ action?: { endpoint?: string; method?: string; body?: Record<string, unknown> };
121
+ } = {};
122
+ if (request.metadata) {
123
+ try { metadata = JSON.parse(request.metadata); } catch { /* ignore */ }
124
+ }
125
+
126
+ const agentId = metadata.agentId || `agent-${id.slice(0, 8)}`;
127
+ const defaultFundLimit = await getDefault<number>('limits.fund', 0.1);
128
+ const defaultSendLimit = await getDefault<number>('limits.send', 0.1);
129
+ const defaultSwapLimit = await getDefault<number>('limits.swap', 0.1);
130
+ const defaultPermissions = await getDefault<string[]>('permissions.default', ['wallet:create:hot', 'send:hot', 'swap', 'fund', 'action:create']);
131
+ const defaultTtl = await getDefault<number>('ttl.agent', 3600);
132
+ const limit = overrideLimits?.fund ?? metadata.limit ?? defaultFundLimit;
133
+ const permissions = metadata.permissions || defaultPermissions;
134
+ const ttl = metadata.ttl || defaultTtl;
135
+ let normalizedPubkey = metadata.pubkey;
136
+ if (normalizedPubkey) {
137
+ if (!isValidAgentPubkey(normalizedPubkey)) {
138
+ return { success: false, statusCode: 400, data: { success: false, error: 'Stored pubkey is invalid for token issuance' } };
139
+ }
140
+ normalizedPubkey = normalizeAgentPubkey(normalizedPubkey);
141
+ }
142
+ if (!normalizedPubkey) {
143
+ return { success: false, statusCode: 400, data: { success: false, error: 'pubkey is required when approving token issuance' } };
144
+ }
145
+
146
+ const finalWalletAccess = walletAccess
147
+ ? walletAccess.map((addr: string) => normalizeAddress(addr))
148
+ : metadata.walletAccess;
149
+
150
+ // Build limits: per-token overrides > request metadata > system defaults
151
+ const baseLimits = { fund: limit, send: defaultSendLimit, swap: defaultSwapLimit };
152
+ const finalLimits = overrideLimits
153
+ ? { ...baseLimits, ...overrideLimits }
154
+ : metadata.limits
155
+ ? { ...baseLimits, ...metadata.limits }
156
+ : baseLimits;
157
+
158
+ const token = await createToken(agentId, limit, permissions, ttl, {
159
+ limits: finalLimits,
160
+ walletAccess: finalWalletAccess,
161
+ credentialAccess: metadata.credentialAccess,
162
+ agentPubkey: normalizedPubkey,
163
+ });
164
+
165
+ // Escrow the raw token in memory — never store it in the DB
166
+ escrowToken(id, token);
167
+
168
+ // Update request status with tokenHash (not raw token) for audit/display
169
+ await prisma.humanAction.update({
170
+ where: { id },
171
+ data: {
172
+ status: 'approved',
173
+ resolvedAt: new Date(),
174
+ metadata: JSON.stringify({
175
+ ...metadata,
176
+ tokenHash: getTokenHash(token),
177
+ limits: finalLimits,
178
+ walletAccess: finalWalletAccess,
179
+ pubkey: normalizedPubkey,
180
+ credentialAccess: metadata.credentialAccess,
181
+ }),
182
+ },
183
+ });
184
+
185
+ // Log the approval
186
+ await prisma.log.create({
187
+ data: {
188
+ walletAddress: getColdWalletAddress() || 'system',
189
+ title: 'Agent Access Approved',
190
+ description: `Generated token for ${agentId} with ${limit} ETH limit`,
191
+ },
192
+ });
193
+
194
+ events.tokenCreated({
195
+ tokenHash: getTokenHash(token),
196
+ agentId,
197
+ limit,
198
+ permissions,
199
+ expiresAt: Date.now() + ttl * 1000,
200
+ });
201
+ events.actionResolved({ id, type: request.type, approved: true, resolvedBy: 'dashboard' });
202
+ logger.actionResolved(id, request.type, true, 'dashboard');
203
+
204
+ // Notify app of approval via app:emit (always, even with auto-execute)
205
+ if (request.type === 'action') {
206
+ emitWalletEvent('app:emit', {
207
+ strategyId: (metadata.agentId || '').replace(/^app:/, ''),
208
+ channel: 'action:resolved',
209
+ data: { requestId: id, approved: true },
210
+ });
211
+ }
212
+
213
+ // Auto-execute pre-computed action if present in metadata
214
+ const hasAutoExecute = request.type === 'action' && metadata.action;
215
+ if (hasAutoExecute) {
216
+ const action = metadata.action as { endpoint?: string; method?: string; body?: Record<string, unknown> };
217
+ if (action.endpoint && action.method) {
218
+ try {
219
+ const actionUrl = `http://127.0.0.1:4242${action.endpoint}`;
220
+ const actionRes = await fetch(actionUrl, {
221
+ method: action.method,
222
+ headers: {
223
+ 'Content-Type': 'application/json',
224
+ 'Authorization': `Bearer ${token}`,
225
+ },
226
+ body: action.method === 'POST' && action.body
227
+ ? JSON.stringify(action.body)
228
+ : undefined,
229
+ });
230
+ const actionResult = await actionRes.text();
231
+ let parsedResult: unknown;
232
+ try { parsedResult = JSON.parse(actionResult); } catch { parsedResult = actionResult; }
233
+
234
+ emitWalletEvent('app:emit', {
235
+ strategyId: (metadata.agentId || '').replace(/^app:/, ''),
236
+ channel: 'action:executed',
237
+ data: {
238
+ requestId: id,
239
+ approved: true,
240
+ action: { endpoint: action.endpoint, method: action.method },
241
+ status: actionRes.ok ? 'success' : 'error',
242
+ statusCode: actionRes.status,
243
+ result: parsedResult,
244
+ },
245
+ });
246
+
247
+ logger.actionResolved(id, 'action:auto-execute', actionRes.ok, 'system');
248
+
249
+ // Feed result back to app AI for a contextual follow-up message
250
+ const appId = (metadata.agentId || '').replace(/^app:/, '');
251
+ if (appId) {
252
+ const summary = metadata.summary || 'action';
253
+ const systemMsg = actionRes.ok
254
+ ? `[SYSTEM] Action "${summary}" approved and executed successfully.\nResult: ${JSON.stringify(parsedResult).slice(0, 500)}\n\nIf there are more steps needed to complete the user's original request, use your tools NOW (wallet_api or request_human_action) to continue. Do not just describe what you will do — do it.`
255
+ : `[SYSTEM] Action "${summary}" approved but failed (${actionRes.status}).\nError: ${JSON.stringify(parsedResult).slice(0, 500)}\n\nInvestigate the error using wallet_api and retry with request_human_action if you can fix the issue. Do NOT just explain the error — try to fix it.`;
256
+
257
+ if (canFireCallback(appId)) {
258
+ handleAppMessage(appId, systemMsg).then(({ reply }) => {
259
+ if (reply) {
260
+ emitWalletEvent('app:emit', {
261
+ strategyId: appId,
262
+ channel: 'agent:message',
263
+ data: { message: reply },
264
+ });
265
+ }
266
+ }).catch((err) => { logger.actionResolved(id, 'action:callback-error', false, getErrorMessage(err)); });
267
+ } else {
268
+ logger.actionResolved(id, 'action:callback-limit', true, `app:${appId}`);
269
+ }
270
+ }
271
+ } catch (err) {
272
+ const errMsg = getErrorMessage(err);
273
+ emitWalletEvent('app:emit', {
274
+ strategyId: (metadata.agentId || '').replace(/^app:/, ''),
275
+ channel: 'action:executed',
276
+ data: {
277
+ requestId: id,
278
+ approved: true,
279
+ action: { endpoint: action.endpoint, method: action.method },
280
+ status: 'error',
281
+ error: errMsg,
282
+ },
283
+ });
284
+
285
+ // Feed error back to app AI
286
+ const appId = (metadata.agentId || '').replace(/^app:/, '');
287
+ if (appId) {
288
+ const summary = metadata.summary || 'action';
289
+ const systemMsg = `[SYSTEM] Action "${summary}" approved but execution failed.\nError: ${errMsg}\n\nInvestigate the error using wallet_api and retry with request_human_action if you can fix the issue. Do NOT just explain the error — try to fix it.`;
290
+
291
+ if (canFireCallback(appId)) {
292
+ handleAppMessage(appId, systemMsg).then(({ reply }) => {
293
+ if (reply) {
294
+ emitWalletEvent('app:emit', {
295
+ strategyId: appId,
296
+ channel: 'agent:message',
297
+ data: { message: reply },
298
+ });
299
+ }
300
+ }).catch((err) => { logger.actionResolved(id, 'action:callback-error', false, getErrorMessage(err)); });
301
+ } else {
302
+ logger.actionResolved(id, 'action:callback-limit', false, `app:${appId}`);
303
+ }
304
+ }
305
+ }
306
+ }
307
+ }
308
+
309
+ return {
310
+ success: true,
311
+ statusCode: 200,
312
+ data: {
313
+ success: true,
314
+ token,
315
+ agentId,
316
+ limit,
317
+ limits: finalLimits,
318
+ permissions,
319
+ walletAccess: finalWalletAccess,
320
+ expiresIn: ttl,
321
+ },
322
+ };
323
+ }
324
+
325
+ // Permission update approvals — generate token with updated permissions
326
+ if (request.type === 'permission_update') {
327
+ if (!isUnlocked()) {
328
+ return { success: false, statusCode: 401, data: { success: false, error: 'Wallet is locked. Unlock first.' } };
329
+ }
330
+
331
+ let metadata: {
332
+ agentId?: string;
333
+ tokenHash?: string;
334
+ requestedPermissions?: string[];
335
+ requestedWalletAccess?: string[];
336
+ requestedLimits?: { fund?: number; send?: number; swap?: number };
337
+ requestedPubkey?: string;
338
+ secretHash?: string;
339
+ } = {};
340
+ if (request.metadata) {
341
+ try { metadata = JSON.parse(request.metadata); } catch { /* ignore */ }
342
+ }
343
+
344
+ const agentId = metadata.agentId || `agent-${id.slice(0, 8)}`;
345
+ const newPermissions = overrideLimits?.permissions ?? metadata.requestedPermissions ?? [];
346
+ const newWalletAccess = walletAccess
347
+ ? walletAccess.map((addr: string) => normalizeAddress(addr))
348
+ : metadata.requestedWalletAccess;
349
+ const newLimits = overrideLimits || metadata.requestedLimits;
350
+ let normalizedPubkey = metadata.requestedPubkey;
351
+ if (normalizedPubkey) {
352
+ if (!isValidAgentPubkey(normalizedPubkey)) {
353
+ return { success: false, statusCode: 400, data: { success: false, error: 'requestedPubkey is invalid' } };
354
+ }
355
+ normalizedPubkey = normalizeAgentPubkey(normalizedPubkey);
356
+ }
357
+ if (!normalizedPubkey) {
358
+ return { success: false, statusCode: 400, data: { success: false, error: 'requestedPubkey is required for token issuance' } };
359
+ }
360
+
361
+ const ttl = await getDefault<number>('ttl.agent', 3600);
362
+ const token = await createToken(agentId, newLimits?.fund ?? 0, newPermissions, ttl, {
363
+ limits: newLimits,
364
+ walletAccess: newWalletAccess,
365
+ agentPubkey: normalizedPubkey,
366
+ });
367
+
368
+ // Escrow the raw token in memory — never store it in the DB
369
+ escrowToken(id, token);
370
+
371
+ await prisma.humanAction.update({
372
+ where: { id },
373
+ data: {
374
+ status: 'approved',
375
+ resolvedAt: new Date(),
376
+ metadata: JSON.stringify({
377
+ ...metadata,
378
+ tokenHash: getTokenHash(token),
379
+ approvedPermissions: newPermissions,
380
+ approvedWalletAccess: newWalletAccess,
381
+ approvedLimits: newLimits,
382
+ requestedPubkey: normalizedPubkey,
383
+ }),
384
+ },
385
+ });
386
+
387
+ await prisma.log.create({
388
+ data: {
389
+ walletAddress: getColdWalletAddress() || 'system',
390
+ title: 'Permission Update Approved',
391
+ description: `Updated permissions for ${agentId}`,
392
+ },
393
+ });
394
+
395
+ events.tokenCreated({
396
+ tokenHash: getTokenHash(token),
397
+ agentId,
398
+ limit: newLimits?.fund ?? 0,
399
+ permissions: newPermissions,
400
+ expiresAt: Date.now() + ttl * 1000,
401
+ });
402
+ events.actionResolved({ id, type: request.type, approved: true, resolvedBy: 'dashboard' });
403
+
404
+ return {
405
+ success: true,
406
+ statusCode: 200,
407
+ data: {
408
+ success: true,
409
+ token,
410
+ agentId,
411
+ permissions: newPermissions,
412
+ walletAccess: newWalletAccess,
413
+ limits: newLimits,
414
+ },
415
+ };
416
+ }
417
+
418
+ // For other types (fund_transfer, etc.), update DB and emit event
419
+ await prisma.humanAction.update({
420
+ where: { id },
421
+ data: { status: approved ? 'approved' : 'rejected', resolvedAt: new Date() },
422
+ });
423
+
424
+ events.actionResolved({ id, type: request.type, approved, resolvedBy: 'dashboard' });
425
+
426
+ return { success: true, statusCode: 200, data: { success: true, approved } };
427
+ }
@@ -0,0 +1,36 @@
1
+ import { ethers } from 'ethers';
2
+ import { getRpcUrl } from './config';
3
+
4
+ /**
5
+ * Resolve an ENS name (.eth) to an Ethereum address.
6
+ * Uses ethers built-in provider.resolveName() which handles ENS natively.
7
+ * ENS resolution always uses Ethereum mainnet (ENS is deployed on L1).
8
+ */
9
+ export async function resolveName(name: string): Promise<{ address: string; name: string }> {
10
+ if (!name || typeof name !== 'string') {
11
+ throw new Error('Name is required');
12
+ }
13
+
14
+ // Only support .eth names for now (.sol is out of scope)
15
+ if (!name.endsWith('.eth')) {
16
+ throw new Error(`Unsupported name format: ${name}. Only .eth names are supported.`);
17
+ }
18
+
19
+ // ENS lives on Ethereum mainnet — always resolve against L1
20
+ const rpcUrl = await getRpcUrl('ethereum');
21
+ const provider = new ethers.JsonRpcProvider(rpcUrl);
22
+
23
+ const address = await provider.resolveName(name);
24
+ if (!address) {
25
+ throw new Error(`Could not resolve: ${name}`);
26
+ }
27
+
28
+ return { address, name };
29
+ }
30
+
31
+ /**
32
+ * Check if a string looks like an ENS name (contains a dot).
33
+ */
34
+ export function looksLikeName(value: string): boolean {
35
+ return value.includes('.') && !value.startsWith('0x') && !value.match(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/);
36
+ }