@elizaos/app-core 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 (399) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +90 -0
  3. package/src/App.tsx +472 -0
  4. package/src/actions/character.test.ts +139 -0
  5. package/src/actions/character.ts +152 -0
  6. package/src/actions/chat-helpers.ts +100 -0
  7. package/src/actions/cloud.ts +59 -0
  8. package/src/actions/index.ts +12 -0
  9. package/src/actions/lifecycle.ts +175 -0
  10. package/src/actions/onboarding.ts +46 -0
  11. package/src/actions/triggers.ts +190 -0
  12. package/src/ambient.d.ts +16 -0
  13. package/src/api/client.ts +5516 -0
  14. package/src/api/index.ts +1 -0
  15. package/src/autonomy/index.ts +477 -0
  16. package/src/bridge/capacitor-bridge.ts +295 -0
  17. package/src/bridge/electrobun-rpc.ts +58 -0
  18. package/src/bridge/electrobun-runtime.ts +28 -0
  19. package/src/bridge/index.ts +5 -0
  20. package/src/bridge/native-plugins.ts +134 -0
  21. package/src/bridge/plugin-bridge.ts +352 -0
  22. package/src/bridge/storage-bridge.ts +162 -0
  23. package/src/chat/index.ts +250 -0
  24. package/src/coding/index.ts +43 -0
  25. package/src/components/AdvancedPageView.tsx +362 -0
  26. package/src/components/AgentActivityBox.tsx +49 -0
  27. package/src/components/ApiKeyConfig.tsx +224 -0
  28. package/src/components/AppsPageView.tsx +52 -0
  29. package/src/components/AppsView.tsx +293 -0
  30. package/src/components/AvatarLoader.tsx +86 -0
  31. package/src/components/AvatarSelector.tsx +223 -0
  32. package/src/components/BscTradePanel.tsx +549 -0
  33. package/src/components/BugReportModal.tsx +499 -0
  34. package/src/components/CharacterView.tsx +1645 -0
  35. package/src/components/ChatAvatar.test.ts +96 -0
  36. package/src/components/ChatAvatar.tsx +147 -0
  37. package/src/components/ChatComposer.tsx +330 -0
  38. package/src/components/ChatMessage.tsx +448 -0
  39. package/src/components/ChatModalView.test.tsx +118 -0
  40. package/src/components/ChatModalView.tsx +125 -0
  41. package/src/components/ChatView.tsx +992 -0
  42. package/src/components/CloudSourceControls.tsx +80 -0
  43. package/src/components/CodingAgentSettingsSection.tsx +536 -0
  44. package/src/components/CommandPalette.tsx +284 -0
  45. package/src/components/CompanionSceneHost.tsx +497 -0
  46. package/src/components/CompanionShell.tsx +31 -0
  47. package/src/components/CompanionView.tsx +109 -0
  48. package/src/components/ConfigPageView.tsx +758 -0
  49. package/src/components/ConfigSaveFooter.tsx +41 -0
  50. package/src/components/ConfirmModal.tsx +379 -0
  51. package/src/components/ConnectionFailedBanner.tsx +91 -0
  52. package/src/components/ConnectorsPageView.tsx +13 -0
  53. package/src/components/ConversationsSidebar.tsx +279 -0
  54. package/src/components/CustomActionEditor.tsx +1125 -0
  55. package/src/components/CustomActionsPanel.tsx +288 -0
  56. package/src/components/CustomActionsView.tsx +322 -0
  57. package/src/components/DatabasePageView.tsx +55 -0
  58. package/src/components/DatabaseView.tsx +814 -0
  59. package/src/components/ElizaCloudDashboard.tsx +1696 -0
  60. package/src/components/EmotePicker.tsx +529 -0
  61. package/src/components/ErrorBoundary.tsx +76 -0
  62. package/src/components/FineTuningView.tsx +1077 -0
  63. package/src/components/GameView.tsx +552 -0
  64. package/src/components/GameViewOverlay.tsx +133 -0
  65. package/src/components/GlobalEmoteOverlay.tsx +155 -0
  66. package/src/components/Header.test.tsx +413 -0
  67. package/src/components/Header.tsx +403 -0
  68. package/src/components/HeartbeatsView.tsx +1003 -0
  69. package/src/components/InventoryView.tsx +385 -0
  70. package/src/components/KnowledgeView.tsx +1128 -0
  71. package/src/components/LanguageDropdown.tsx +188 -0
  72. package/src/components/LifoMonitorPanel.tsx +196 -0
  73. package/src/components/LifoSandboxView.tsx +499 -0
  74. package/src/components/LoadingScreen.tsx +77 -0
  75. package/src/components/LogsPageView.tsx +17 -0
  76. package/src/components/LogsView.tsx +239 -0
  77. package/src/components/MediaGalleryView.tsx +433 -0
  78. package/src/components/MediaSettingsSection.tsx +893 -0
  79. package/src/components/MessageContent.tsx +815 -0
  80. package/src/components/OnboardingWizard.test.tsx +107 -0
  81. package/src/components/OnboardingWizard.tsx +189 -0
  82. package/src/components/PairingView.tsx +110 -0
  83. package/src/components/PermissionsSection.tsx +1186 -0
  84. package/src/components/PluginsPageView.tsx +9 -0
  85. package/src/components/PluginsView.tsx +3157 -0
  86. package/src/components/ProviderSwitcher.tsx +908 -0
  87. package/src/components/RestartBanner.tsx +76 -0
  88. package/src/components/RuntimeView.tsx +460 -0
  89. package/src/components/SaveCommandModal.tsx +211 -0
  90. package/src/components/SecretsView.tsx +569 -0
  91. package/src/components/SettingsView.tsx +825 -0
  92. package/src/components/ShellOverlays.tsx +41 -0
  93. package/src/components/ShortcutsOverlay.tsx +155 -0
  94. package/src/components/SkillsView.tsx +1435 -0
  95. package/src/components/StartupFailureView.tsx +63 -0
  96. package/src/components/StreamView.tsx +483 -0
  97. package/src/components/StripeEmbeddedCheckout.tsx +155 -0
  98. package/src/components/SubscriptionStatus.tsx +640 -0
  99. package/src/components/SystemWarningBanner.tsx +71 -0
  100. package/src/components/ThemeToggle.tsx +100 -0
  101. package/src/components/TrajectoriesView.tsx +526 -0
  102. package/src/components/TrajectoryDetailView.tsx +426 -0
  103. package/src/components/TriggersView.tsx +1 -0
  104. package/src/components/VectorBrowserView.tsx +1633 -0
  105. package/src/components/VoiceConfigView.tsx +675 -0
  106. package/src/components/VrmStage.test.ts +219 -0
  107. package/src/components/VrmStage.tsx +432 -0
  108. package/src/components/WhatsAppQrOverlay.tsx +230 -0
  109. package/src/components/__tests__/chainConfig.test.ts +220 -0
  110. package/src/components/apps/AppDetailPane.tsx +242 -0
  111. package/src/components/apps/AppsCatalogGrid.tsx +137 -0
  112. package/src/components/apps/extensions/HyperscapeAppDetailPanel.tsx +577 -0
  113. package/src/components/apps/extensions/registry.ts +16 -0
  114. package/src/components/apps/extensions/types.ts +9 -0
  115. package/src/components/apps/helpers.ts +44 -0
  116. package/src/components/avatar/VrmAnimationLoader.test.ts +164 -0
  117. package/src/components/avatar/VrmAnimationLoader.ts +151 -0
  118. package/src/components/avatar/VrmBlinkController.ts +118 -0
  119. package/src/components/avatar/VrmCameraManager.ts +407 -0
  120. package/src/components/avatar/VrmEngine.ts +2678 -0
  121. package/src/components/avatar/VrmFootShadow.ts +96 -0
  122. package/src/components/avatar/VrmViewer.tsx +421 -0
  123. package/src/components/avatar/__tests__/VrmCameraManager.test.ts +168 -0
  124. package/src/components/avatar/__tests__/VrmEngine.test.ts +1574 -0
  125. package/src/components/avatar/mixamoVRMRigMap.ts +62 -0
  126. package/src/components/avatar/retargetMixamoFbxToVrm.ts +144 -0
  127. package/src/components/avatar/retargetMixamoGltfToVrm.ts +119 -0
  128. package/src/components/chainConfig.ts +380 -0
  129. package/src/components/companion/CompanionHeader.tsx +47 -0
  130. package/src/components/companion/CompanionSceneHost.tsx +1 -0
  131. package/src/components/companion/VrmStage.tsx +2 -0
  132. package/src/components/companion/__tests__/walletUtils.test.ts +742 -0
  133. package/src/components/companion/walletUtils.ts +290 -0
  134. package/src/components/companion-shell-styles.test.ts +142 -0
  135. package/src/components/companion-shell-styles.ts +270 -0
  136. package/src/components/confirm-delete-control.tsx +69 -0
  137. package/src/components/conversations/ConversationListItem.tsx +185 -0
  138. package/src/components/conversations/conversation-utils.ts +151 -0
  139. package/src/components/format.ts +131 -0
  140. package/src/components/index.ts +94 -0
  141. package/src/components/inventory/CopyableAddress.tsx +41 -0
  142. package/src/components/inventory/InventoryToolbar.tsx +142 -0
  143. package/src/components/inventory/NftGrid.tsx +99 -0
  144. package/src/components/inventory/TokenLogo.tsx +71 -0
  145. package/src/components/inventory/TokensTable.tsx +216 -0
  146. package/src/components/inventory/constants.ts +170 -0
  147. package/src/components/inventory/index.ts +29 -0
  148. package/src/components/inventory/media-url.test.ts +38 -0
  149. package/src/components/inventory/media-url.ts +36 -0
  150. package/src/components/inventory/useInventoryData.ts +460 -0
  151. package/src/components/knowledge-upload-image.ts +215 -0
  152. package/src/components/labels.ts +46 -0
  153. package/src/components/onboarding/ActivateStep.tsx +30 -0
  154. package/src/components/onboarding/ConnectionStep.tsx +1530 -0
  155. package/src/components/onboarding/IdentityStep.tsx +147 -0
  156. package/src/components/onboarding/OnboardingPanel.tsx +39 -0
  157. package/src/components/onboarding/OnboardingStepNav.tsx +31 -0
  158. package/src/components/onboarding/PermissionsStep.tsx +20 -0
  159. package/src/components/onboarding/RpcStep.tsx +402 -0
  160. package/src/components/onboarding/WakeUpStep.tsx +184 -0
  161. package/src/components/permissions/PermissionIcon.tsx +25 -0
  162. package/src/components/permissions/StreamingPermissions.tsx +413 -0
  163. package/src/components/plugins/showcase-data.ts +481 -0
  164. package/src/components/shared/ShellHeaderControls.tsx +193 -0
  165. package/src/components/shared-companion-scene-context.ts +15 -0
  166. package/src/components/skeletons.tsx +88 -0
  167. package/src/components/stream/ActivityFeed.tsx +113 -0
  168. package/src/components/stream/AvatarPip.tsx +10 -0
  169. package/src/components/stream/ChatContent.tsx +126 -0
  170. package/src/components/stream/ChatTicker.tsx +55 -0
  171. package/src/components/stream/IdleContent.tsx +73 -0
  172. package/src/components/stream/StatusBar.tsx +469 -0
  173. package/src/components/stream/StreamSettings.tsx +506 -0
  174. package/src/components/stream/StreamTerminal.tsx +94 -0
  175. package/src/components/stream/StreamVoiceConfig.tsx +160 -0
  176. package/src/components/stream/helpers.ts +134 -0
  177. package/src/components/stream/overlays/OverlayLayer.tsx +75 -0
  178. package/src/components/stream/overlays/built-in/ActionTickerWidget.tsx +64 -0
  179. package/src/components/stream/overlays/built-in/AlertPopupWidget.tsx +87 -0
  180. package/src/components/stream/overlays/built-in/BrandingWidget.tsx +51 -0
  181. package/src/components/stream/overlays/built-in/CustomHtmlWidget.tsx +105 -0
  182. package/src/components/stream/overlays/built-in/PeonGlassWidget.tsx +265 -0
  183. package/src/components/stream/overlays/built-in/PeonHudWidget.tsx +247 -0
  184. package/src/components/stream/overlays/built-in/PeonSakuraWidget.tsx +278 -0
  185. package/src/components/stream/overlays/built-in/ThoughtBubbleWidget.tsx +77 -0
  186. package/src/components/stream/overlays/built-in/ViewerCountWidget.tsx +46 -0
  187. package/src/components/stream/overlays/built-in/index.ts +13 -0
  188. package/src/components/stream/overlays/registry.ts +22 -0
  189. package/src/components/stream/overlays/types.ts +90 -0
  190. package/src/components/stream/overlays/useOverlayLayout.ts +218 -0
  191. package/src/components/trajectory-format.ts +50 -0
  192. package/src/components/ui-badges.tsx +109 -0
  193. package/src/components/ui-switch.tsx +57 -0
  194. package/src/components/vector-browser-three.ts +27 -0
  195. package/src/config/config-catalog.ts +1092 -0
  196. package/src/config/config-field.tsx +1901 -0
  197. package/src/config/config-renderer.tsx +730 -0
  198. package/src/config/index.ts +11 -0
  199. package/src/config/ui-renderer.tsx +1751 -0
  200. package/src/config/ui-spec.ts +256 -0
  201. package/src/events/index.ts +89 -0
  202. package/src/hooks/index.ts +13 -0
  203. package/src/hooks/useBugReport.tsx +43 -0
  204. package/src/hooks/useCanvasWindow.ts +372 -0
  205. package/src/hooks/useChatAvatarVoice.ts +111 -0
  206. package/src/hooks/useContextMenu.ts +127 -0
  207. package/src/hooks/useKeyboardShortcuts.ts +86 -0
  208. package/src/hooks/useLifoSync.ts +143 -0
  209. package/src/hooks/useMemoryMonitor.ts +334 -0
  210. package/src/hooks/useRenderGuard.ts +43 -0
  211. package/src/hooks/useRetakeCapture.ts +67 -0
  212. package/src/hooks/useStreamPopoutNavigation.ts +27 -0
  213. package/src/hooks/useTimeout.ts +37 -0
  214. package/src/hooks/useVoiceChat.ts +1441 -0
  215. package/src/hooks/useWhatsAppPairing.ts +123 -0
  216. package/src/i18n/index.ts +76 -0
  217. package/src/i18n/locales/en.json +1194 -0
  218. package/src/i18n/locales/es.json +1194 -0
  219. package/src/i18n/locales/ko.json +1194 -0
  220. package/src/i18n/locales/pt.json +1194 -0
  221. package/src/i18n/locales/zh-CN.json +1194 -0
  222. package/src/i18n/messages.ts +21 -0
  223. package/src/index.ts +6 -0
  224. package/src/navigation/index.ts +282 -0
  225. package/src/navigation.test.ts +189 -0
  226. package/src/onboarding-config.test.ts +104 -0
  227. package/src/onboarding-config.ts +114 -0
  228. package/src/platform/browser-launch.test.ts +94 -0
  229. package/src/platform/browser-launch.ts +149 -0
  230. package/src/platform/index.ts +58 -0
  231. package/src/platform/init.ts +236 -0
  232. package/src/platform/lifo.ts +215 -0
  233. package/src/providers/index.ts +99 -0
  234. package/src/state/AppContext.tsx +5846 -0
  235. package/src/state/index.ts +6 -0
  236. package/src/state/internal.ts +86 -0
  237. package/src/state/onboarding-resume.test.ts +135 -0
  238. package/src/state/onboarding-resume.ts +263 -0
  239. package/src/state/parsers.test.ts +124 -0
  240. package/src/state/parsers.ts +308 -0
  241. package/src/state/persistence.ts +321 -0
  242. package/src/state/shell-routing.ts +32 -0
  243. package/src/state/types.ts +701 -0
  244. package/src/state/ui-preferences.ts +3 -0
  245. package/src/state/useApp.ts +23 -0
  246. package/src/state/vrm.ts +76 -0
  247. package/src/stories/AppMockProvider.tsx +32 -0
  248. package/src/stories/ChatEmptyState.stories.tsx +27 -0
  249. package/src/stories/ChatMessage.stories.tsx +115 -0
  250. package/src/stories/CompanionHeader.stories.tsx +74 -0
  251. package/src/stories/CompanionView.stories.tsx +33 -0
  252. package/src/stories/ConversationListItem.stories.tsx +102 -0
  253. package/src/stories/TypingIndicator.stories.tsx +28 -0
  254. package/src/styles/anime.css +6324 -0
  255. package/src/styles/base.css +196 -0
  256. package/src/styles/onboarding-game.css +738 -0
  257. package/src/styles/styles.css +2087 -0
  258. package/src/styles/xterm.css +241 -0
  259. package/src/types/index.ts +715 -0
  260. package/src/types/react-test-renderer.d.ts +45 -0
  261. package/src/utils/asset-url.ts +110 -0
  262. package/src/utils/assistant-text.ts +172 -0
  263. package/src/utils/clipboard.ts +41 -0
  264. package/src/utils/desktop-dialogs.ts +80 -0
  265. package/src/utils/index.ts +6 -0
  266. package/src/utils/number-parsing.ts +125 -0
  267. package/src/utils/openExternalUrl.ts +20 -0
  268. package/src/utils/spoken-text.ts +65 -0
  269. package/src/utils/streaming-text.ts +120 -0
  270. package/src/voice/index.ts +1 -0
  271. package/src/voice/types.ts +197 -0
  272. package/src/wallet-rpc.ts +176 -0
  273. package/test/app/AppContext.pty-sessions.test.tsx +143 -0
  274. package/test/app/MessageContent.test.tsx +326 -0
  275. package/test/app/PermissionsOnboarding.test.tsx +356 -0
  276. package/test/app/PermissionsSection.test.tsx +573 -0
  277. package/test/app/advanced-trajectory-fine-tuning.e2e.test.ts +393 -0
  278. package/test/app/agent-activity-box.test.tsx +132 -0
  279. package/test/app/agent-transfer-lock.test.ts +274 -0
  280. package/test/app/api-client-electron-fallback.test.ts +139 -0
  281. package/test/app/api-client-timeout.test.ts +75 -0
  282. package/test/app/api-client-ws.test.ts +98 -0
  283. package/test/app/api-client.ws-max-reconnect.test.ts +139 -0
  284. package/test/app/api-client.ws-reconnect.test.ts +157 -0
  285. package/test/app/app-context-autonomy-events.test.ts +478 -0
  286. package/test/app/apps-page-view.test.ts +114 -0
  287. package/test/app/apps-view.test.ts +769 -0
  288. package/test/app/autonomous-workflows.e2e.test.ts +765 -0
  289. package/test/app/autonomy-events.test.ts +150 -0
  290. package/test/app/avatar-selector.test.tsx +52 -0
  291. package/test/app/bsc-trade-panel.test.tsx +134 -0
  292. package/test/app/bug-report-modal.test.tsx +353 -0
  293. package/test/app/character-customization.e2e.test.ts +1199 -0
  294. package/test/app/chat-advanced-features.e2e.test.ts +706 -0
  295. package/test/app/chat-composer.test.tsx +181 -0
  296. package/test/app/chat-language-header.test.ts +64 -0
  297. package/test/app/chat-message.test.tsx +222 -0
  298. package/test/app/chat-modal-view.test.tsx +191 -0
  299. package/test/app/chat-routine-filter.test.ts +96 -0
  300. package/test/app/chat-send-lock.test.ts +1465 -0
  301. package/test/app/chat-stream-api-client.test.tsx +390 -0
  302. package/test/app/chat-view-game-modal.test.tsx +661 -0
  303. package/test/app/chat-view.test.tsx +877 -0
  304. package/test/app/cloud-api.e2e.test.ts +258 -0
  305. package/test/app/cloud-login-flow.e2e.test.ts +494 -0
  306. package/test/app/cloud-login-lock.test.ts +411 -0
  307. package/test/app/command-palette.test.tsx +184 -0
  308. package/test/app/command-registry.test.ts +75 -0
  309. package/test/app/companion-greeting-wave.test.tsx +425 -0
  310. package/test/app/companion-stale-conversation.test.tsx +447 -0
  311. package/test/app/companion-view.test.tsx +686 -0
  312. package/test/app/confirm-delete-control.test.ts +79 -0
  313. package/test/app/confirm-modal.test.tsx +219 -0
  314. package/test/app/connectors-ui.e2e.test.ts +508 -0
  315. package/test/app/conversations-sidebar-game-modal.test.tsx +260 -0
  316. package/test/app/conversations-sidebar.test.tsx +160 -0
  317. package/test/app/custom-actions-smoke.test.ts +387 -0
  318. package/test/app/custom-avatar-api-client.test.ts +207 -0
  319. package/test/app/desktop-utils.test.ts +145 -0
  320. package/test/app/electrobun-rpc-bridge.test.ts +83 -0
  321. package/test/app/events.test.ts +88 -0
  322. package/test/app/export-import-flows.e2e.test.ts +700 -0
  323. package/test/app/fine-tuning-view.test.ts +471 -0
  324. package/test/app/game-view-auth-session.test.tsx +186 -0
  325. package/test/app/game-view.test.ts +444 -0
  326. package/test/app/global-emote-overlay.test.tsx +106 -0
  327. package/test/app/header-status.test.tsx +149 -0
  328. package/test/app/i18n.test.ts +152 -0
  329. package/test/app/inventory-bsc-view.test.ts +940 -0
  330. package/test/app/knowledge-ui.e2e.test.ts +762 -0
  331. package/test/app/knowledge-upload-helpers.test.ts +124 -0
  332. package/test/app/lifecycle-lock.test.ts +267 -0
  333. package/test/app/lifo-popout-utils.test.ts +208 -0
  334. package/test/app/lifo-safe-endpoint.test.ts +34 -0
  335. package/test/app/loading-screen.test.tsx +45 -0
  336. package/test/app/memory-monitor.test.ts +332 -0
  337. package/test/app/navigation.test.tsx +22 -0
  338. package/test/app/onboarding-finish-lock.test.ts +663 -0
  339. package/test/app/onboarding-language.test.tsx +160 -0
  340. package/test/app/onboarding-steps.test.tsx +375 -0
  341. package/test/app/open-external-url.test.ts +65 -0
  342. package/test/app/pages-navigation-smoke.e2e.test.ts +633 -0
  343. package/test/app/pairing-lock.test.ts +260 -0
  344. package/test/app/pairing-view.test.tsx +74 -0
  345. package/test/app/permissions-section.test.ts +432 -0
  346. package/test/app/plugin-bridge.test.ts +109 -0
  347. package/test/app/plugins-ui.e2e.test.ts +605 -0
  348. package/test/app/plugins-view-game-modal.test.tsx +650 -0
  349. package/test/app/plugins-view-toggle-restart.test.ts +129 -0
  350. package/test/app/provider-dropdown-default.test.tsx +302 -0
  351. package/test/app/restart-banner.test.tsx +197 -0
  352. package/test/app/retake-capture.test.ts +84 -0
  353. package/test/app/sandbox-api-client.test.ts +108 -0
  354. package/test/app/save-command-modal.test.tsx +109 -0
  355. package/test/app/secrets-view.test.tsx +92 -0
  356. package/test/app/settings-control-styles.test.tsx +142 -0
  357. package/test/app/settings-reset.e2e.test.ts +726 -0
  358. package/test/app/settings-sections.e2e.test.ts +614 -0
  359. package/test/app/shared-format.test.ts +44 -0
  360. package/test/app/shared-switch.test.ts +69 -0
  361. package/test/app/shell-mode-switching.e2e.test.ts +829 -0
  362. package/test/app/shell-mode-tab-memory.test.tsx +58 -0
  363. package/test/app/shell-overlays.test.tsx +50 -0
  364. package/test/app/shortcuts-overlay.test.tsx +111 -0
  365. package/test/app/sse-interruption.test.ts +122 -0
  366. package/test/app/startup-asset-missing.e2e.test.ts +126 -0
  367. package/test/app/startup-backend-missing.e2e.test.ts +118 -0
  368. package/test/app/startup-chat.e2e.test.ts +305 -0
  369. package/test/app/startup-conversation-restore.test.tsx +344 -0
  370. package/test/app/startup-failure-view.test.tsx +103 -0
  371. package/test/app/startup-onboarding.e2e.test.ts +618 -0
  372. package/test/app/startup-timeout.test.tsx +80 -0
  373. package/test/app/startup-token-401.e2e.test.ts +103 -0
  374. package/test/app/stream-helpers.test.ts +46 -0
  375. package/test/app/stream-popout-navigation.test.tsx +41 -0
  376. package/test/app/stream-status-bar.test.tsx +89 -0
  377. package/test/app/theme-toggle.test.tsx +33 -0
  378. package/test/app/training-api-client.test.ts +128 -0
  379. package/test/app/trajectories-view.test.tsx +220 -0
  380. package/test/app/triggers-api-client.test.ts +77 -0
  381. package/test/app/triggers-navigation.test.ts +113 -0
  382. package/test/app/triggers-view.e2e.test.ts +674 -0
  383. package/test/app/update-channel-lock.test.ts +259 -0
  384. package/test/app/vector-browser.async-cleanup.test.tsx +367 -0
  385. package/test/app/vector-browser.e2e.test.ts +653 -0
  386. package/test/app/vrm-stage.test.tsx +351 -0
  387. package/test/app/vrm-viewer.test.tsx +298 -0
  388. package/test/app/wallet-api-save-lock.test.ts +298 -0
  389. package/test/app/wallet-hooks.test.ts +405 -0
  390. package/test/app/wallet-ui-flows.e2e.test.ts +556 -0
  391. package/test/avatar/asset-url.test.ts +90 -0
  392. package/test/avatar/avatar-selector.test.ts +173 -0
  393. package/test/avatar/mixamo-vrm-rig-map.test.ts +111 -0
  394. package/test/avatar/voice-chat-streaming-text.test.ts +96 -0
  395. package/test/avatar/voice-chat.test.ts +391 -0
  396. package/test/ui/command-palette-commands.test.ts +57 -0
  397. package/test/ui/ui-renderer.test.ts +39 -0
  398. package/tsconfig.build.json +19 -0
  399. package/tsconfig.json +20 -0
@@ -0,0 +1,62 @@
1
+ import type { VRMHumanBoneName } from "@pixiv/three-vrm";
2
+
3
+ // Mixamo -> VRM humanoid bone mapping.
4
+ // Keys are normalized to `mixamorig<JointName>`.
5
+ export const mixamoVRMRigMap: Record<string, VRMHumanBoneName> = {
6
+ mixamorigHips: "hips",
7
+ mixamorigSpine: "spine",
8
+ mixamorigSpine1: "chest",
9
+ mixamorigSpine2: "upperChest",
10
+ mixamorigNeck: "neck",
11
+ mixamorigHead: "head",
12
+
13
+ mixamorigLeftShoulder: "leftShoulder",
14
+ mixamorigLeftArm: "leftUpperArm",
15
+ mixamorigLeftForeArm: "leftLowerArm",
16
+ mixamorigLeftHand: "leftHand",
17
+ mixamorigLeftHandThumb1: "leftThumbMetacarpal",
18
+ mixamorigLeftHandThumb2: "leftThumbProximal",
19
+ mixamorigLeftHandThumb3: "leftThumbDistal",
20
+ mixamorigLeftHandIndex1: "leftIndexProximal",
21
+ mixamorigLeftHandIndex2: "leftIndexIntermediate",
22
+ mixamorigLeftHandIndex3: "leftIndexDistal",
23
+ mixamorigLeftHandMiddle1: "leftMiddleProximal",
24
+ mixamorigLeftHandMiddle2: "leftMiddleIntermediate",
25
+ mixamorigLeftHandMiddle3: "leftMiddleDistal",
26
+ mixamorigLeftHandRing1: "leftRingProximal",
27
+ mixamorigLeftHandRing2: "leftRingIntermediate",
28
+ mixamorigLeftHandRing3: "leftRingDistal",
29
+ mixamorigLeftHandPinky1: "leftLittleProximal",
30
+ mixamorigLeftHandPinky2: "leftLittleIntermediate",
31
+ mixamorigLeftHandPinky3: "leftLittleDistal",
32
+
33
+ mixamorigRightShoulder: "rightShoulder",
34
+ mixamorigRightArm: "rightUpperArm",
35
+ mixamorigRightForeArm: "rightLowerArm",
36
+ mixamorigRightHand: "rightHand",
37
+ mixamorigRightHandPinky1: "rightLittleProximal",
38
+ mixamorigRightHandPinky2: "rightLittleIntermediate",
39
+ mixamorigRightHandPinky3: "rightLittleDistal",
40
+ mixamorigRightHandRing1: "rightRingProximal",
41
+ mixamorigRightHandRing2: "rightRingIntermediate",
42
+ mixamorigRightHandRing3: "rightRingDistal",
43
+ mixamorigRightHandMiddle1: "rightMiddleProximal",
44
+ mixamorigRightHandMiddle2: "rightMiddleIntermediate",
45
+ mixamorigRightHandMiddle3: "rightMiddleDistal",
46
+ mixamorigRightHandIndex1: "rightIndexProximal",
47
+ mixamorigRightHandIndex2: "rightIndexIntermediate",
48
+ mixamorigRightHandIndex3: "rightIndexDistal",
49
+ mixamorigRightHandThumb1: "rightThumbMetacarpal",
50
+ mixamorigRightHandThumb2: "rightThumbProximal",
51
+ mixamorigRightHandThumb3: "rightThumbDistal",
52
+
53
+ mixamorigLeftUpLeg: "leftUpperLeg",
54
+ mixamorigLeftLeg: "leftLowerLeg",
55
+ mixamorigLeftFoot: "leftFoot",
56
+ mixamorigLeftToeBase: "leftToes",
57
+
58
+ mixamorigRightUpLeg: "rightUpperLeg",
59
+ mixamorigRightLeg: "rightLowerLeg",
60
+ mixamorigRightFoot: "rightFoot",
61
+ mixamorigRightToeBase: "rightToes",
62
+ };
@@ -0,0 +1,144 @@
1
+ import type { VRM, VRMHumanBoneName } from "@pixiv/three-vrm";
2
+ import * as THREE from "three";
3
+ import { mixamoVRMRigMap } from "./mixamoVRMRigMap";
4
+
5
+ function normalizeMixamoRigName(name: string): string {
6
+ const pipe = name.lastIndexOf("|");
7
+ const base = pipe >= 0 ? name.slice(pipe + 1) : name;
8
+ const colon = base.indexOf(":");
9
+ if (colon >= 0) {
10
+ const ns = base.slice(0, colon);
11
+ const rest = base.slice(colon + 1);
12
+ if (ns === "mixamorig") return `mixamorig${rest}`;
13
+ return rest;
14
+ }
15
+ return base;
16
+ }
17
+
18
+ function isVrm0(vrm: VRM): boolean {
19
+ const mv = String(vrm.meta?.metaVersion ?? "");
20
+ return mv.startsWith("0");
21
+ }
22
+
23
+ function findNode(
24
+ scene: THREE.Object3D,
25
+ rawName: string,
26
+ normalizedName: string,
27
+ ): THREE.Object3D | null {
28
+ return (
29
+ scene.getObjectByName(rawName) ??
30
+ scene.getObjectByName(normalizedName) ??
31
+ scene.getObjectByName(
32
+ rawName.includes(":") ? (rawName.split(":")[1] ?? rawName) : rawName,
33
+ ) ??
34
+ null
35
+ );
36
+ }
37
+
38
+ /**
39
+ * Retarget a Mixamo FBX animation clip onto a VRM.
40
+ * Quaternion tracks are converted into VRM space and position tracks are
41
+ * scaled by hips height ratio.
42
+ */
43
+ export function retargetMixamoFbxToVrm(
44
+ sourceScene: THREE.Group,
45
+ sourceClip: THREE.AnimationClip,
46
+ vrm: VRM,
47
+ ): THREE.AnimationClip {
48
+ sourceScene.updateMatrixWorld(true);
49
+ vrm.scene.updateMatrixWorld(true);
50
+
51
+ const tracks: THREE.KeyframeTrack[] = [];
52
+ const restRotationInverse = new THREE.Quaternion();
53
+ const parentRestWorldRotation = new THREE.Quaternion();
54
+ const q = new THREE.Quaternion();
55
+
56
+ const motionHipsNode = findNode(
57
+ sourceScene,
58
+ "mixamorigHips",
59
+ "mixamorigHips",
60
+ );
61
+ const motionHipsHeight = Math.abs(motionHipsNode?.position.y ?? 0);
62
+ const vrmHipsHeight = Math.abs(
63
+ vrm.humanoid?.normalizedRestPose.hips?.position?.[1] ?? 0,
64
+ );
65
+ const hipsPositionScale =
66
+ motionHipsHeight > 1e-6 && vrmHipsHeight > 1e-6
67
+ ? vrmHipsHeight / motionHipsHeight
68
+ : 1;
69
+
70
+ for (const track of sourceClip.tracks) {
71
+ const parts = track.name.split(".");
72
+ const rawRigName = parts[0];
73
+ const propertyName = parts[1];
74
+ if (!rawRigName || !propertyName) continue;
75
+ const normalizedRigName = normalizeMixamoRigName(rawRigName);
76
+ const vrmBoneName = mixamoVRMRigMap[normalizedRigName];
77
+ if (!vrmBoneName) continue;
78
+
79
+ const vrmNode = vrm.humanoid?.getNormalizedBoneNode(
80
+ vrmBoneName as VRMHumanBoneName,
81
+ );
82
+ if (!vrmNode) continue;
83
+
84
+ const mixamoRigNode = findNode(sourceScene, rawRigName, normalizedRigName);
85
+ if (!mixamoRigNode || !mixamoRigNode.parent) continue;
86
+
87
+ mixamoRigNode.getWorldQuaternion(restRotationInverse).invert();
88
+ mixamoRigNode.parent.getWorldQuaternion(parentRestWorldRotation);
89
+
90
+ if (
91
+ propertyName === "quaternion" &&
92
+ track instanceof THREE.QuaternionKeyframeTrack
93
+ ) {
94
+ const values = track.values.slice();
95
+ for (let i = 0; i < values.length; i += 4) {
96
+ q.fromArray(values, i);
97
+ q.premultiply(parentRestWorldRotation).multiply(restRotationInverse);
98
+ q.toArray(values, i);
99
+ }
100
+
101
+ tracks.push(
102
+ new THREE.QuaternionKeyframeTrack(
103
+ `${vrmNode.name}.quaternion`,
104
+ track.times,
105
+ values.map((v, i) => (isVrm0(vrm) && i % 2 === 0 ? -v : v)),
106
+ ),
107
+ );
108
+ continue;
109
+ }
110
+
111
+ // Keep position-track behavior aligned with Girlfie runtime.
112
+ if (
113
+ propertyName === "position" &&
114
+ track instanceof THREE.VectorKeyframeTrack
115
+ ) {
116
+ const values = track.values.map(
117
+ (v, i) => (isVrm0(vrm) && i % 3 !== 1 ? -v : v) * hipsPositionScale,
118
+ );
119
+ tracks.push(
120
+ new THREE.VectorKeyframeTrack(
121
+ `${vrmNode.name}.position`,
122
+ track.times,
123
+ values,
124
+ ),
125
+ );
126
+ }
127
+ }
128
+
129
+ const hasHipsTrack = tracks.some((track) =>
130
+ track.name.startsWith(
131
+ `${vrm.humanoid?.getNormalizedBoneNode("hips" as VRMHumanBoneName)?.name ?? "__missing__"}.`,
132
+ ),
133
+ );
134
+ if (!hasHipsTrack) {
135
+ throw new Error(
136
+ `Idle FBX retargeting failed: no hips bone track found (mapped ${tracks.length} tracks). ` +
137
+ "Expected Mixamo bone names like mixamorigHips/mixamorigSpine...",
138
+ );
139
+ }
140
+
141
+ const clip = new THREE.AnimationClip("idle", sourceClip.duration, tracks);
142
+ clip.optimize();
143
+ return clip;
144
+ }
@@ -0,0 +1,119 @@
1
+ import type { VRM, VRMHumanBoneName } from "@pixiv/three-vrm";
2
+ import * as THREE from "three";
3
+ import { mixamoVRMRigMap } from "./mixamoVRMRigMap";
4
+
5
+ function normalizeMixamoRigName(name: string): string {
6
+ const pipe = name.lastIndexOf("|");
7
+ const base = pipe >= 0 ? name.slice(pipe + 1) : name;
8
+ const colon = base.indexOf(":");
9
+ if (colon >= 0) {
10
+ const ns = base.slice(0, colon);
11
+ const rest = base.slice(colon + 1);
12
+ if (ns === "mixamorig") return `mixamorig${rest}`;
13
+ return rest;
14
+ }
15
+ return base;
16
+ }
17
+
18
+ function isVrm0(vrm: VRM): boolean {
19
+ const mv = String(vrm.meta?.metaVersion ?? "");
20
+ return mv.startsWith("0");
21
+ }
22
+
23
+ function findNode(
24
+ scene: THREE.Object3D,
25
+ rawName: string,
26
+ normalizedName: string,
27
+ ): THREE.Object3D | null {
28
+ return (
29
+ scene.getObjectByName(rawName) ??
30
+ scene.getObjectByName(normalizedName) ??
31
+ scene.getObjectByName(
32
+ rawName.includes(":") ? (rawName.split(":")[1] ?? rawName) : rawName,
33
+ ) ??
34
+ null
35
+ );
36
+ }
37
+
38
+ /**
39
+ * Retarget a Mixamo-style GLB animation clip onto a VRM.
40
+ * Position tracks are ignored to avoid root motion.
41
+ */
42
+ export function retargetMixamoGltfToVrm(
43
+ animation: { scene: THREE.Group; animations: THREE.AnimationClip[] },
44
+ vrm: VRM,
45
+ clipName?: string,
46
+ ): THREE.AnimationClip {
47
+ animation.scene.updateMatrixWorld(true);
48
+ vrm.scene.updateMatrixWorld(true);
49
+
50
+ const sourceClip = animation.animations[0];
51
+ if (!sourceClip) {
52
+ throw new Error("GLB contains no animation clips");
53
+ }
54
+
55
+ const tracks: Array<THREE.QuaternionKeyframeTrack> = [];
56
+ const restRotationInverse = new THREE.Quaternion();
57
+ const parentRestWorldRotation = new THREE.Quaternion();
58
+ const q = new THREE.Quaternion();
59
+
60
+ for (const track of sourceClip.tracks) {
61
+ const parts = track.name.split(".");
62
+ const rawRigName = parts[0];
63
+ const propertyName = parts[1];
64
+ if (!rawRigName || !propertyName) continue;
65
+ if (propertyName !== "quaternion") continue;
66
+ if (!(track instanceof THREE.QuaternionKeyframeTrack)) continue;
67
+
68
+ const normalizedRigName = normalizeMixamoRigName(rawRigName);
69
+ const vrmBoneName = mixamoVRMRigMap[normalizedRigName];
70
+ if (!vrmBoneName) continue;
71
+
72
+ const vrmNode = vrm.humanoid?.getNormalizedBoneNode(
73
+ vrmBoneName as VRMHumanBoneName,
74
+ );
75
+ if (!vrmNode) continue;
76
+
77
+ const mixamoRigNode = findNode(
78
+ animation.scene,
79
+ rawRigName,
80
+ normalizedRigName,
81
+ );
82
+ if (!mixamoRigNode || !mixamoRigNode.parent) continue;
83
+
84
+ mixamoRigNode.getWorldQuaternion(restRotationInverse).invert();
85
+ mixamoRigNode.parent.getWorldQuaternion(parentRestWorldRotation);
86
+
87
+ const values = track.values.slice();
88
+ for (let i = 0; i < values.length; i += 4) {
89
+ q.fromArray(values, i);
90
+ q.premultiply(parentRestWorldRotation).multiply(restRotationInverse);
91
+ q.toArray(values, i);
92
+ }
93
+
94
+ tracks.push(
95
+ new THREE.QuaternionKeyframeTrack(
96
+ `${vrmNode.name}.quaternion`,
97
+ track.times,
98
+ values.map((v, i) => (isVrm0(vrm) && i % 2 === 0 ? -v : v)),
99
+ ),
100
+ );
101
+ }
102
+
103
+ const hasHipsTrack = tracks.some((track) =>
104
+ track.name.startsWith(
105
+ `${vrm.humanoid?.getNormalizedBoneNode("hips" as VRMHumanBoneName)?.name ?? "__missing__"}.`,
106
+ ),
107
+ );
108
+ if (!hasHipsTrack) {
109
+ throw new Error(
110
+ `Retargeting failed: no hips bone track found (mapped ${tracks.length} tracks). ` +
111
+ "Expected Mixamo bone names like mixamorigHips/mixamorigSpine...",
112
+ );
113
+ }
114
+
115
+ const name = clipName ?? sourceClip.name ?? "retargeted";
116
+ const clip = new THREE.AnimationClip(name, sourceClip.duration, tracks);
117
+ clip.optimize();
118
+ return clip;
119
+ }
@@ -0,0 +1,380 @@
1
+ /**
2
+ * Central chain configuration registry.
3
+ *
4
+ * Every chain-specific constant (explorer URLs, native token details,
5
+ * gas thresholds, stablecoin addresses, logo URLs, address validation)
6
+ * lives here so that UI components and hooks can derive values from
7
+ * a single source of truth rather than scattering inline constants.
8
+ */
9
+
10
+ /* ── Types ─────────────────────────────────────────────────────────── */
11
+
12
+ export type ChainKey =
13
+ | "bsc"
14
+ | "avax"
15
+ | "solana"
16
+ | "ethereum"
17
+ | "base"
18
+ | "arbitrum"
19
+ | "optimism"
20
+ | "polygon";
21
+
22
+ export interface Stablecoin {
23
+ symbol: string;
24
+ address: string;
25
+ }
26
+
27
+ export interface ChainConfig {
28
+ /** Unique identifier used as filter key and in storage keys. */
29
+ chainKey: ChainKey;
30
+ /** Human-readable chain name. */
31
+ name: string;
32
+ /** Native gas-token symbol (e.g. BNB, AVAX, SOL). */
33
+ nativeSymbol: string;
34
+ /** Native token decimals. */
35
+ nativeDecimals: number;
36
+ /** Whether this is an EVM-compatible chain. */
37
+ isEvm: boolean;
38
+
39
+ /* ── Explorer ──────────────────────────────── */
40
+
41
+ /** Base URL of the chain's block explorer. */
42
+ explorerBaseUrl: string;
43
+ /** Path template for token pages — `{address}` is replaced. */
44
+ explorerTokenPath: string;
45
+ /** Path template for transaction pages — `{hash}` is replaced. */
46
+ explorerTxPath: string;
47
+
48
+ /* ── Logos ──────────────────────────────────── */
49
+
50
+ /** URL for the native gas-token logo. */
51
+ nativeLogoUrl: string;
52
+ /** TrustWallet assets CDN slug (e.g. `smartchain`, `avalanchec`). */
53
+ trustWalletSlug: string | null;
54
+
55
+ /* ── Gas ────────────────────────────────────── */
56
+
57
+ /** Minimum native balance to consider the wallet "trade-ready". */
58
+ gasReadyThreshold: number;
59
+ /** Reserve kept aside from max-balance swaps. */
60
+ swapGasReserve: number;
61
+
62
+ /* ── Stablecoins ───────────────────────────── */
63
+
64
+ /** Well-known stablecoin contract addresses on this chain. */
65
+ stablecoins: Stablecoin[];
66
+
67
+ /* ── Address ───────────────────────────────── */
68
+
69
+ /** Regex to validate an address on this chain. */
70
+ addressRegex: RegExp;
71
+
72
+ /* ── DexScreener ───────────────────────────── */
73
+
74
+ /** Chain ID used by the DexScreener API. */
75
+ dexScreenerChainId: string;
76
+
77
+ /* ── Variants ──────────────────────────────── */
78
+
79
+ /** Alternative chain name strings that resolve to this config. */
80
+ nameVariants: string[];
81
+
82
+ /* ── Branding ──────────────────────────────── */
83
+
84
+ /** Brand color for the chain (hex string, e.g. `"#627eea"`). */
85
+ color: string;
86
+ }
87
+
88
+ /* ── Registry ──────────────────────────────────────────────────────── */
89
+
90
+ const HEX_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
91
+ const SOLANA_ADDRESS_RE = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
92
+
93
+ export const CHAIN_CONFIGS: Record<ChainKey, ChainConfig> = {
94
+ bsc: {
95
+ chainKey: "bsc",
96
+ name: "BSC",
97
+ nativeSymbol: "BNB",
98
+ nativeDecimals: 18,
99
+ isEvm: true,
100
+ explorerBaseUrl: "https://bscscan.com",
101
+ explorerTokenPath: "/token/{address}",
102
+ explorerTxPath: "/tx/{hash}",
103
+ nativeLogoUrl:
104
+ "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/smartchain/info/logo.png",
105
+ trustWalletSlug: "smartchain",
106
+ gasReadyThreshold: 0.005,
107
+ swapGasReserve: 0.002,
108
+ stablecoins: [
109
+ { symbol: "USDT", address: "0x55d398326f99059fF775485246999027B3197955" },
110
+ { symbol: "USDC", address: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d" },
111
+ ],
112
+ addressRegex: HEX_ADDRESS_RE,
113
+ dexScreenerChainId: "bsc",
114
+ nameVariants: ["bsc", "bnb chain", "bnb smart chain"],
115
+ color: "#f3ba2f",
116
+ },
117
+
118
+ avax: {
119
+ chainKey: "avax",
120
+ name: "Avalanche",
121
+ nativeSymbol: "AVAX",
122
+ nativeDecimals: 18,
123
+ isEvm: true,
124
+ explorerBaseUrl: "https://snowtrace.io",
125
+ explorerTokenPath: "/token/{address}",
126
+ explorerTxPath: "/tx/{hash}",
127
+ nativeLogoUrl:
128
+ "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/avalanchec/info/logo.png",
129
+ trustWalletSlug: "avalanchec",
130
+ gasReadyThreshold: 0.01,
131
+ swapGasReserve: 0.005,
132
+ stablecoins: [
133
+ { symbol: "USDT", address: "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7" },
134
+ { symbol: "USDC", address: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E" },
135
+ ],
136
+ addressRegex: HEX_ADDRESS_RE,
137
+ dexScreenerChainId: "avalanche",
138
+ nameVariants: ["avax", "avalanche", "c-chain", "avalanche c-chain"],
139
+ color: "#e84142",
140
+ },
141
+
142
+ solana: {
143
+ chainKey: "solana",
144
+ name: "Solana",
145
+ nativeSymbol: "SOL",
146
+ nativeDecimals: 9,
147
+ isEvm: false,
148
+ explorerBaseUrl: "https://solscan.io",
149
+ explorerTokenPath: "/token/{address}",
150
+ explorerTxPath: "/tx/{hash}",
151
+ nativeLogoUrl:
152
+ "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/solana/info/logo.png",
153
+ trustWalletSlug: "solana",
154
+ gasReadyThreshold: 0.01,
155
+ swapGasReserve: 0.005,
156
+ stablecoins: [
157
+ {
158
+ symbol: "USDC",
159
+ address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
160
+ },
161
+ ],
162
+ addressRegex: SOLANA_ADDRESS_RE,
163
+ dexScreenerChainId: "solana",
164
+ nameVariants: ["solana", "sol"],
165
+ color: "#9945ff",
166
+ },
167
+
168
+ ethereum: {
169
+ chainKey: "ethereum",
170
+ name: "Ethereum",
171
+ nativeSymbol: "ETH",
172
+ nativeDecimals: 18,
173
+ isEvm: true,
174
+ explorerBaseUrl: "https://etherscan.io",
175
+ explorerTokenPath: "/token/{address}",
176
+ explorerTxPath: "/tx/{hash}",
177
+ nativeLogoUrl:
178
+ "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png",
179
+ trustWalletSlug: "ethereum",
180
+ gasReadyThreshold: 0.005,
181
+ swapGasReserve: 0.002,
182
+ stablecoins: [
183
+ { symbol: "USDT", address: "0xdAC17F958D2ee523a2206206994597C13D831ec7" },
184
+ { symbol: "USDC", address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" },
185
+ ],
186
+ addressRegex: HEX_ADDRESS_RE,
187
+ dexScreenerChainId: "ethereum",
188
+ nameVariants: ["ethereum", "mainnet", "eth"],
189
+ color: "#627eea",
190
+ },
191
+
192
+ base: {
193
+ chainKey: "base",
194
+ name: "Base",
195
+ nativeSymbol: "ETH",
196
+ nativeDecimals: 18,
197
+ isEvm: true,
198
+ explorerBaseUrl: "https://basescan.org",
199
+ explorerTokenPath: "/token/{address}",
200
+ explorerTxPath: "/tx/{hash}",
201
+ nativeLogoUrl:
202
+ "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/base/info/logo.png",
203
+ trustWalletSlug: "base",
204
+ gasReadyThreshold: 0.005,
205
+ swapGasReserve: 0.001,
206
+ stablecoins: [
207
+ { symbol: "USDC", address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" },
208
+ ],
209
+ addressRegex: HEX_ADDRESS_RE,
210
+ dexScreenerChainId: "base",
211
+ nameVariants: ["base"],
212
+ color: "#0052ff",
213
+ },
214
+
215
+ arbitrum: {
216
+ chainKey: "arbitrum",
217
+ name: "Arbitrum",
218
+ nativeSymbol: "ETH",
219
+ nativeDecimals: 18,
220
+ isEvm: true,
221
+ explorerBaseUrl: "https://arbiscan.io",
222
+ explorerTokenPath: "/token/{address}",
223
+ explorerTxPath: "/tx/{hash}",
224
+ nativeLogoUrl:
225
+ "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png",
226
+ trustWalletSlug: null,
227
+ gasReadyThreshold: 0.005,
228
+ swapGasReserve: 0.001,
229
+ stablecoins: [
230
+ { symbol: "USDC", address: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831" },
231
+ ],
232
+ addressRegex: HEX_ADDRESS_RE,
233
+ dexScreenerChainId: "arbitrum",
234
+ nameVariants: ["arbitrum"],
235
+ color: "#12aaff",
236
+ },
237
+
238
+ optimism: {
239
+ chainKey: "optimism",
240
+ name: "Optimism",
241
+ nativeSymbol: "ETH",
242
+ nativeDecimals: 18,
243
+ isEvm: true,
244
+ explorerBaseUrl: "https://optimistic.etherscan.io",
245
+ explorerTokenPath: "/token/{address}",
246
+ explorerTxPath: "/tx/{hash}",
247
+ nativeLogoUrl:
248
+ "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/info/logo.png",
249
+ trustWalletSlug: null,
250
+ gasReadyThreshold: 0.005,
251
+ swapGasReserve: 0.001,
252
+ stablecoins: [
253
+ { symbol: "USDC", address: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85" },
254
+ ],
255
+ addressRegex: HEX_ADDRESS_RE,
256
+ dexScreenerChainId: "optimism",
257
+ nameVariants: ["optimism"],
258
+ color: "#ff0420",
259
+ },
260
+
261
+ polygon: {
262
+ chainKey: "polygon",
263
+ name: "Polygon",
264
+ nativeSymbol: "MATIC",
265
+ nativeDecimals: 18,
266
+ isEvm: true,
267
+ explorerBaseUrl: "https://polygonscan.com",
268
+ explorerTokenPath: "/token/{address}",
269
+ explorerTxPath: "/tx/{hash}",
270
+ nativeLogoUrl:
271
+ "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/polygon/info/logo.png",
272
+ trustWalletSlug: "polygon",
273
+ gasReadyThreshold: 0.5,
274
+ swapGasReserve: 0.1,
275
+ stablecoins: [
276
+ { symbol: "USDT", address: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F" },
277
+ { symbol: "USDC", address: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359" },
278
+ ],
279
+ addressRegex: HEX_ADDRESS_RE,
280
+ dexScreenerChainId: "polygon",
281
+ nameVariants: ["polygon"],
282
+ color: "#8247e5",
283
+ },
284
+ };
285
+
286
+ /* ── Lookup helpers ────────────────────────────────────────────────── */
287
+
288
+ /** Pre-built lookup table: lowercase variant → ChainConfig. */
289
+ const _variantMap = new Map<string, ChainConfig>();
290
+ for (const config of Object.values(CHAIN_CONFIGS)) {
291
+ for (const variant of config.nameVariants) {
292
+ _variantMap.set(variant.toLowerCase(), config);
293
+ }
294
+ }
295
+
296
+ /** Resolve a chain name (case-insensitive, trimmed) to its config. */
297
+ export function getChainConfig(chainName: string): ChainConfig | null {
298
+ return _variantMap.get(chainName.trim().toLowerCase()) ?? null;
299
+ }
300
+
301
+ /**
302
+ * Resolve a chain name string to a `ChainKey`.
303
+ * Returns `null` for unrecognised chains.
304
+ */
305
+ export function resolveChainKey(chainName: string): ChainKey | null {
306
+ const config = getChainConfig(chainName);
307
+ return config?.chainKey ?? null;
308
+ }
309
+
310
+ /**
311
+ * Build the explorer URL for a token on the given chain.
312
+ * Returns `null` if the chain is unknown or the address is invalid.
313
+ */
314
+ export function getExplorerTokenUrl(
315
+ chainName: string,
316
+ address: string,
317
+ ): string | null {
318
+ const config = getChainConfig(chainName);
319
+ if (!config) return null;
320
+ const trimmed = address.trim();
321
+ if (!config.addressRegex.test(trimmed)) return null;
322
+ return `${config.explorerBaseUrl}${config.explorerTokenPath.replace("{address}", trimmed)}`;
323
+ }
324
+
325
+ /**
326
+ * Build the explorer URL for a transaction on the given chain.
327
+ * Returns `null` if the chain is unknown.
328
+ */
329
+ export function getExplorerTxUrl(
330
+ chainName: string,
331
+ hash: string,
332
+ ): string | null {
333
+ const config = getChainConfig(chainName);
334
+ if (!config) return null;
335
+ return `${config.explorerBaseUrl}${config.explorerTxPath.replace("{hash}", hash.trim())}`;
336
+ }
337
+
338
+ /**
339
+ * Get the native token logo URL for a chain, or `null` if unknown.
340
+ */
341
+ export function getNativeLogoUrl(chainName: string): string | null {
342
+ return getChainConfig(chainName)?.nativeLogoUrl ?? null;
343
+ }
344
+
345
+ /**
346
+ * Get the TrustWallet CDN logo URL for a contract token on the given chain.
347
+ * Returns `null` if the chain has no TrustWallet slug or no contract address.
348
+ */
349
+ export function getContractLogoUrl(
350
+ chainName: string,
351
+ contractAddress: string | null,
352
+ ): string | null {
353
+ if (!contractAddress) return null;
354
+ const config = getChainConfig(chainName);
355
+ if (!config?.trustWalletSlug) return null;
356
+ return `https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/${config.trustWalletSlug}/assets/${contractAddress}/logo.png`;
357
+ }
358
+
359
+ /**
360
+ * Resolve a stablecoin address on a given chain by symbol.
361
+ * Returns `null` if not found.
362
+ */
363
+ export function getStablecoinAddress(
364
+ chainName: string,
365
+ symbol: string,
366
+ ): string | null {
367
+ const config = getChainConfig(chainName);
368
+ if (!config) return null;
369
+ const upper = symbol.trim().toUpperCase();
370
+ return config.stablecoins.find((s) => s.symbol === upper)?.address ?? null;
371
+ }
372
+
373
+ /** The primary chains we want to support prominently. */
374
+ export const PRIMARY_CHAIN_KEYS: ChainKey[] = [
375
+ "ethereum",
376
+ "base",
377
+ "bsc",
378
+ "avax",
379
+ "solana",
380
+ ];