@sponsorcoin/merit-wallet 0.0.1 → 0.0.2

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 (267) hide show
  1. package/README.md +22 -0
  2. package/dist/AgentSelectDropDown.js +4 -2
  3. package/dist/AgentSelectDropDownWithAvatar.js +2 -1
  4. package/dist/ChangePasswordPanel.js +6 -6
  5. package/dist/ConnectedMessagePanel.js +17 -15
  6. package/dist/ConnectedNetworkPanel.js +7 -12
  7. package/dist/DeleteWalletDialog.js +59 -12
  8. package/dist/DialogPortal.d.ts +3 -0
  9. package/dist/DialogPortal.js +19 -0
  10. package/dist/MeritWallet.d.ts +47 -47
  11. package/dist/MeritWallet.js +1384 -762
  12. package/dist/PasswordPromptDialog.js +8 -6
  13. package/dist/RecoveryPhraseScreen.js +3 -7
  14. package/dist/SecuritySetupScreen.d.ts +44 -0
  15. package/dist/SecuritySetupScreen.js +119 -0
  16. package/dist/SecurityWarningScreen.d.ts +5 -0
  17. package/dist/SecurityWarningScreen.js +18 -0
  18. package/dist/TestAccountsSection.js +2 -1
  19. package/dist/TokenPanel.js +29 -9
  20. package/dist/TokenSlotPanels.js +7 -8
  21. package/dist/UnlockSecondStep.d.ts +7 -0
  22. package/dist/UnlockSecondStep.js +50 -0
  23. package/dist/VaultAccountsPanel.js +22 -22
  24. package/dist/WalletOnboardingPanel.js +11 -12
  25. package/dist/WalletSecuritySection.js +4 -15
  26. package/dist/account/AccountDetailScreen.d.ts +7 -2
  27. package/dist/account/AccountDetailScreen.js +52 -28
  28. package/dist/account/AccountPrivateKeyRow.d.ts +6 -0
  29. package/dist/account/AccountPrivateKeyRow.js +6 -2
  30. package/dist/account/AddAccountFlow.d.ts +18 -18
  31. package/dist/account/AddAccountFlow.js +159 -121
  32. package/dist/account/DetailImageBlock.d.ts +44 -0
  33. package/dist/account/DetailImageBlock.js +46 -0
  34. package/dist/account/DetailScreenLayout.d.ts +38 -0
  35. package/dist/account/DetailScreenLayout.js +30 -0
  36. package/dist/account/MetaDetailScreen.d.ts +30 -0
  37. package/dist/account/MetaDetailScreen.js +33 -0
  38. package/dist/account/RemoveAccountPanel.d.ts +14 -0
  39. package/dist/account/RemoveAccountPanel.js +97 -0
  40. package/dist/account/accountDetailHostFromProfile.d.ts +4 -2
  41. package/dist/account/accountDetailHostFromProfile.js +3 -2
  42. package/dist/account/accountSource.d.ts +36 -0
  43. package/dist/account/accountSource.js +82 -0
  44. package/dist/account/form/AccountAvatarPanel.js +12 -235
  45. package/dist/account/form/AccountFormPanel.d.ts +6 -2
  46. package/dist/account/form/AccountFormPanel.js +82 -114
  47. package/dist/account/form/AvatarControls.d.ts +25 -0
  48. package/dist/account/form/AvatarControls.js +27 -0
  49. package/dist/account/form/AvatarPicture.d.ts +11 -0
  50. package/dist/account/form/AvatarPicture.js +11 -0
  51. package/dist/account/form/CopyIcon.d.ts +1 -0
  52. package/dist/account/form/CopyIcon.js +7 -0
  53. package/dist/account/form/DisconnectedControl.js +2 -1
  54. package/dist/account/form/formTypes.d.ts +0 -1
  55. package/dist/account/form/useAccountForm.d.ts +8 -2
  56. package/dist/account/form/useAccountForm.js +79 -44
  57. package/dist/account/form/useAccountFormDerivedState.js +1 -1
  58. package/dist/account/form/useAccountLiveRefresh.d.ts +15 -0
  59. package/dist/account/form/useAccountLiveRefresh.js +46 -0
  60. package/dist/account/form/useAvatarPreviewSize.d.ts +39 -0
  61. package/dist/account/form/useAvatarPreviewSize.js +245 -0
  62. package/dist/account/responseFailure.d.ts +1 -0
  63. package/dist/account/responseFailure.js +7 -0
  64. package/dist/account/signIn.js +3 -6
  65. package/dist/approval/approvalLifecycle.d.ts +15 -0
  66. package/dist/approval/approvalLifecycle.js +51 -0
  67. package/dist/approval/meritApproval.js +48 -3
  68. package/dist/approval/pendingApprovalMessageStore.d.ts +7 -0
  69. package/dist/approval/pendingApprovalMessageStore.js +18 -0
  70. package/dist/approval/useMeritApprovalMessagePanel.d.ts +7 -0
  71. package/dist/approval/useMeritApprovalMessagePanel.js +132 -0
  72. package/dist/auth/accountNoticeStore.d.ts +22 -0
  73. package/dist/auth/accountNoticeStore.js +31 -0
  74. package/dist/auth/approvalWaiting.d.ts +4 -2
  75. package/dist/auth/approvalWaiting.js +16 -2
  76. package/dist/auth/authenticator.d.ts +29 -4
  77. package/dist/auth/authenticator.js +3 -2
  78. package/dist/auth/connect.js +1 -1
  79. package/dist/auth/injectedAuthenticator.d.ts +4 -1
  80. package/dist/auth/injectedAuthenticator.js +28 -1
  81. package/dist/auth/keptAccounts.d.ts +14 -0
  82. package/dist/auth/keptAccounts.js +126 -0
  83. package/dist/auth/meritConnection.d.ts +31 -0
  84. package/dist/auth/meritConnection.js +65 -0
  85. package/dist/auth/registerApprovingWallet.d.ts +1 -0
  86. package/dist/auth/registerApprovingWallet.js +15 -0
  87. package/dist/auth/useAccountNoticeMessagePanel.d.ts +5 -0
  88. package/dist/auth/useAccountNoticeMessagePanel.js +54 -0
  89. package/dist/auth/useApprovalMessagePanel.js +16 -6
  90. package/dist/auth/walletAuthenticators.d.ts +35 -0
  91. package/dist/auth/walletAuthenticators.js +83 -0
  92. package/dist/auth/walletAuthenticatorsReact.d.ts +9 -0
  93. package/dist/auth/walletAuthenticatorsReact.js +14 -0
  94. package/dist/balance/createBalanceReader.d.ts +23 -0
  95. package/dist/balance/createBalanceReader.js +24 -0
  96. package/dist/chain/chainReconcile.d.ts +68 -0
  97. package/dist/chain/chainReconcile.js +139 -0
  98. package/dist/config/WalletSessionDialogs.js +3 -1
  99. package/dist/debug/DebugBug.d.ts +8 -0
  100. package/dist/debug/DebugBug.js +74 -0
  101. package/dist/debug/debugStore.d.ts +39 -0
  102. package/dist/debug/debugStore.js +148 -0
  103. package/dist/hostContract.d.ts +6 -6
  104. package/dist/index.d.ts +52 -18
  105. package/dist/index.js +46 -13
  106. package/dist/injectableComponentTypes.d.ts +97 -0
  107. package/dist/injectableComponentTypes.js +12 -0
  108. package/dist/keystore/index.d.ts +3 -0
  109. package/dist/keystore/index.js +10 -0
  110. package/dist/keystore/publicAccountStore.d.ts +14 -0
  111. package/dist/keystore/publicAccountStore.js +29 -0
  112. package/dist/keystore/secretCache.d.ts +20 -0
  113. package/dist/keystore/secretCache.js +48 -0
  114. package/dist/panels/AcknowledgeDialog.d.ts +6 -0
  115. package/dist/panels/AcknowledgeDialog.js +14 -0
  116. package/dist/panels/ActiveAccountSelectDropDown.d.ts +22 -0
  117. package/dist/panels/ActiveAccountSelectDropDown.js +57 -0
  118. package/dist/panels/AddressPanel.js +2 -1
  119. package/dist/panels/AgentSelectDropDown.js +3 -36
  120. package/dist/panels/AnonymousAvatar.js +2 -1
  121. package/dist/panels/DetailRow.d.ts +11 -0
  122. package/dist/panels/DetailRow.js +13 -0
  123. package/dist/panels/MenuTabHeaderBar.js +6 -64
  124. package/dist/panels/MeritInfoPanel.js +2 -2
  125. package/dist/panels/MeritInfoPanelReal.js +5 -3
  126. package/dist/panels/MessageDetailsSection.js +2 -1
  127. package/dist/panels/MessageLabelValueRow.js +2 -1
  128. package/dist/panels/MessagePanel.js +2 -18
  129. package/dist/panels/MessagePanelReal.d.ts +51 -1
  130. package/dist/panels/MessagePanelReal.js +123 -20
  131. package/dist/panels/NetworkDetailPanel.js +3 -46
  132. package/dist/panels/NetworkSelectDropDown.d.ts +5 -1
  133. package/dist/panels/NetworkSelectDropDown.js +8 -5
  134. package/dist/panels/PanelTitle.js +3 -67
  135. package/dist/panels/ReadOnlyMetaDataTable.js +12 -11
  136. package/dist/panels/SendTabPanel.d.ts +10 -1
  137. package/dist/panels/SendTabPanel.js +16 -4
  138. package/dist/panels/TokenAddressComponent.js +4 -6
  139. package/dist/panels/TokenDetailPanel.js +11 -48
  140. package/dist/panels/TokenSelectDropDown.d.ts +7 -1
  141. package/dist/panels/TokenSelectDropDown.js +7 -8
  142. package/dist/panels/TradingStationPanel.js +4 -15
  143. package/dist/panels/WalletAccountHeader.d.ts +5 -5
  144. package/dist/panels/WalletAccountHeader.js +11 -40
  145. package/dist/panels/WalletAccountSelectDropDown.d.ts +15 -0
  146. package/dist/panels/WalletAccountSelectDropDown.js +42 -0
  147. package/dist/panels/WalletRadioPanels.d.ts +9 -12
  148. package/dist/panels/WalletRadioPanels.js +97 -41
  149. package/dist/panels/clipboardClear.d.ts +7 -0
  150. package/dist/panels/clipboardClear.js +31 -0
  151. package/dist/panels/index.d.ts +3 -11
  152. package/dist/panels/index.js +1 -6
  153. package/dist/panels/meritWalletDataFetch.d.ts +20 -10
  154. package/dist/panels/meritWalletDataFetch.js +53 -13
  155. package/dist/panels/useChainInfo.d.ts +5 -0
  156. package/dist/panels/useChainInfo.js +28 -0
  157. package/dist/peer/peerConnection.d.ts +43 -0
  158. package/dist/peer/peerConnection.js +126 -0
  159. package/dist/platform/domPlatform.js +111 -1
  160. package/dist/platform/platform.d.ts +19 -0
  161. package/dist/provider/useMeritLink.d.ts +8 -0
  162. package/dist/provider/useMeritLink.js +8 -0
  163. package/dist/provider/useMetaMask.d.ts +11 -0
  164. package/dist/provider/useMetaMask.js +67 -0
  165. package/dist/radioPanelGroups.d.ts +8 -10
  166. package/dist/radioPanelGroups.js +47 -13
  167. package/dist/receipt/transactionReceipts.d.ts +27 -0
  168. package/dist/receipt/transactionReceipts.js +25 -0
  169. package/dist/refresh/refreshReact.d.ts +30 -0
  170. package/dist/refresh/refreshReact.js +101 -0
  171. package/dist/refresh/refreshTree.d.ts +42 -0
  172. package/dist/refresh/refreshTree.js +81 -0
  173. package/dist/rewards/ConnectedRewardsPanel.d.ts +6 -1
  174. package/dist/rewards/ConnectedRewardsPanel.js +58 -10
  175. package/dist/rewards/accountRoleFlags.d.ts +1 -0
  176. package/dist/rewards/accountRoleFlags.js +25 -0
  177. package/dist/rewards/chainClock.d.ts +17 -0
  178. package/dist/rewards/chainClock.js +63 -0
  179. package/dist/rewards/estimateRewards.d.ts +11 -3
  180. package/dist/rewards/estimateRewards.js +76 -2
  181. package/dist/security/mfaFactorsStore.d.ts +16 -0
  182. package/dist/security/mfaFactorsStore.js +55 -0
  183. package/dist/security/passkey.d.ts +20 -0
  184. package/dist/security/passkey.js +60 -0
  185. package/dist/security/signInOptions.d.ts +20 -0
  186. package/dist/security/signInOptions.js +12 -0
  187. package/dist/security/totp.d.ts +13 -0
  188. package/dist/security/totp.js +77 -0
  189. package/dist/send/SendComponent.js +2 -2
  190. package/dist/send/sendTransfer.d.ts +2 -2
  191. package/dist/send/sendTransfer.js +3 -17
  192. package/dist/session/activeAccountCore.d.ts +81 -0
  193. package/dist/session/activeAccountCore.js +57 -0
  194. package/dist/session/indexedDbKey.d.ts +12 -0
  195. package/dist/session/indexedDbKey.js +39 -0
  196. package/dist/session/persistedPassword.d.ts +23 -0
  197. package/dist/session/persistedPassword.js +65 -0
  198. package/dist/session/validateLogin.d.ts +11 -0
  199. package/dist/session/validateLogin.js +11 -0
  200. package/dist/session/walletSession.d.ts +9 -0
  201. package/dist/session/walletSession.js +10 -4
  202. package/dist/session/walletSessionReact.d.ts +1 -0
  203. package/dist/session/walletSessionReact.js +2 -1
  204. package/dist/sponsor/ConnectedSponsorPanel.d.ts +7 -0
  205. package/dist/sponsor/ConnectedSponsorPanel.js +32 -0
  206. package/dist/sponsor/ConnectedSponsorStakingList.d.ts +15 -0
  207. package/dist/sponsor/ConnectedSponsorStakingList.js +205 -35
  208. package/dist/sponsor/sponsorReads.d.ts +13 -0
  209. package/dist/sponsor/sponsorReads.js +59 -0
  210. package/dist/sponsor/stakingListMode.d.ts +9 -0
  211. package/dist/sponsor/stakingListMode.js +20 -0
  212. package/dist/swap/parseDecimalAmount.d.ts +5 -0
  213. package/dist/swap/parseDecimalAmount.js +26 -0
  214. package/dist/sync/pushContextPatch.d.ts +24 -0
  215. package/dist/sync/pushContextPatch.js +19 -0
  216. package/dist/sync/syncToken.d.ts +52 -0
  217. package/dist/sync/syncToken.js +128 -0
  218. package/dist/trade/tradeHost.d.ts +37 -0
  219. package/dist/trade/tradeHost.js +29 -0
  220. package/dist/vault/providerController.d.ts +21 -0
  221. package/dist/vault/providerController.js +93 -9
  222. package/dist/vault/vaultCapability.d.ts +4 -0
  223. package/dist/vault/vaultCapability.js +5 -1
  224. package/dist/vault/walletAccounts.d.ts +9 -1
  225. package/dist/vault/walletAccounts.js +27 -3
  226. package/dist/vault/workerVault.js +7 -1
  227. package/dist/wallet/AccountSigningSection.js +4 -5
  228. package/dist/wallet/WalletBackupSection.js +2 -4
  229. package/dist/wallet/accountHolders.d.ts +9 -0
  230. package/dist/wallet/accountHolders.js +22 -0
  231. package/dist/wallet/accountSigning.js +6 -1
  232. package/dist/wallet/approverAccounts.d.ts +42 -2
  233. package/dist/wallet/approverAccounts.js +110 -19
  234. package/dist/wallet/approverAccountsReact.d.ts +3 -0
  235. package/dist/wallet/approverAccountsReact.js +118 -18
  236. package/dist/wallet/sameAddress.d.ts +1 -0
  237. package/dist/wallet/sameAddress.js +4 -0
  238. package/dist/wallet/wallet.d.ts +10 -2
  239. package/dist/wallet/wallet.js +61 -28
  240. package/dist/wallet/walletSlugs.d.ts +2 -0
  241. package/dist/wallet/walletSlugs.js +7 -0
  242. package/dist/walletConfig.d.ts +13 -0
  243. package/dist/walletConfig.js +53 -4
  244. package/dist/walletConfigPanelProps.d.ts +7 -1
  245. package/dist/walletConfigPanelProps.js +17 -5
  246. package/dist/walletData.d.ts +2 -2
  247. package/package.json +4 -7
  248. package/dist/ConnectedPasswordPanel.d.ts +0 -13
  249. package/dist/ConnectedPasswordPanel.js +0 -30
  250. package/dist/WalletOverlayHost.d.ts +0 -35
  251. package/dist/WalletOverlayHost.js +0 -46
  252. package/dist/account/accountProfileClient.d.ts +0 -18
  253. package/dist/account/accountProfileClient.js +0 -50
  254. package/dist/auth/ApprovalWaitingNotice.d.ts +0 -4
  255. package/dist/auth/ApprovalWaitingNotice.js +0 -20
  256. package/dist/panels/DetailPanelEmptyState.d.ts +0 -10
  257. package/dist/panels/DetailPanelEmptyState.js +0 -19
  258. package/dist/panels/GenericListPanel.d.ts +0 -15
  259. package/dist/panels/GenericListPanel.js +0 -33
  260. package/dist/panels/RewardsPendingByAccountTypePanel.d.ts +0 -9
  261. package/dist/panels/RewardsPendingByAccountTypePanel.js +0 -39
  262. package/dist/panels/SendAddressHeaderBar.d.ts +0 -14
  263. package/dist/panels/SendAddressHeaderBar.js +0 -80
  264. package/dist/panels/SendTitle.d.ts +0 -1
  265. package/dist/panels/SendTitle.js +0 -13
  266. package/dist/panels/SendToAddressComponent.d.ts +0 -5
  267. package/dist/panels/SendToAddressComponent.js +0 -13
package/README.md CHANGED
@@ -7,3 +7,25 @@ It depends on `@sponsorcoin/spcoin-panels` (peer dependency). The plan for mergi
7
7
  is in `docs/meritWalletNpmDesignToDo.txt` of the web app repo.
8
8
 
9
9
  Build: `npm run build` (tsc, output in `dist/`, which is tracked like the other packages).
10
+
11
+ ## Host injectables and defaults
12
+
13
+ The wallet owns portable defaults where they are safe. Hosts inject the parts that differ by deployment. An empty optional hook or API is not a working implementation: required behavior must be supplied by the host.
14
+
15
+ | Injectable | Wallet default | What an app overrides it for | Who supplies it today |
16
+ | --- | --- | --- | --- |
17
+ | `MeritWalletApi` (`session`, `accounts`, `signing`) | `createWallet()` supplies an empty authenticator registry and in-memory active-authenticator selection; it does not create a vault or sign. | Connect the wallet UI to a real vault, accounts, approvals, signing, and transaction transport. | Web: `webWallet` and `webApprovers` backed by wallet state/server routes. Extension: local vault and background worker. |
18
+ | `createWallet` registry, default backend, and active-authenticator store | Empty registry; `appDefault` is required; active selection falls back to memory. | Register the host's signers and persist account-to-signer selection. | Web: server keystore authenticator and web default. Extension: local vault authenticator and extension storage. |
19
+ | Vault storage and capability | `VaultSession` and `createLocalVault` implement crypto and account operations; the host must provide ciphertext storage. | Choose secure persistence and an unlock lifecycle for the device. | Web uses its server keystore. Extension uses `chrome.storage` through the background worker. |
20
+ | `MeritWalletApi.networks` | Network data comes from shared `spcoin-feeds`; wallet selection helpers do not own persistence. | Supply a different network set or persist active-network changes. | Web: shared registry plus app chain selection. Extension: shared registry plus extension chain selection. |
21
+ | `WalletDataSource.resolveNetwork` | Shared `spcoin-feeds` resolver when omitted by consumers that use the shared network helper. | Use an app-specific registry. | Web and extension use shared feeds; no separate resolver is currently injected. |
22
+ | `WalletDataSource.loadNetworkInfo` | Relative fetch from `/assets/blockchains/<chainId>/info.json` in the panel layer. | Change the asset origin or provide a native/platform data source. | Web uses same-origin assets. Extension supplies its packaged asset base URL. |
23
+ | `WalletDataSource.loadTokenRecord` | No loader; token detail can remain partial when no loader is supplied. | Hydrate an address-only token from the app's token registry. | Web supplies its token store loader. Extension currently leaves it unset. |
24
+ | `MeritPlatform` | `domPlatform`: browser clipboard, URL opening, file download, localStorage preferences, visibility and focus. | Replace browser operations with native share/storage/lifecycle behavior. | Web and extension use the package default. |
25
+ | `walletAssetBaseUrl` | Empty base URL, so assets resolve from the current origin. | Point packaged wallet identity assets at an extension or other app bundle. | Web uses its origin. Extension supplies `chrome.runtime.getURL('')`. |
26
+ | Sponsor read cache store | LocalStorage with memory fallback. | Share/cache sponsor totals through an app backend or platform storage. | Web registers the HTTP cache store. Extension uses the local default. |
27
+ | Sponsor read transport (`SpCoinReadStepFn`) | HTTP POST to the same-origin `/api/spCoin/run-script` route. | Use a direct RPC client or another route when the host has no web server. | Web uses the default route. Extension injects direct chain RPC. |
28
+ | Hosted API base | Enabled by default for engine features that use host API routes. | Disable hosted reads when running without the web server, or select another API base. | Web keeps the same-origin API. Extension disables hosted API calls. |
29
+ | Account/profile source | No universal profile backend; package offers account profile UI/client contracts. | Load and persist account labels, profiles, and active account for the host. | Web uses account API/store. Extension uses its local profile adapter and bundled assets. |
30
+
31
+ These defaults are runtime behavior, not a claim that every host can run without configuration: vault operations, token hydration, and chain reads require their corresponding host implementation. Keep host adapters limited to those real differences; do not copy package defaults into host code.
@@ -11,6 +11,7 @@
11
11
  // that passes renderAccountAvatar; without it the package's plain AccountAvatar is used (no click behaviour).
12
12
  'use client';
13
13
  import { jsx as _jsx } from "react/jsx-runtime";
14
+ import { panelPartClass } from '@sponsorcoin/spcoin-common/styles';
14
15
  import { useCallback } from 'react';
15
16
  import { FEED_TYPE } from '@sponsorcoin/spcoin-common/context';
16
17
  import { SP_COIN_DISPLAY } from '@sponsorcoin/spcoin-common/panels';
@@ -36,9 +37,10 @@ export default function ConnectedAgentSelectDropDown({ panelGateId, label = 'Sel
36
37
  feedType: FEED_TYPE.REMOTE_AGENT_ACCOUNTS,
37
38
  onCommit: (asset) => setAgentAccount(asset),
38
39
  selectOnLogoClick: true,
40
+ currentTokenAddress: address || undefined,
39
41
  validateSelection: (addr) => validateAccount('AGENT', addr, exchangeContext?.apiCoreSyncedMembers?.accounts ?? {}),
40
42
  }, 'AgentSelectDropDown:openAgentList', SP_COIN_DISPLAY.REMOTE_ACCOUNT_AGENT_LIST);
41
- }, [agentListVisible, openActiveListPanel, closeActiveListPanel, setAgentAccount, exchangeContext?.apiCoreSyncedMembers?.accounts]);
43
+ }, [agentListVisible, openActiveListPanel, closeActiveListPanel, setAgentAccount, exchangeContext?.apiCoreSyncedMembers?.accounts, address]);
42
44
  return (_jsx(PortableAgentSelectDropDown
43
45
  // No agent (never selected, or selection cleared) falls through to the portable component's shared Anonymous avatar.
44
46
  , {
@@ -46,6 +48,6 @@ export default function ConnectedAgentSelectDropDown({ panelGateId, label = 'Sel
46
48
  icon: agentAccount && !isUnselected
47
49
  ? (renderAccountAvatar
48
50
  ? renderAccountAvatar(agentAccount)
49
- : _jsx(AccountAvatar, { account: agentAccount, mode: SP_COIN_DISPLAY.AGENT_ACCOUNT, className: "h-full w-full object-cover", roleLabel: "AGENT" }))
51
+ : _jsx(AccountAvatar, { account: agentAccount, mode: SP_COIN_DISPLAY.AGENT_ACCOUNT, className: panelPartClass.avatarFill, roleLabel: "AGENT" }))
50
52
  : undefined, address: isUnselected ? undefined : address || undefined, symbol: isUnselected ? undefined : agentAccount?.symbol, placeholderLabel: label, onSelectClick: openAgentList, listOpen: agentListVisible, addrPrePostSize: addrPrePostSize, panelGateId: panelGateId === null ? undefined : (panelGateId ?? SP_COIN_DISPLAY.AGENT_SELECT_DROP_DOWN), panelGate: panelGateId === null ? undefined : PanelGate }));
51
53
  }
@@ -4,9 +4,10 @@
4
4
  // the account panel on click (spcoin-panels' AccountAvatarWithOpen), so a host no longer has to write the adapter.
5
5
  'use client';
6
6
  import { jsx as _jsx } from "react/jsx-runtime";
7
+ import { panelPartClass } from '@sponsorcoin/spcoin-common/styles';
7
8
  import { SP_COIN_DISPLAY } from '@sponsorcoin/spcoin-common/panels';
8
9
  import { AccountAvatarWithOpen } from '@sponsorcoin/spcoin-panels';
9
10
  import ConnectedAgentSelectDropDown from './AgentSelectDropDown';
10
11
  export default function AgentSelectDropDownWithAvatar({ panelGateId, label = 'Select Agent', addrPrePostSize = 4 }) {
11
- return (_jsx(ConnectedAgentSelectDropDown, { panelGateId: panelGateId, label: label, addrPrePostSize: addrPrePostSize, renderAccountAvatar: (account) => _jsx(AccountAvatarWithOpen, { account: account, mode: SP_COIN_DISPLAY.AGENT_ACCOUNT, className: "h-full w-full object-cover", roleLabel: "AGENT" }) }));
12
+ return (_jsx(ConnectedAgentSelectDropDown, { panelGateId: panelGateId, label: label, addrPrePostSize: addrPrePostSize, renderAccountAvatar: (account) => _jsx(AccountAvatarWithOpen, { account: account, mode: SP_COIN_DISPLAY.AGENT_ACCOUNT, className: panelPartClass.avatarFill, roleLabel: "AGENT" }) }));
12
13
  }
@@ -4,12 +4,12 @@
4
4
  // re-encrypts the vault (the extension sends merit/vault/changePassword to its worker, which checks the old password again).
5
5
  'use client';
6
6
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
+ import { formKitStyle } from '@sponsorcoin/spcoin-common/styles';
7
8
  import { useState } from 'react';
8
9
  import { MIN_WALLET_PASSWORD_LENGTH } from './session/walletSession';
9
- const BORDER = '#334155';
10
- const box = { boxSizing: 'border-box', width: '100%', padding: 12, color: '#fff', display: 'flex', flexDirection: 'column', gap: 8 };
11
- const field = { boxSizing: 'border-box', width: '100%', borderRadius: 8, border: '1px solid ' + BORDER, background: '#0E111B', padding: '8px 12px', color: '#fff', font: 'inherit', outline: 'none' };
12
- const primary = { boxSizing: 'border-box', width: '100%', borderRadius: 8, border: 0, background: '#5981F3', padding: '8px 16px', fontWeight: 600, color: '#fff', cursor: 'pointer', font: 'inherit' };
10
+ const box = formKitStyle.box(12);
11
+ const field = formKitStyle.input;
12
+ const primary = formKitStyle.primary;
13
13
  /**
14
14
  * 2026-10-11 (docs/design/accountAuthDesign.txt 14.3, item V5): `note` is the host's own small print (the web's development keystore says that every account is re-encrypted); `title` and `confirmLabel`
15
15
  * let a host keep its wording. The new password must also differ from the current one.
@@ -22,7 +22,7 @@ export default function ChangePasswordPanel({ changePassword, onDone, note, titl
22
22
  const [busy, setBusy] = useState(false);
23
23
  const [changed, setChanged] = useState(false);
24
24
  if (changed) {
25
- return (_jsxs("div", { id: "WALLET_PASSWORD_CHANGED", style: box, children: [_jsx("p", { style: { margin: 0 }, children: "Your password was changed." }), _jsx("button", { type: "button", style: primary, onClick: onDone, children: "Done" })] }));
25
+ return (_jsxs("div", { id: "WALLET_PASSWORD_CHANGED", style: box, children: [_jsx("p", { style: formKitStyle.plain, children: "Your password was changed." }), _jsx("button", { type: "button", style: primary, onClick: onDone, children: "Done" })] }));
26
26
  }
27
27
  return (_jsxs("form", { id: "WALLET_CHANGE_PASSWORD", style: box, onSubmit: (e) => {
28
28
  e.preventDefault();
@@ -38,5 +38,5 @@ export default function ChangePasswordPanel({ changePassword, onDone, note, titl
38
38
  .then(() => setChanged(true))
39
39
  .catch((err) => setError(err instanceof Error ? err.message : 'The password could not be changed.'))
40
40
  .finally(() => setBusy(false));
41
- }, children: [_jsx("p", { style: { margin: 0, fontWeight: 600 }, children: title }), _jsx("input", { type: "password", autoComplete: "current-password", placeholder: "Current password", value: oldPw, onChange: (e) => setOldPw(e.target.value), style: field }), _jsx("input", { type: "password", autoComplete: "new-password", placeholder: "New password", value: newPw, onChange: (e) => setNewPw(e.target.value), style: field }), _jsx("input", { type: "password", autoComplete: "new-password", placeholder: "Confirm new password", value: confirm, onChange: (e) => setConfirm(e.target.value), style: field }), error && _jsx("p", { style: { color: '#f87171', fontSize: 12, margin: 0 }, children: error }), _jsx("button", { type: "submit", disabled: busy || !oldPw, style: { ...primary, opacity: busy || !oldPw ? 0.6 : 1 }, children: busy ? 'Changing…' : confirmLabel }), note && _jsx("p", { style: { margin: 0, fontSize: 12, color: '#9ca3af' }, children: note })] }));
41
+ }, children: [_jsx("p", { style: formKitStyle.heading, children: title }), _jsx("input", { type: "password", autoComplete: "current-password", placeholder: "Current password", value: oldPw, onChange: (e) => setOldPw(e.target.value), style: field }), _jsx("input", { type: "password", autoComplete: "new-password", placeholder: "New password", value: newPw, onChange: (e) => setNewPw(e.target.value), style: field }), _jsx("input", { type: "password", autoComplete: "new-password", placeholder: "Confirm new password", value: confirm, onChange: (e) => setConfirm(e.target.value), style: field }), error && _jsx("p", { style: formKitStyle.error, children: error }), _jsx("button", { type: "submit", disabled: busy || !oldPw, style: formKitStyle.primaryDim(busy || !oldPw), children: busy ? 'Changing…' : confirmLabel }), note && _jsx("p", { style: formKitStyle.note, children: note })] }));
42
42
  }
@@ -11,25 +11,27 @@
11
11
  // account row is a simple avatar + "ROLE: symbol | name" + address line, since the web pill's click behaviour is web-only.
12
12
  'use client';
13
13
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
- import { SP_COIN_DISPLAY } from '@sponsorcoin/spcoin-common/panels';
15
- import { useExchangeContext } from '@sponsorcoin/spcoin-exchange-engine';
16
- import { AccountAvatar, TokenLogo } from '@sponsorcoin/spcoin-panels';
14
+ import { messageRowStyle } from '@sponsorcoin/spcoin-common/styles';
15
+ import { useSyncExternalStore } from 'react';
16
+ import { useExchangeContext, stepPanelContentStore } from '@sponsorcoin/spcoin-exchange-engine';
17
+ import { MessageAccountRow, TokenLogo } from '@sponsorcoin/spcoin-panels';
17
18
  import { MessagePanelReal } from './panels';
18
- const ROLE_TO_MODE = {
19
- SPONSOR: SP_COIN_DISPLAY.SPONSOR_ACCOUNT,
20
- RECIPIENT: SP_COIN_DISPLAY.RECIPIENT_ACCOUNT,
21
- AGENT: SP_COIN_DISPLAY.AGENT_ACCOUNT,
22
- ACCOUNT: SP_COIN_DISPLAY.ACTIVE_ACCOUNT,
23
- };
24
- function DefaultAccountRow({ entry }) {
25
- const label = [entry.account.symbol, entry.account.name].filter(Boolean).join(' | ');
26
- return (_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(AccountAvatar, { account: entry.account, mode: ROLE_TO_MODE[entry.role], roleLabel: entry.role, className: "h-8 w-8 rounded-full object-cover" }), _jsxs("span", { className: "text-sm", children: [_jsxs("span", { className: "font-semibold", children: [entry.role, ":"] }), " ", label] })] }), _jsx("div", { className: "pl-10 text-xs opacity-80 break-all", children: String(entry.account.address ?? '') }), entry.detail && _jsx("div", { className: "pl-10 text-xs opacity-80", children: entry.detail })] }));
27
- }
19
+ import { pendingApprovalMessageStore } from './approval/pendingApprovalMessageStore';
28
20
  /** The web app's MessageTokenRow, unchanged. */
29
21
  function DefaultTokenRow({ entry }) {
30
- return (_jsxs("div", { className: "flex flex-col", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(TokenLogo, { tokenContract: entry.token, className: "h-8 w-8 rounded-full object-contain" }), _jsxs("span", { className: "text-sm", children: [_jsxs("span", { className: "font-semibold", children: [entry.label, ":"] }), ' ', [entry.token.symbol, entry.token.name].filter(Boolean).join(': ')] })] }), entry.detail && _jsx("div", { className: "pl-10 text-xs opacity-80", children: entry.detail })] }));
22
+ return (_jsxs("div", { style: messageRowStyle.tokenColumn, children: [_jsxs("div", { style: messageRowStyle.line, children: [_jsx(TokenLogo, { tokenContract: entry.token, className: "", style: messageRowStyle.tokenLogo }), _jsxs("span", { style: messageRowStyle.label, children: [_jsxs("span", { style: messageRowStyle.role, children: [entry.label, ":"] }), ' ', [entry.token.symbol, entry.token.name].filter(Boolean).join(': ')] })] }), entry.detail && _jsx("div", { style: messageRowStyle.detail, children: entry.detail })] }));
31
23
  }
32
24
  export default function ConnectedMessagePanel({ renderAccountRow, renderTokenRow }) {
33
25
  const { errorMessage } = useExchangeContext();
34
- return (_jsx(MessagePanelReal, { errorMessage: errorMessage, renderAccountRow: renderAccountRow ?? ((entry, i) => _jsx(DefaultAccountRow, { entry: entry }, `${entry.role}-${i}`)), renderTokenRow: renderTokenRow ?? ((entry, i) => _jsx(DefaultTokenRow, { entry: entry }, `token-${i}`)) }));
26
+ // 2026-10-29: present only while a Merit-authorized write is waiting on a person —
27
+ // useMeritApprovalMessagePanel.ts (mounted once in MeritWallet.tsx) publishes it here rather
28
+ // than as a prop, since this component is mounted in more than one place — see that store's own
29
+ // doc comment for why.
30
+ const pendingApproval = useSyncExternalStore(pendingApprovalMessageStore.subscribe, pendingApprovalMessageStore.getSnapshot, pendingApprovalMessageStore.getServerSnapshot);
31
+ // A step-through popup published for MESSAGE_PANEL (the Swap tab's Steps flow) replaces the normal message while it is up.
32
+ const stepContent = useSyncExternalStore(stepPanelContentStore.subscribe, stepPanelContentStore.getSnapshot, stepPanelContentStore.getServerSnapshot);
33
+ // A write waiting on a person (Approve / Reject, the password) always comes first: the step popup returns once it is answered.
34
+ if (stepContent && !pendingApproval)
35
+ return _jsx("div", { style: messageRowStyle.stepContent, children: stepContent });
36
+ return (_jsx(MessagePanelReal, { errorMessage: errorMessage, renderAccountRow: renderAccountRow ?? ((entry, i) => _jsx(MessageAccountRow, { entry: entry }, `${entry.role}-${i}`)), renderTokenRow: renderTokenRow ?? ((entry, i) => _jsx(DefaultTokenRow, { entry: entry }, `token-${i}`)), pendingApproval: pendingApproval }));
35
37
  }
@@ -12,16 +12,15 @@
12
12
  // A host that passes nothing gets the registry defaults, which is what the extension uses.
13
13
  'use client';
14
14
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
15
+ import { tokenRowClass, tokenRowStyle } from '@sponsorcoin/spcoin-common/styles';
15
16
  import { useEffect, useMemo, useRef, useState } from 'react';
16
17
  import { CopyBtn } from './TokenSlotPanels';
17
18
  import { SP_COIN_DISPLAY } from '@sponsorcoin/spcoin-common/panels';
18
- import { defaultMissingImage, previewNetworkStore, useExchangeContext, usePanelVisible, usePreviewNetworkChainId, } from '@sponsorcoin/spcoin-exchange-engine';
19
+ import { defaultStaticImage, previewNetworkStore, useExchangeContext, usePanelVisible, usePreviewNetworkChainId, } from '@sponsorcoin/spcoin-exchange-engine';
19
20
  import { getNetworkConfiguration } from '@sponsorcoin/spcoin-feeds/networks';
20
21
  import { getNetworkMetaData } from '@sponsorcoin/spcoin-panels';
21
- import { ReadOnlyMetaDataTable } from './panels';
22
+ import MetaDetailScreen from './account/MetaDetailScreen';
22
23
  import { useWalletData } from './walletData';
23
- // text-sp-sm is a Tailwind token only the web app defines (var(--sp-text-sm), 1rem); an inline style gives every host the same size.
24
- const SP_TEXT_SM = { fontSize: 'var(--sp-text-sm, 1rem)' };
25
24
  /** A NetworkElement for a chain id straight from the registry (the default for previewing a chain other than the live one). */
26
25
  export function networkElementFromRegistry(chainId) {
27
26
  const cfg = getNetworkConfiguration(chainId);
@@ -55,8 +54,6 @@ export function useLiveNetworkFromContext() {
55
54
  }
56
55
  export default function ConnectedNetworkPanel({ useLiveNetwork = useLiveNetworkFromContext }) {
57
56
  const vNetworkPanel = usePanelVisible(SP_COIN_DISPLAY.NETWORK_PANEL);
58
- const vNetworkMetaData = usePanelVisible(SP_COIN_DISPLAY.NETWORK_META_DATA);
59
- const vNetworkLogo = usePanelVisible(SP_COIN_DISPLAY.NETWORK_LOGO);
60
57
  const { resolveNetwork, loadNetworkInfo } = useWalletData();
61
58
  const { network: liveNetwork, chainId: liveChainId } = useLiveNetwork();
62
59
  const previewChainId = usePreviewNetworkChainId();
@@ -100,7 +97,7 @@ export default function ConnectedNetworkPanel({ useLiveNetwork = useLiveNetworkF
100
97
  if (!vNetworkPanel)
101
98
  return null;
102
99
  if (!chainId) {
103
- return (_jsx("div", { id: "NETWORK_PANEL", children: _jsxs("div", { className: "p-4 text-slate-200 text-center", style: SP_TEXT_SM, children: [_jsx("p", { className: "mb-2 font-semibold", children: "No network selected." }), _jsx("p", { className: "m-0", children: "Select a network to view its details." })] }) }));
100
+ return (_jsx("div", { id: "NETWORK_PANEL", children: _jsxs("div", { className: tokenRowClass.empty, style: tokenRowStyle.text, children: [_jsx("p", { className: tokenRowClass.emptyTitle, children: "No network selected." }), _jsx("p", { className: tokenRowClass.emptyText, children: "Select a network to view its details." })] }) }));
104
101
  }
105
102
  const resolvedNetwork = { ...network, ...info };
106
103
  const logoURL = (resolvedNetwork.logoURL ?? '').toString().trim();
@@ -111,13 +108,11 @@ export default function ConnectedNetworkPanel({ useLiveNetwork = useLiveNetworkF
111
108
  if (f.kind === 'url') {
112
109
  return {
113
110
  label: f.label,
114
- value: (_jsxs("span", { className: "flex items-center justify-between w-full gap-2", children: [_jsx("a", { href: f.value, target: "_blank", rel: "noopener noreferrer", className: "underline decoration-slate-400/60 underline-offset-2 hover:decoration-slate-200 break-all", children: f.value }), _jsx(CopyBtn, { text: f.value })] })),
111
+ value: (_jsxs("span", { className: tokenRowClass.cell, children: [_jsx("a", { href: f.value, target: "_blank", rel: "noopener noreferrer", className: tokenRowClass.link, children: f.value }), _jsx(CopyBtn, { text: f.value })] })),
115
112
  };
116
113
  }
117
114
  return { label: f.label, value: f.value };
118
115
  });
119
- return (_jsx("div", { id: "NETWORK_PANEL", children: vNetworkMetaData && (_jsx(ReadOnlyMetaDataTable, { rows: rows, logoURL: logoURL || defaultMissingImage, logoAlt: name, logoVisible: vNetworkLogo,
120
- // Network logos are already complete, self-contained circular brand marks with real transparent padding, so no navy fill behind
121
- // them and no card frame around them (unlike token and account logos).
122
- logoBackgroundClassName: "", logoContainerClassName: "p-2" })) }));
116
+ // 2026-11-09 (owner): drawn by the shared read-only screen, the twin of the Account Details screen. Network logos are already complete, self-contained circular brand marks with real transparent padding, so no navy fill behind them.
117
+ return (_jsx(MetaDetailScreen, { screenId: "NETWORK_PANEL", logoFlag: SP_COIN_DISPLAY.NETWORK_LOGO, metaFlag: SP_COIN_DISPLAY.NETWORK_META_DATA, logoId: "NETWORK_LOGO", metaId: "NETWORK_META_DATA", logoSrc: logoURL || defaultStaticImage, logoAlt: name, toggleLabel: "Network Meta", imageKind: "network", rows: rows, plainLogo: true, fitImageShape: true }));
123
118
  }
@@ -2,26 +2,73 @@
2
2
  //
3
3
  // 2026-10-08 -- the confirmation popup for deleting the wallet from this device: it asks for the wallet password and deletes only when the host says
4
4
  // the password is right. Host-independent: the host supplies confirmDelete(password), which throws a readable message for a wrong password.
5
+ //
6
+ // 2026-10-22 (owner request): the submit button itself carries the whole state (see dialogStyle.deleteConfirmButton's own doc comment) -- blue
7
+ // "Password Required" while empty, still blue with the host's confirmLabel once something is typed (nothing here can know the password is RIGHT
8
+ // before the server says so, so typing alone never turns it green), a quick double red/blue flash on a wrong password then solid red with the
9
+ // server's own message for a few seconds before it reverts on its own, and green only once the server actually confirms the password and the
10
+ // delete succeeds. A wrong password is kept, not cleared (the same rule the login screen follows), so a typo can be fixed instead of retyped. An
11
+ // eye icon toggles the field between masked and plain text.
5
12
  'use client';
6
13
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
- import { useState } from 'react';
14
+ import { dialogStyle } from '@sponsorcoin/spcoin-common/styles';
15
+ import { Eye, EyeOff } from 'lucide-react';
16
+ import { useEffect, useState } from 'react';
17
+ import DialogPortal from './DialogPortal';
18
+ const FLASH_STEP_MS = 150;
19
+ const FLASH_STEPS = 4; // two full red/green cycles
20
+ const ERROR_SHOWN_MS = 5000; // how long the solid red state stays, after the flash settles
8
21
  /**
9
22
  * 2026-10-11 (docs/design/accountAuthDesign.txt 14.3, item V5): `warning` carries the host's wording for what is lost (the default says the wallet is removed from this device); `title` and `confirmLabel` keep a
10
23
  * host's own words (the web's development keystore says "Remove Account"); `footnote` is optional small print under the buttons.
11
24
  */
12
25
  export default function DeleteWalletDialog({ confirmDelete, onClose, warning, title = 'Delete wallet', confirmLabel = 'Delete wallet', footnote }) {
13
26
  const [password, setPassword] = useState('');
27
+ const [visible, setVisible] = useState(false);
14
28
  const [error, setError] = useState('');
29
+ const [errorNonce, setErrorNonce] = useState(0);
30
+ const [flashOn, setFlashOn] = useState(false);
31
+ const [errorSettled, setErrorSettled] = useState(false);
15
32
  const [busy, setBusy] = useState(false);
16
- return (_jsx("div", { id: "DELETE_WALLET_DIALOG", role: "dialog", "aria-modal": "true", style: { position: 'fixed', inset: 0, zIndex: 1000, background: 'rgba(0,0,0,0.6)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 12 }, children: _jsxs("form", { style: { boxSizing: 'border-box', width: '100%', maxWidth: 340, background: '#0E111B', border: '1px solid #7f1d1d', borderRadius: 12, padding: 16, color: '#fff', display: 'flex', flexDirection: 'column', gap: 8 }, onSubmit: (e) => {
17
- e.preventDefault();
18
- setBusy(true);
19
- setError('');
20
- confirmDelete(password)
21
- .catch((err) => {
22
- setError(err instanceof Error ? err.message : 'The wallet could not be deleted.');
23
- setPassword('');
24
- })
25
- .finally(() => setBusy(false));
26
- }, children: [_jsx("p", { style: { margin: 0, fontWeight: 700 }, children: title }), _jsx("p", { style: { margin: 0, fontSize: 12, color: '#fbbf24' }, children: warning ?? 'This deletes the wallet and all its accounts from this device. You can only get them back with your Secret Recovery Phrase (imported accounts need their private keys).' }), _jsx("input", { type: "password", autoFocus: true, autoComplete: "current-password", placeholder: "Enter your password to confirm", value: password, onChange: (e) => setPassword(e.target.value), style: { boxSizing: 'border-box', width: '100%', borderRadius: 8, border: '1px solid #334155', background: '#0b0e17', padding: '8px 12px', color: '#fff', font: 'inherit', outline: 'none' } }), error && _jsx("p", { style: { margin: 0, fontSize: 12, color: '#f87171' }, children: error }), _jsx("button", { type: "submit", disabled: busy || !password, style: { borderRadius: 8, border: 0, background: '#b91c1c', color: '#fff', padding: '8px 16px', fontWeight: 600, cursor: 'pointer', font: 'inherit', opacity: busy || !password ? 0.6 : 1 }, children: busy ? 'Deleting…' : confirmLabel }), _jsx("button", { type: "button", onClick: onClose, style: { borderRadius: 8, border: '1px solid #334155', background: 'transparent', color: '#fff', padding: '8px 16px', cursor: 'pointer', font: 'inherit' }, children: "Cancel" }), footnote && _jsx("p", { style: { margin: 0, fontSize: 12, color: '#9ca3af' }, children: footnote })] }) }));
33
+ // only the server knows whether the password is right: green appears here, and only here, once confirmDelete actually resolves.
34
+ const [success, setSuccess] = useState(false);
35
+ // one failed submit runs the flash, then the solid error, then clears itself -- a SECOND failure (even with the identical message) must restart the
36
+ // whole sequence, which is why this keys off a nonce rather than the error text (two equal strings would not re-trigger a dependent effect).
37
+ useEffect(() => {
38
+ if (errorNonce === 0)
39
+ return;
40
+ setErrorSettled(false);
41
+ setFlashOn(true);
42
+ let step = 1;
43
+ const flashTimer = setInterval(() => {
44
+ step += 1;
45
+ setFlashOn((v) => !v);
46
+ if (step >= FLASH_STEPS) {
47
+ clearInterval(flashTimer);
48
+ setErrorSettled(true);
49
+ }
50
+ }, FLASH_STEP_MS);
51
+ const revertTimer = setTimeout(() => {
52
+ setError('');
53
+ setErrorSettled(false);
54
+ }, FLASH_STEPS * FLASH_STEP_MS + ERROR_SHOWN_MS);
55
+ return () => {
56
+ clearInterval(flashTimer);
57
+ clearTimeout(revertTimer);
58
+ };
59
+ }, [errorNonce]);
60
+ const variant = success ? 'success' : error ? (errorSettled ? 'error' : 'flash') : password ? 'typed' : 'empty';
61
+ const label = success ? 'Deleted' : busy ? 'Deleting…' : error ? error : password ? confirmLabel : 'Password Required';
62
+ return (_jsx(DialogPortal, { children: _jsx("div", { id: "DELETE_WALLET_DIALOG", role: "dialog", "aria-modal": "true", style: dialogStyle.backdrop, children: _jsxs("form", { style: dialogStyle.formCardDanger, onSubmit: (e) => {
63
+ e.preventDefault();
64
+ setBusy(true);
65
+ setError('');
66
+ confirmDelete(password)
67
+ .then(() => setSuccess(true))
68
+ .catch((err) => {
69
+ setError(err instanceof Error ? err.message : 'The wallet could not be deleted.');
70
+ setErrorNonce((n) => n + 1);
71
+ })
72
+ .finally(() => setBusy(false));
73
+ }, children: [_jsx("p", { style: dialogStyle.title, children: title }), _jsx("p", { style: dialogStyle.warning, children: warning ?? 'This deletes the wallet and all its accounts from this device. You can only get them back with your Secret Recovery Phrase (imported accounts need their private keys).' }), _jsxs("div", { style: dialogStyle.inputWrap, children: [_jsx("input", { type: visible ? 'text' : 'password', autoFocus: true, autoComplete: "current-password", placeholder: "Enter your password to confirm", value: password, onChange: (e) => setPassword(e.target.value), style: dialogStyle.input }), _jsx("button", { type: "button", onClick: () => setVisible((v) => !v), "aria-label": visible ? 'Hide password' : 'Show password', title: visible ? 'Hide password' : 'Show password', style: dialogStyle.eyeButton, children: visible ? _jsx(EyeOff, { size: 16 }) : _jsx(Eye, { size: 16 }) })] }), _jsx("button", { type: "submit", disabled: busy || !password, style: dialogStyle.deleteConfirmButton(variant, flashOn, busy), children: label }), _jsx("button", { type: "button", onClick: onClose, style: dialogStyle.cancelButton, children: "Cancel" }), footnote && _jsx("p", { style: dialogStyle.footnote, children: footnote })] }) }) }));
27
74
  }
@@ -0,0 +1,3 @@
1
+ export default function DialogPortal({ children }: {
2
+ children: React.ReactNode;
3
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,19 @@
1
+ // File: node_source/meritWallet/packages/@sponsorcoin/merit-wallet/src/DialogPortal.tsx
2
+ //
3
+ // 2026-10-22 (owner report: "the Delete Permanently button does not work"). Every full-screen confirmation dialog (DeleteWalletDialog, PasswordPromptDialog, the Reset Password card in
4
+ // WalletSessionDialogs) is `position: fixed; inset: 0` drawn deep inside MeritWallet's own tree. The web app's floating wallet positions itself with a CSS `transform` (SpCoinWalletPopup.tsx's
5
+ // drag positioning) — a plain CSS rule then reparents `position: fixed` to THAT transformed ancestor instead of the real viewport, so the dialog's "full screen" backdrop no longer actually
6
+ // covers the screen, and the Config tab's own scrollable content painted OVER it, catching every click meant for the dialog's buttons (confirmed with document.elementFromPoint at the button's
7
+ // own pixel: it resolved to the Config panel, never the dialog, even on a forced click). Escaping to the platform's portal target (platform/platform.ts's portalContainer — document.body for
8
+ // every real DOM host) fixes it for every wallet layout mode and every host; this file itself touches no device API directly (test/platformBoundary.test.mjs), only the platform interface.
9
+ 'use client';
10
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
11
+ import { createPortal } from 'react-dom';
12
+ import { usePlatform } from './platform/platform';
13
+ export default function DialogPortal({ children }) {
14
+ const container = usePlatform().portalContainer();
15
+ // No portal target (a host with no such nesting problem, or none supplied): render in place, same as before this fix existed.
16
+ if (!container)
17
+ return _jsx(_Fragment, { children: children });
18
+ return createPortal(children, container);
19
+ }
@@ -1,39 +1,32 @@
1
1
  import React from 'react';
2
- import { SP_COIN_DISPLAY } from '@sponsorcoin/spcoin-common/panels';
2
+ import { type PeerConnection } from './peer/peerConnection';
3
3
  import type { APP_TYPE } from '@sponsorcoin/spcoin-common';
4
- import { type WalletListSelectionSlot } from '@sponsorcoin/spcoin-exchange-engine';
5
- import { type AssetListEntry, type AccountListGroup, type FetchBalance, type OpenTarget, type MeritWalletPasswordMode as ConfigPasswordMode, type ApplicationSyncMode, type MeritWalletLocation, type MeritExtensionChannel } from '@sponsorcoin/spcoin-panels';
4
+ import { type AssetListEntry, type AccountListGroup, type AccountListRemoteSelection, type FetchBalance, type OpenTarget, type MeritWalletPasswordMode as ConfigPasswordMode, type ApplicationSyncMode, type MeritWalletLocation, type MeritExtensionChannel } from '@sponsorcoin/spcoin-panels';
6
5
  import type { Wallet } from './wallet/wallet';
7
6
  import { type MeritPlatform } from './platform/platform';
8
7
  import { type WalletDerivation } from './wallet/walletProps';
9
8
  import type { ApproverHost } from './wallet/approverAccounts';
10
9
  import type { IconCacheStorage } from '@sponsorcoin/spcoin-feeds/shared';
11
10
  import { type MenuTabKey, type MeritWalletNetworkRow } from './panels';
12
- import { type RewardsHost } from './rewards/ConnectedRewardsPanel';
11
+ import { type ApprovalHost } from './approval/useMeritApprovalMessagePanel';
12
+ import { type RewardsHost, type RewardsPanelExtras } from './rewards/ConnectedRewardsPanel';
13
13
  import { type AddAccountHost } from './account/AddAccountFlow';
14
14
  import { type TestAccountsApi } from './TestAccountsSection';
15
+ import { type TradeHost } from './trade/tradeHost';
15
16
  import type { WalletSessionHost } from './walletSessionHost';
16
17
  import { type AccountDetailHost } from './account/AccountDetailScreen';
18
+ import { type SecuritySetupHost } from './SecuritySetupScreen';
19
+ import { type WalletOnboardingPanelProps } from './WalletOnboardingPanel';
17
20
  import { type SponsorStakingHost } from './sponsor/ConnectedSponsorStakingList';
18
21
  import { type HostTransactionResult } from './receipt/transactionReceipts';
19
- import { type AssetEntryKind, type AssetEntryResult } from '@sponsorcoin/spcoin-exchange-engine';
20
22
  import type { AuthenticationType } from './auth/authenticationType';
21
- import { type SwapTradeButtonHost } from './swap/SwapTradeButton';
22
- type ActiveListMode = WalletListSelectionSlot | null;
23
23
  export type { MeritWalletNetworkRow };
24
24
  export interface MeritWalletProps {
25
- /**
26
- * 2026-10-05 — resolves an address typed into a list's ADDRESS_PANEL that is NOT in the
27
- * rows already loaded (on-chain token lookup / account hydration). The host builds it
28
- * from the engine's resolveAssetEntry with its own RPC client; omit it and such an
29
- * address just shows "not in this list". `peerAddress` is the other side's token (duplicate guard).
30
- */
31
- resolveAssetAddress?: (address: string, kind: AssetEntryKind, peerAddress?: string) => Promise<AssetEntryResult>;
32
25
  /** Docked (full-height, square corners, no right border — a split-pane
33
26
  * layout) vs. floating/dialog (rounded corners, capped height). Default
34
27
  * false. */
35
28
  docked?: boolean;
36
- /** Drops the web app's 364px cap in favor of a plain 100% width — for a
29
+ /** Drops the web app's 380px cap in favor of a plain 100% width — for a
37
30
  * consumer (like a Chrome side panel) that's already narrow and
38
31
  * user-resizable rather than floating inside a wider page. Default
39
32
  * false. */
@@ -75,9 +68,10 @@ export interface MeritWalletProps {
75
68
  networkRows?: MeritWalletNetworkRow[];
76
69
  accountGroups?: AccountListGroup[];
77
70
  tokenRows?: AssetListEntry[];
78
- recipientRows?: AssetListEntry[];
79
71
  /** 2026-10-09 -- turns the Rewards tab live (Trading / Staked / Pending with estimate and claim, Auto Refresh), the same card and data hook the web app runs. Omit for the static table. */
80
72
  rewardsHost?: RewardsHost;
73
+ /** What the host adds around the Rewards card: a shared Auto Refresh store, the Deposit Account row's starting address, a refresh registration hook and a trace sink (all data or functions). */
74
+ rewardsExtras?: RewardsPanelExtras;
81
75
  /**
82
76
  * 2026-10-09 -- turns the Sponsored Recipient Accounts list (opened from Rewards > Unstake) live, with Un-Stake: the web app's card and confirm popup over the host's
83
77
  * chain reads and one send-an-unstake-transaction function.
@@ -99,11 +93,6 @@ export interface MeritWalletProps {
99
93
  onAccountIconClick?: (address: string) => void;
100
94
  /** 2026-10-08 (table row 21): the "Add a Wallet/Account" button at the foot of the account list. Omit it and the list shows the button inert, as before. */
101
95
  onAddAccount?: () => void;
102
- /**
103
- * 2026-10-08 (table row 22): lets a host without the web trading pipeline (the extension) get a working Swap tab. When set and the host passes no
104
- * zeroXTradeButtonContent / uniSelectContent of its own, the 0x trade button and the Uniswap section are the package's (price + swap through this host).
105
- */
106
- swapHost?: SwapTradeButtonHost;
107
96
  /** Data to render in the account-details overlay once
108
97
  * onAccountIconClick's caller has resolved it. `address` gates which
109
98
  * click this answers — this component shows a loading state for any
@@ -128,17 +117,14 @@ export interface MeritWalletProps {
128
117
  accountDetailHost?: AccountDetailHost;
129
118
  /** 2026-10-10 (docs/design/accountAuthDesign.txt section 17 item 2): what the shared Add a Wallet/Account flow does for this host. When given, the list's button opens the shared flow; otherwise onAddAccount is called as before. */
130
119
  addAccountHost?: AddAccountHost;
131
- onTokenIconClick?: (address: string) => void;
132
- tokenDetail?: {
133
- address: string;
134
- logoSrc?: string;
135
- name?: string;
136
- symbol?: string;
137
- decimals?: number;
138
- website?: string;
139
- explorer?: string;
140
- description?: string;
141
- } | null;
120
+ /** 2026-10-31 (owner design): the account list was opened BY A REMOTE (a site asking the wallet to connect). The list opens by itself, titled "Remote <caller> Account Selector", every row has a Connect / Connected button and the footer is Done / Cancel. */
121
+ remoteSelection?: AccountListRemoteSelection;
122
+ /** 2026-11-03 (owner request): a request the person must answer (connect this site, sign, send) is VALIDATE_REMOTE_CONNECT, a page inside the wallet's own chrome (header, title bar, tabs). The host supplies the page and what its back arrow does (refuse); the component opens and closes the panel with it. */
123
+ approvalPage?: {
124
+ title: string;
125
+ element: React.ReactNode;
126
+ onCancel: () => void;
127
+ };
142
128
  onNetworkIconClick?: (networkId: string) => void;
143
129
  sendAmount?: string;
144
130
  onSendAmountChange?: (value: string) => void;
@@ -176,6 +162,10 @@ export interface MeritWalletProps {
176
162
  * installed from, how an approver is unlocked. Passing it asks for the derived list: it replaces the host's own accountGroups / onAccountRowSelect once the list has loaded (they stay as the fallback until then).
177
163
  */
178
164
  approverHost?: ApproverHost;
165
+ /** 2026-11-01: the wallets this one is paired with (the other Merit wallet, a phone wallet app), by authenticator id, each a PeerConnection the host built from its own transport (src/peer/peerConnection.ts). A peer's Connected is its `connected` (reachable and logged on); a wallet with no peer keeps the approverHost's rule. */
166
+ peers?: Record<string, PeerConnection>;
167
+ /** 2026-11-07 (owner request): one connection hook per REMOTE wallet, by authenticator id (web: useMeritExtension and useMetaMask; extension: useMeritWebApp and useMetaMask), each returning true while that wallet is connected. The component calls them every render in the object's key order, so the host passes ONE constant object (never built per render). A wallet's group in the account list reads Disconnected (red; Connect on hover) while its hook says false, and Add Account(s) while it says true. */
168
+ walletLinkHooks?: Record<string, () => boolean>;
179
169
  /** The banner above the account list (a failed connect). Derived when `approverHost` is given; a host may also set it. */
180
170
  accountListNotice?: string;
181
171
  /** The wallet that signs for the active account (derived from `wallet` when the host does not pass it), shown as the account header's tooltip. */
@@ -187,24 +177,24 @@ export interface MeritWalletProps {
187
177
  /** Which wallets are connected to this app, by wallet asset folder (merit, meritExtension, metamask, ledger, trezor): the wallet list's Connected / Disconnected buttons. Derived from `approverHost` when the host does not pass it. */
188
178
  walletConnection?: Record<string, boolean>;
189
179
  /** Disconnect a wallet of the wallet list, by asset folder. Derived from `approverHost` when the host does not pass it. */
190
- onDisconnectWallet?: (slug: string) => void;
180
+ onDisconnectWallet?: (slug: string) => void | Promise<unknown>;
191
181
  /** Show the wallet pill (WalletSelectDropDown) just under the tabs: its down chevron opens the wallet list (REMOTE_WALLET_LIST), its icon opens the active wallet's panel (WALLET_PANEL). Default true. */
192
182
  showWalletSelect?: boolean;
193
183
  /** Where the wallet asset files (public/assets/wallets) are served from: this page's own origin by default; a host on another origin (the extension) passes the web app's address. */
194
184
  walletAssetBaseUrl?: string;
185
+ /** 2026-11-03 (owner request: the same for every app that uses this component): the address of the account service, the one server the wallet calls to sign in and publish an account profile (any server that implements /api/spCoin/auth and /api/spCoin/accounts). Every host injects it the same way; none is assumed. Omitted or empty: no call is made and Publish Account says so. */
186
+ accountServiceUrl?: string;
195
187
  /** What the component may derive from `wallet` (default: everything). A host that has its own lock gate (the web app) leaves out 'lock' and 'logoff'. */
196
188
  walletDerives?: readonly WalletDerivation[];
197
- /** 2026-10-11 (plan row 13): a host that mounts the shared Sponsor panel (spcoin-panels' SponsorPanelController, driven by the exchange context) passes it here, and it replaces this component's own Sponsor tab. */
198
- sponsorPanel?: React.ReactNode;
199
- /** 2026-10-11 (plan row 13): the host's list overlay (spcoin-panels' ActiveListPanelHost) for the lists the shared panels open through the panel tree; shown in place of the tab body while a panel-tree list is open. */
200
- listHost?: React.ReactNode;
189
+ /** The trade screens' host data and functions (the Swap tab and the Sponsor tab trade through one path): the trading provider, a trace sink, how a recipient's site opens and one hook for the confirm popup's state. The component draws the screens. */
190
+ tradeHost?: TradeHost;
201
191
  manageRadioPanels?: boolean;
202
- overlayHost?: React.ReactNode;
203
- agentSelectSlot?: React.ReactNode;
204
192
  fetchBalance?: FetchBalance;
205
- listPanelIdOverrides?: Partial<Record<Exclude<ActiveListMode, null>, SP_COIN_DISPLAY>>;
206
193
  passwordMode?: 'checking' | 'setup' | 'unlock';
207
- passwordIcon?: React.ReactNode;
194
+ /** The artwork of the login page: an image URL (the data); the component draws it. */
195
+ passwordIconSrc?: string;
196
+ /** How the wallet answers a write's approval: the host checks the password (`unlock`) and may gate a debug step. The component draws the approval screen itself. */
197
+ approvalHost?: ApprovalHost;
208
198
  /**
209
199
  * 2026-10-08 (table row 23): the wallet is locked (or has no wallet yet), so show the password screen in place of everything else, whatever the panel tree says.
210
200
  * The lock gate lives here, in the component; whether and where the wallet is shown at all stays with the host (a layout gate).
@@ -223,6 +213,14 @@ export interface MeritWalletProps {
223
213
  */
224
214
  recoveryPhrase?: string;
225
215
  onRecoveryPhraseSaved?: () => void;
216
+ /** 2026-11-13 (owner: optional MFA after the phrase): when set, the phrase screen's Continue opens the optional "Protect your wallet" screen (authenticator app / passkey) before onRecoveryPhraseSaved fires; Skip is always there. Omitted: no such step. */
217
+ securitySetup?: SecuritySetupHost;
218
+ /**
219
+ * 2026-10-19 (docs/meritMigration.txt rows 27 / 28: a host passes data and functions, it never draws a wallet screen): first-run setup. While this is set the component draws the
220
+ * "Set up your wallet" screen (create a wallet, import by recovery phrase, restore a backup) INSTEAD of the wallet, and the host only supplies what the screen calls. The host clears it
221
+ * (sets undefined) when onDone fires and the vault exists.
222
+ */
223
+ onboarding?: WalletOnboardingPanelProps;
226
224
  sellBalanceText?: string;
227
225
  buyBalanceText?: string;
228
226
  /** 2026-10-05, on request — the 0X engine's trade/submit
@@ -237,11 +235,11 @@ export interface MeritWalletProps {
237
235
  * additionally gates this slot on its own panel-tree node,
238
236
  * so this component adds no visibility check of its own
239
237
  * for it either). */
240
- zeroXTradeButtonContent?: React.ReactNode;
241
- uniSelectContent?: React.ReactNode;
242
238
  configPasswordMode?: ConfigPasswordMode;
243
239
  onConfigPasswordModeChange?: (mode: ConfigPasswordMode) => void;
244
- configPersistedTimeoutContent?: React.ReactNode;
240
+ /** The persisted-password time limit in hours (the data) and what a change does (the action): the component draws the Time Limit picker itself. Omit both and there is no picker. */
241
+ configPersistedTimeoutHours?: number;
242
+ onConfigPersistedTimeoutHoursChange?: (hours: number) => void;
245
243
  configPasswordDescription?: string;
246
244
  configMandatorySecurity?: boolean;
247
245
  onConfigMandatorySecurityChange?: (mandatory: boolean) => void;
@@ -279,6 +277,8 @@ export interface MeritWalletProps {
279
277
  configZeroXEngineVisible?: boolean;
280
278
  onConfig0xEngineChange?: (checked: boolean) => void;
281
279
  configResetPanelsContent?: React.ReactNode;
280
+ /** A host-supplied section shown last on the Config tab (the web app's dev-only shared-libraries sync/build control); the extension supplies none. */
281
+ configDevToolsContent?: React.ReactNode;
282
282
  configExtensionChannel?: MeritExtensionChannel;
283
283
  onConfigExtensionChannelChange?: (channel: MeritExtensionChannel) => void;
284
284
  configExtensionDownloadPath?: string;
@@ -292,7 +292,7 @@ export interface MeritWalletProps {
292
292
  walletSession?: WalletSessionHost;
293
293
  }
294
294
  /**
295
- * The component. A host that passes `wallet` gets the lock screen, the lock button, the authentication type and the Wallet Security section from it (src/wallet/walletProps.tsx); everything else, and every
296
- * prop the host passes explicitly, is unchanged.
295
+ * The component. First-run setup replaces the wallet (a different component type, so the wallet's own state starts fresh when setup ends, the same as when the host used to swap it out);
296
+ * otherwise the wired wallet. The setup screen runs inside the same platform context (clipboard) as the wallet.
297
297
  */
298
- export default function MeritWallet(props: MeritWalletProps): React.FunctionComponentElement<React.ProviderProps<MeritPlatform>>;
298
+ export default function MeritWallet(props: MeritWalletProps): React.FunctionComponentElement<MeritWalletProps> | React.FunctionComponentElement<React.ProviderProps<MeritPlatform>>;