@mysten-incubation/dev-wallet 0.0.1 → 0.2.0

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 (182) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/LICENSE +189 -0
  3. package/dist/_virtual/{_@oxc-project_runtime@0.107.0 → _@oxc-project_runtime@0.127.0}/helpers/decorate.mjs +2 -3
  4. package/dist/adapters/base-adapter.mjs +6 -7
  5. package/dist/adapters/base-adapter.mjs.map +1 -1
  6. package/dist/adapters/browser.d.mts +4 -1
  7. package/dist/adapters/browser.mjs +4 -2
  8. package/dist/adapters/build-managed-account.mjs +8 -10
  9. package/dist/adapters/build-managed-account.mjs.map +1 -1
  10. package/dist/adapters/devstack-adapter.d.mts +111 -0
  11. package/dist/adapters/devstack-adapter.d.mts.map +1 -0
  12. package/dist/adapters/devstack-adapter.mjs +224 -0
  13. package/dist/adapters/devstack-adapter.mjs.map +1 -0
  14. package/dist/adapters/devstack-paths.d.mts +12 -0
  15. package/dist/adapters/devstack-paths.d.mts.map +1 -0
  16. package/dist/adapters/devstack-paths.mjs +12 -0
  17. package/dist/adapters/devstack-paths.mjs.map +1 -0
  18. package/dist/adapters/fork-relay.d.mts +109 -0
  19. package/dist/adapters/fork-relay.d.mts.map +1 -0
  20. package/dist/adapters/fork-relay.mjs +208 -0
  21. package/dist/adapters/fork-relay.mjs.map +1 -0
  22. package/dist/adapters/idb-store.mjs +12 -16
  23. package/dist/adapters/idb-store.mjs.map +1 -1
  24. package/dist/adapters/in-memory-adapter.mjs +4 -10
  25. package/dist/adapters/in-memory-adapter.mjs.map +1 -1
  26. package/dist/adapters/passkey-adapter.mjs +4 -12
  27. package/dist/adapters/passkey-adapter.mjs.map +1 -1
  28. package/dist/adapters/remote-cli-adapter.mjs +37 -72
  29. package/dist/adapters/remote-cli-adapter.mjs.map +1 -1
  30. package/dist/adapters/webcrypto-adapter.mjs +2 -4
  31. package/dist/adapters/webcrypto-adapter.mjs.map +1 -1
  32. package/dist/bin/cli.mjs +9 -22
  33. package/dist/bin/cli.mjs.map +1 -1
  34. package/dist/client/dev-wallet-client.mjs +13 -28
  35. package/dist/client/dev-wallet-client.mjs.map +1 -1
  36. package/dist/client/index.mjs +1 -2
  37. package/dist/client/request-handler.mjs +36 -42
  38. package/dist/client/request-handler.mjs.map +1 -1
  39. package/dist/index.mjs +1 -2
  40. package/dist/react/components.d.mts +9 -9
  41. package/dist/react/components.d.mts.map +1 -1
  42. package/dist/react/components.mjs +2 -5
  43. package/dist/react/components.mjs.map +1 -1
  44. package/dist/react/context.d.mts +3 -3
  45. package/dist/react/context.d.mts.map +1 -1
  46. package/dist/react/context.mjs +3 -8
  47. package/dist/react/context.mjs.map +1 -1
  48. package/dist/react/index.mjs +1 -2
  49. package/dist/react/useDevWallet.d.mts +3 -0
  50. package/dist/react/useDevWallet.d.mts.map +1 -1
  51. package/dist/react/useDevWallet.mjs +9 -14
  52. package/dist/react/useDevWallet.mjs.map +1 -1
  53. package/dist/server/cli-signing-middleware.mjs +7 -20
  54. package/dist/server/cli-signing-middleware.mjs.map +1 -1
  55. package/dist/server/index.mjs +1 -2
  56. package/dist/standalone/assets/{in-memory-adapter-CBF4h2KD.js → in-memory-adapter-qRkNfj9O.js} +1 -1
  57. package/dist/standalone/assets/main-DDltQnzR.js +3954 -0
  58. package/dist/standalone/assets/{webcrypto-adapter-DysAubFb.js → webcrypto-adapter-FejcrL9x.js} +1 -1
  59. package/dist/standalone/index.html +11 -12
  60. package/dist/ui/copy-controller.mjs +3 -3
  61. package/dist/ui/copy-controller.mjs.map +1 -1
  62. package/dist/ui/dev-wallet-account-selector.d.mts +3 -4
  63. package/dist/ui/dev-wallet-account-selector.d.mts.map +1 -1
  64. package/dist/ui/dev-wallet-account-selector.mjs +48 -25
  65. package/dist/ui/dev-wallet-account-selector.mjs.map +1 -1
  66. package/dist/ui/dev-wallet-accounts.d.mts +9 -4
  67. package/dist/ui/dev-wallet-accounts.d.mts.map +1 -1
  68. package/dist/ui/dev-wallet-accounts.mjs +62 -140
  69. package/dist/ui/dev-wallet-accounts.mjs.map +1 -1
  70. package/dist/ui/dev-wallet-balances.d.mts +10 -3
  71. package/dist/ui/dev-wallet-balances.d.mts.map +1 -1
  72. package/dist/ui/dev-wallet-balances.mjs +241 -43
  73. package/dist/ui/dev-wallet-balances.mjs.map +1 -1
  74. package/dist/ui/dev-wallet-connect.d.mts +3 -3
  75. package/dist/ui/dev-wallet-connect.d.mts.map +1 -1
  76. package/dist/ui/dev-wallet-connect.mjs +24 -16
  77. package/dist/ui/dev-wallet-connect.mjs.map +1 -1
  78. package/dist/ui/dev-wallet-dropdown.d.mts +3 -3
  79. package/dist/ui/dev-wallet-dropdown.d.mts.map +1 -1
  80. package/dist/ui/dev-wallet-dropdown.mjs +37 -47
  81. package/dist/ui/dev-wallet-dropdown.mjs.map +1 -1
  82. package/dist/ui/dev-wallet-fork-panel.d.mts +43 -0
  83. package/dist/ui/dev-wallet-fork-panel.d.mts.map +1 -0
  84. package/dist/ui/dev-wallet-fork-panel.mjs +500 -0
  85. package/dist/ui/dev-wallet-fork-panel.mjs.map +1 -0
  86. package/dist/ui/dev-wallet-network-badge.d.mts +3 -4
  87. package/dist/ui/dev-wallet-network-badge.d.mts.map +1 -1
  88. package/dist/ui/dev-wallet-network-badge.mjs +52 -21
  89. package/dist/ui/dev-wallet-network-badge.mjs.map +1 -1
  90. package/dist/ui/dev-wallet-new-account.d.mts +3 -4
  91. package/dist/ui/dev-wallet-new-account.d.mts.map +1 -1
  92. package/dist/ui/dev-wallet-new-account.mjs +31 -105
  93. package/dist/ui/dev-wallet-new-account.mjs.map +1 -1
  94. package/dist/ui/dev-wallet-objects.d.mts +3 -3
  95. package/dist/ui/dev-wallet-objects.d.mts.map +1 -1
  96. package/dist/ui/dev-wallet-objects.mjs +76 -39
  97. package/dist/ui/dev-wallet-objects.mjs.map +1 -1
  98. package/dist/ui/dev-wallet-panel.d.mts +24 -4
  99. package/dist/ui/dev-wallet-panel.d.mts.map +1 -1
  100. package/dist/ui/dev-wallet-panel.mjs +247 -64
  101. package/dist/ui/dev-wallet-panel.mjs.map +1 -1
  102. package/dist/ui/dev-wallet-popup.d.mts +3 -5
  103. package/dist/ui/dev-wallet-popup.d.mts.map +1 -1
  104. package/dist/ui/dev-wallet-popup.mjs +19 -15
  105. package/dist/ui/dev-wallet-popup.mjs.map +1 -1
  106. package/dist/ui/dev-wallet-settings.d.mts +3 -4
  107. package/dist/ui/dev-wallet-settings.d.mts.map +1 -1
  108. package/dist/ui/dev-wallet-settings.mjs +149 -97
  109. package/dist/ui/dev-wallet-settings.mjs.map +1 -1
  110. package/dist/ui/dev-wallet-signing-modal.d.mts +11 -4
  111. package/dist/ui/dev-wallet-signing-modal.d.mts.map +1 -1
  112. package/dist/ui/dev-wallet-signing-modal.mjs +46 -18
  113. package/dist/ui/dev-wallet-signing-modal.mjs.map +1 -1
  114. package/dist/ui/dev-wallet-signing.d.mts +9 -3
  115. package/dist/ui/dev-wallet-signing.d.mts.map +1 -1
  116. package/dist/ui/dev-wallet-signing.mjs +119 -130
  117. package/dist/ui/dev-wallet-signing.mjs.map +1 -1
  118. package/dist/ui/dev-wallet-standalone.d.mts +8 -3
  119. package/dist/ui/dev-wallet-standalone.d.mts.map +1 -1
  120. package/dist/ui/dev-wallet-standalone.mjs +303 -38
  121. package/dist/ui/dev-wallet-standalone.mjs.map +1 -1
  122. package/dist/ui/dev-wallet-tab-bar.d.mts +13 -5
  123. package/dist/ui/dev-wallet-tab-bar.d.mts.map +1 -1
  124. package/dist/ui/dev-wallet-tab-bar.mjs +143 -82
  125. package/dist/ui/dev-wallet-tab-bar.mjs.map +1 -1
  126. package/dist/ui/index.d.mts +3 -2
  127. package/dist/ui/index.mjs +2 -2
  128. package/dist/ui/mount.d.mts +14 -1
  129. package/dist/ui/mount.d.mts.map +1 -1
  130. package/dist/ui/mount.mjs +5 -2
  131. package/dist/ui/mount.mjs.map +1 -1
  132. package/dist/ui/styles.mjs +428 -66
  133. package/dist/ui/styles.mjs.map +1 -1
  134. package/dist/ui/transaction-analyzer.mjs +19 -30
  135. package/dist/ui/transaction-analyzer.mjs.map +1 -1
  136. package/dist/ui/utils.d.mts +59 -0
  137. package/dist/ui/utils.d.mts.map +1 -0
  138. package/dist/ui/utils.mjs +42 -15
  139. package/dist/ui/utils.mjs.map +1 -1
  140. package/dist/ui/wallet-controller.mjs +89 -20
  141. package/dist/ui/wallet-controller.mjs.map +1 -1
  142. package/dist/wallet/constants.d.mts +9 -0
  143. package/dist/wallet/constants.d.mts.map +1 -0
  144. package/dist/wallet/constants.mjs +1 -1
  145. package/dist/wallet/dev-wallet.mjs +42 -99
  146. package/dist/wallet/dev-wallet.mjs.map +1 -1
  147. package/dist/wallet/initializer.d.mts +3 -0
  148. package/dist/wallet/initializer.d.mts.map +1 -1
  149. package/dist/wallet/initializer.mjs +9 -14
  150. package/dist/wallet/initializer.mjs.map +1 -1
  151. package/dist/wallet/signing.mjs +8 -21
  152. package/dist/wallet/signing.mjs.map +1 -1
  153. package/package.json +8 -6
  154. package/src/adapters/browser.ts +21 -0
  155. package/src/adapters/devstack-adapter.ts +342 -0
  156. package/src/adapters/devstack-paths.ts +16 -0
  157. package/src/adapters/fork-relay.ts +321 -0
  158. package/src/app/index.html +10 -11
  159. package/src/react/useDevWallet.ts +5 -1
  160. package/src/ui/dev-wallet-account-selector.ts +44 -17
  161. package/src/ui/dev-wallet-accounts.ts +87 -124
  162. package/src/ui/dev-wallet-balances.ts +248 -27
  163. package/src/ui/dev-wallet-connect.ts +19 -8
  164. package/src/ui/dev-wallet-dropdown.ts +7 -5
  165. package/src/ui/dev-wallet-fork-panel.ts +591 -0
  166. package/src/ui/dev-wallet-network-badge.ts +49 -10
  167. package/src/ui/dev-wallet-new-account.ts +21 -62
  168. package/src/ui/dev-wallet-objects.ts +59 -19
  169. package/src/ui/dev-wallet-panel.ts +261 -42
  170. package/src/ui/dev-wallet-popup.ts +12 -6
  171. package/src/ui/dev-wallet-settings.ts +159 -94
  172. package/src/ui/dev-wallet-signing-modal.ts +47 -7
  173. package/src/ui/dev-wallet-signing.ts +124 -97
  174. package/src/ui/dev-wallet-standalone.ts +310 -22
  175. package/src/ui/dev-wallet-tab-bar.ts +132 -70
  176. package/src/ui/index.ts +3 -0
  177. package/src/ui/mount.ts +23 -1
  178. package/src/ui/styles.ts +434 -58
  179. package/src/ui/utils.ts +66 -0
  180. package/src/ui/wallet-controller.ts +103 -5
  181. package/src/wallet/initializer.ts +7 -1
  182. package/dist/standalone/assets/main-VE4olb-Q.js +0 -2841
@@ -21,22 +21,25 @@ export class DevWalletConnect extends LitElement {
21
21
  css`
22
22
  :host {
23
23
  display: flex;
24
+ flex: 1;
24
25
  flex-direction: column;
25
26
  min-height: 0;
26
27
  }
27
28
 
28
29
  .connect-content {
29
30
  flex: 1;
31
+ min-height: 0;
30
32
  padding: 16px;
33
+ background: var(--dev-wallet-bg-0);
31
34
  }
32
35
 
33
36
  .connect-header {
34
- text-align: center;
35
- margin-bottom: 12px;
37
+ text-align: left;
38
+ margin-bottom: 14px;
36
39
  }
37
40
 
38
41
  .connect-title {
39
- font-size: 14px;
42
+ font-size: 13px;
40
43
  font-weight: var(--dev-wallet-font-weight-semibold);
41
44
  color: var(--dev-wallet-foreground);
42
45
  }
@@ -67,19 +70,25 @@ export class DevWalletConnect extends LitElement {
67
70
  .account-list {
68
71
  max-height: 240px;
69
72
  overflow-y: auto;
73
+ display: flex;
74
+ flex-direction: column;
75
+ gap: 6px;
70
76
  }
71
77
 
72
78
  .account-item {
73
79
  display: flex;
74
80
  align-items: center;
75
81
  gap: 8px;
76
- padding: 8px;
77
- border-radius: var(--dev-wallet-radius-md);
82
+ padding: 10px;
83
+ border-radius: var(--dev-wallet-radius-lg);
84
+ border: 1px solid var(--dev-wallet-border);
85
+ background: var(--dev-wallet-surface);
78
86
  cursor: pointer;
79
87
  }
80
88
 
81
89
  .account-item:hover {
82
- background: var(--dev-wallet-secondary);
90
+ background: var(--dev-wallet-bg-hover);
91
+ border-color: var(--dev-wallet-border-strong);
83
92
  }
84
93
 
85
94
  .account-item input[type='checkbox'] {
@@ -105,8 +114,9 @@ export class DevWalletConnect extends LitElement {
105
114
  text-transform: uppercase;
106
115
  letter-spacing: 0.3px;
107
116
  padding: 1px 5px;
108
- border-radius: var(--dev-wallet-radius-2xs);
109
- background: color-mix(in oklab, var(--dev-wallet-primary) 15%, transparent);
117
+ border-radius: 999px;
118
+ border: 1px solid var(--dev-wallet-border-strong);
119
+ background: var(--dev-wallet-accent-fade);
110
120
  color: var(--dev-wallet-primary);
111
121
  margin-left: 6px;
112
122
  }
@@ -114,6 +124,7 @@ export class DevWalletConnect extends LitElement {
114
124
  .connect-footer {
115
125
  padding: 12px 16px;
116
126
  border-top: 1px solid var(--dev-wallet-border);
127
+ background: var(--dev-wallet-surface);
117
128
  }
118
129
 
119
130
  /* Override: connect uses --primary instead of --positive for approve */
@@ -29,16 +29,18 @@ export class DevWalletDropdown extends LitElement {
29
29
  position: absolute;
30
30
  top: 100%;
31
31
  left: 0;
32
- margin-top: 4px;
32
+ margin-top: 6px;
33
33
  min-width: 100%;
34
- background: var(--dev-wallet-background);
35
- border: 1px solid var(--dev-wallet-border);
36
- border-radius: var(--dev-wallet-radius-md);
37
- box-shadow: var(--dev-wallet-shadow-md);
34
+ background: var(--dev-wallet-bg-2);
35
+ border: 1px solid var(--dev-wallet-border-2);
36
+ border-radius: var(--dev-wallet-radius-lg);
37
+ box-shadow: var(--dev-wallet-shadow-lg);
38
38
  z-index: 10;
39
39
  overflow: hidden;
40
40
  max-height: var(--dropdown-max-height, none);
41
41
  overflow-y: auto;
42
+ padding: 4px;
43
+ animation: popin 120ms ease-out;
42
44
  }
43
45
 
44
46
  :host([full-width]) .popover {
@@ -0,0 +1,591 @@
1
+ // Copyright (c) Mysten Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ //
4
+ // Dev-wallet fork-controls panel. Renders when the active stack is in
5
+ // `runtime === 'forked'` mode and surfaces a curated slice of the
6
+ // sui-fork admin API to the operator: status read, advance-clock,
7
+ // advance-checkpoint, and impersonation slot toggles.
8
+
9
+ import { css, html, LitElement, nothing } from 'lit';
10
+ import { customElement, property, state } from 'lit/decorators.js';
11
+
12
+ import type { ForkImpersonationSlot, ForkRelay, ForkStatus } from '../adapters/fork-relay.js';
13
+ import {
14
+ actionButtonStyles,
15
+ cardItemStyles,
16
+ formInputStyles,
17
+ inlineErrorStyles,
18
+ listContainerStyles,
19
+ monoTruncateStyles,
20
+ sectionHeaderStyles,
21
+ subLabelStyles,
22
+ sharedStyles,
23
+ stateStyles,
24
+ } from './styles.js';
25
+ import { formatAddress, getErrorMessage } from './utils.js';
26
+
27
+ /** Default polling cadence for `GetStatus`. Slow on purpose — the panel
28
+ * also refreshes after every action verb lands. */
29
+ const STATUS_POLL_MS = 4000;
30
+
31
+ @customElement('dev-wallet-fork-panel')
32
+ export class DevWalletForkPanel extends LitElement {
33
+ static override styles = [
34
+ sharedStyles,
35
+ sectionHeaderStyles,
36
+ actionButtonStyles,
37
+ stateStyles,
38
+ cardItemStyles,
39
+ listContainerStyles,
40
+ formInputStyles,
41
+ inlineErrorStyles,
42
+ monoTruncateStyles,
43
+ subLabelStyles,
44
+ css`
45
+ .section {
46
+ margin-bottom: 18px;
47
+ }
48
+
49
+ .section:last-child {
50
+ margin-bottom: 0;
51
+ }
52
+
53
+ .status-grid {
54
+ display: grid;
55
+ grid-template-columns: max-content 1fr;
56
+ column-gap: 12px;
57
+ row-gap: 6px;
58
+ font-size: 12px;
59
+ padding: 10px 12px;
60
+ border-radius: var(--dev-wallet-radius-md);
61
+ border: 1px solid var(--dev-wallet-border);
62
+ background: var(--dev-wallet-secondary);
63
+ }
64
+
65
+ .status-label {
66
+ font-size: 10px;
67
+ font-weight: var(--dev-wallet-font-weight-medium);
68
+ align-self: center;
69
+ }
70
+
71
+ .status-value {
72
+ font-family: var(--dev-wallet-font-mono);
73
+ color: var(--dev-wallet-foreground);
74
+ word-break: break-all;
75
+ }
76
+
77
+ .status-value.dim {
78
+ color: var(--dev-wallet-muted-foreground);
79
+ }
80
+
81
+ .action-row {
82
+ display: flex;
83
+ gap: 6px;
84
+ align-items: stretch;
85
+ }
86
+
87
+ .action-input {
88
+ flex: 1;
89
+ min-width: 0;
90
+ padding: 8px 10px;
91
+ font-size: 12px;
92
+ font-family: var(--dev-wallet-font-mono);
93
+ border-radius: var(--dev-wallet-radius-sm);
94
+ border: 1px solid var(--dev-wallet-border);
95
+ background: var(--dev-wallet-background);
96
+ }
97
+
98
+ .btn-action {
99
+ min-width: 96px;
100
+ padding: 0 14px;
101
+ border-radius: var(--dev-wallet-radius-sm);
102
+ font-size: 12px;
103
+ font-weight: var(--dev-wallet-font-weight-semibold);
104
+ background: var(--dev-wallet-primary);
105
+ color: var(--dev-wallet-primary-foreground);
106
+ transition: background-color 0.15s;
107
+ }
108
+
109
+ .btn-action:hover:not(:disabled) {
110
+ background: oklab(from var(--dev-wallet-primary) calc(l - 0.03) a b);
111
+ }
112
+
113
+ .btn-action:disabled {
114
+ opacity: 0.55;
115
+ cursor: not-allowed;
116
+ }
117
+
118
+ .slot-list {
119
+ gap: 6px;
120
+ }
121
+
122
+ .slot-item {
123
+ gap: 8px;
124
+ padding: 8px 10px;
125
+ }
126
+
127
+ .slot-meta {
128
+ flex: 1;
129
+ min-width: 0;
130
+ display: flex;
131
+ flex-direction: column;
132
+ gap: 2px;
133
+ }
134
+
135
+ .slot-label {
136
+ font-size: 12px;
137
+ font-weight: var(--dev-wallet-font-weight-medium);
138
+ color: var(--dev-wallet-foreground);
139
+ overflow: hidden;
140
+ text-overflow: ellipsis;
141
+ white-space: nowrap;
142
+ }
143
+
144
+ .slot-address {
145
+ font-size: 10px;
146
+ }
147
+
148
+ .slot-toggle {
149
+ padding: 4px 10px;
150
+ border-radius: var(--dev-wallet-radius-xs);
151
+ background: var(--dev-wallet-background);
152
+ border: 1px solid var(--dev-wallet-border);
153
+ color: var(--dev-wallet-muted-foreground);
154
+ font-weight: var(--dev-wallet-font-weight-semibold);
155
+ }
156
+
157
+ .slot-toggle.on {
158
+ background: var(--dev-wallet-primary);
159
+ color: var(--dev-wallet-primary-foreground);
160
+ border-color: var(--dev-wallet-primary);
161
+ }
162
+
163
+ .slot-toggle:hover:not(:disabled) {
164
+ filter: brightness(1.05);
165
+ }
166
+
167
+ .slot-toggle:disabled {
168
+ opacity: 0.55;
169
+ cursor: not-allowed;
170
+ }
171
+
172
+ .helper-text {
173
+ font-size: 11px;
174
+ color: var(--dev-wallet-muted-foreground);
175
+ margin-top: 6px;
176
+ line-height: 1.4;
177
+ }
178
+
179
+ .fork-banner {
180
+ padding: 8px 12px;
181
+ background: rgba(234, 179, 8, 0.12);
182
+ border: 1px solid rgba(234, 179, 8, 0.35);
183
+ border-radius: var(--dev-wallet-radius-sm);
184
+ font-size: 11px;
185
+ color: var(--dev-wallet-muted-foreground);
186
+ line-height: 1.4;
187
+ margin-bottom: 14px;
188
+ }
189
+
190
+ .fork-banner strong {
191
+ color: #92400e;
192
+ font-weight: var(--dev-wallet-font-weight-semibold);
193
+ }
194
+
195
+ .inline-error {
196
+ font-size: 11px;
197
+ margin-top: 6px;
198
+ }
199
+ `,
200
+ ];
201
+
202
+ /** Fork relay client. Provided by the parent (`wallet-controller`)
203
+ * when the active manifest exposes a forked stack. When `null`,
204
+ * the panel renders an empty state — the tab gating in the
205
+ * controller normally prevents this. */
206
+ @property({ attribute: false })
207
+ relay: ForkRelay | null = null;
208
+
209
+ /** Upstream label (`'mainnet'`, `'testnet'`, …). Surfaced in the
210
+ * banner so the operator can't misread which chain the fork forked. */
211
+ @property({ type: String })
212
+ upstream = '';
213
+
214
+ @state()
215
+ private _status: ForkStatus | null = null;
216
+
217
+ @state()
218
+ private _statusError: string | null = null;
219
+
220
+ @state()
221
+ private _statusLoading = false;
222
+
223
+ @state()
224
+ private _actionInFlight: 'advance-clock' | 'advance-checkpoint' | null = null;
225
+
226
+ @state()
227
+ private _actionError: string | null = null;
228
+
229
+ @state()
230
+ private _slots: ForkImpersonationSlot[] = [];
231
+
232
+ @state()
233
+ private _slotsError: string | null = null;
234
+
235
+ @state()
236
+ private _slotToggleInFlight: string | null = null;
237
+
238
+ @state()
239
+ private _clockInputMs = '1000';
240
+
241
+ @state()
242
+ private _checkpointInputCount = '1';
243
+
244
+ #pollTimer: ReturnType<typeof setInterval> | null = null;
245
+
246
+ override connectedCallback() {
247
+ super.connectedCallback();
248
+ this.#startPolling();
249
+ void this.#refreshAll();
250
+ }
251
+
252
+ override disconnectedCallback() {
253
+ super.disconnectedCallback();
254
+ this.#stopPolling();
255
+ }
256
+
257
+ override willUpdate(changed: Map<string, unknown>) {
258
+ if (changed.has('relay')) {
259
+ this._status = null;
260
+ this._statusError = null;
261
+ this._slots = [];
262
+ this._slotsError = null;
263
+ if (this.relay !== null) {
264
+ void this.#refreshAll();
265
+ }
266
+ }
267
+ }
268
+
269
+ override render() {
270
+ if (!this.relay) {
271
+ return html`<div class="empty-state" part="empty-state">
272
+ Fork controls require a devstack wallet-app connection.
273
+ </div>`;
274
+ }
275
+
276
+ const upstream = this._status?.upstream ?? this.upstream;
277
+ return html`
278
+ <div class="fork-banner" part="fork-banner">
279
+ <strong>Fork mode:</strong> changes here only affect the local
280
+ ${upstream ? html`<em>${upstream}</em>` : 'forked'} sui-fork stack — no real funds at risk.
281
+ </div>
282
+
283
+ <div class="section">
284
+ <div class="section-header">Status</div>
285
+ ${this.#renderStatus()}
286
+ </div>
287
+
288
+ <div class="section">
289
+ <div class="section-header">Advance clock</div>
290
+ <div class="action-row">
291
+ <input
292
+ class="action-input"
293
+ part="advance-clock-input"
294
+ type="number"
295
+ min="1"
296
+ step="1"
297
+ aria-label="Advance clock duration in ms"
298
+ .value=${this._clockInputMs}
299
+ @input=${this.#onClockInput}
300
+ ?disabled=${this._actionInFlight !== null}
301
+ />
302
+ <button
303
+ class="btn-action"
304
+ part="advance-clock-button"
305
+ ?disabled=${this._actionInFlight !== null || !this.#parseClock()}
306
+ @click=${this.#onAdvanceClock}
307
+ >
308
+ ${this._actionInFlight === 'advance-clock' ? 'Advancing…' : '+ ms'}
309
+ </button>
310
+ </div>
311
+ <div class="helper-text">
312
+ Advances <code>clock::timestamp_ms()</code> by the given milliseconds.
313
+ </div>
314
+ </div>
315
+
316
+ <div class="section">
317
+ <div class="section-header">Advance checkpoints</div>
318
+ <div class="action-row">
319
+ <input
320
+ class="action-input"
321
+ part="advance-checkpoint-input"
322
+ type="number"
323
+ min="1"
324
+ step="1"
325
+ aria-label="Advance checkpoint count"
326
+ .value=${this._checkpointInputCount}
327
+ @input=${this.#onCheckpointInput}
328
+ ?disabled=${this._actionInFlight !== null}
329
+ />
330
+ <button
331
+ class="btn-action"
332
+ part="advance-checkpoint-button"
333
+ ?disabled=${this._actionInFlight !== null || !this.#parseCheckpointCount()}
334
+ @click=${this.#onAdvanceCheckpoint}
335
+ >
336
+ ${this._actionInFlight === 'advance-checkpoint' ? 'Sealing…' : '+ ckpt'}
337
+ </button>
338
+ </div>
339
+ <div class="helper-text">
340
+ Seals pending transactions into <code>n</code> new checkpoints.
341
+ </div>
342
+ ${this._actionError
343
+ ? html`<div class="inline-error" part="action-error">${this._actionError}</div>`
344
+ : nothing}
345
+ </div>
346
+
347
+ <div class="section">
348
+ <div class="section-header">Impersonation slots</div>
349
+ ${this.#renderSlots()}
350
+ </div>
351
+ `;
352
+ }
353
+
354
+ #renderStatus() {
355
+ if (this._statusLoading && this._status === null) {
356
+ return html`<div class="loading" part="status-loading">Loading fork status…</div>`;
357
+ }
358
+ if (this._statusError) {
359
+ return html` <div class="error-state" part="status-error">${this._statusError}</div> `;
360
+ }
361
+ if (!this._status) {
362
+ return html`<div class="empty-state" part="status-empty">Fork status unavailable.</div>`;
363
+ }
364
+ const { checkpoint, clockMs, autoTickMs } = this._status;
365
+ const clockIso = (() => {
366
+ try {
367
+ return new Date(Number(clockMs)).toISOString();
368
+ } catch {
369
+ return '—';
370
+ }
371
+ })();
372
+ return html`
373
+ <div class="status-grid" part="status-grid">
374
+ <span class="status-label">Checkpoint</span>
375
+ <span class="status-value" part="status-checkpoint">${checkpoint.toString()}</span>
376
+ <span class="status-label">Clock</span>
377
+ <span class="status-value" part="status-clock">${clockMs.toString()}</span>
378
+ <span class="status-label">UTC</span>
379
+ <span class="status-value dim" part="status-clock-iso">${clockIso}</span>
380
+ <span class="status-label">Auto-tick</span>
381
+ <span class="status-value dim" part="status-auto-tick">
382
+ ${autoTickMs ? `${autoTickMs}ms` : 'off'}
383
+ </span>
384
+ </div>
385
+ `;
386
+ }
387
+
388
+ #renderSlots() {
389
+ if (this._slotsError) {
390
+ return html`<div class="error-state" part="slots-error">${this._slotsError}</div>`;
391
+ }
392
+ if (this._slots.length === 0) {
393
+ return html`<div class="empty-state" part="slots-empty">
394
+ No impersonation slots configured. Add addresses via
395
+ <code>Sui({fork:{impersonate:[…]}})</code>.
396
+ </div>`;
397
+ }
398
+ return html`
399
+ <div class="slot-list" part="slot-list">
400
+ ${this._slots.map((slot) => {
401
+ const inFlight = this._slotToggleInFlight === slot.address;
402
+ return html`
403
+ <div
404
+ class=${slot.active ? 'slot-item active' : 'slot-item'}
405
+ part=${slot.active ? 'slot-item slot-item-active' : 'slot-item'}
406
+ >
407
+ <div class="slot-meta">
408
+ <span class="slot-label">${slot.label ?? formatAddress(slot.address)}</span>
409
+ <span class="slot-address">${formatAddress(slot.address)}</span>
410
+ </div>
411
+ <button
412
+ class=${slot.active ? 'slot-toggle on' : 'slot-toggle'}
413
+ part="slot-toggle"
414
+ aria-pressed=${slot.active}
415
+ ?disabled=${inFlight}
416
+ @click=${() => this.#onToggleSlot(slot)}
417
+ >
418
+ ${inFlight ? '…' : slot.active ? 'On' : 'Off'}
419
+ </button>
420
+ </div>
421
+ `;
422
+ })}
423
+ </div>
424
+ <div class="helper-text">
425
+ When <strong>On</strong>, user-driven tx signing for the matching account routes through
426
+ <code>executeImpersonated</code> instead of a real signature.
427
+ </div>
428
+ `;
429
+ }
430
+
431
+ // ── Input bindings ────────────────────────────────────────────────────
432
+
433
+ #onClockInput = (e: Event) => {
434
+ this._clockInputMs = (e.target as HTMLInputElement).value;
435
+ };
436
+
437
+ #onCheckpointInput = (e: Event) => {
438
+ this._checkpointInputCount = (e.target as HTMLInputElement).value;
439
+ };
440
+
441
+ #parseClock(): number | null {
442
+ const n = Number(this._clockInputMs);
443
+ if (!Number.isFinite(n) || n <= 0 || !Number.isInteger(n)) return null;
444
+ return n;
445
+ }
446
+
447
+ #parseCheckpointCount(): number | null {
448
+ const n = Number(this._checkpointInputCount);
449
+ if (!Number.isFinite(n) || n <= 0 || !Number.isInteger(n)) return null;
450
+ return n;
451
+ }
452
+
453
+ // ── Action handlers ───────────────────────────────────────────────────
454
+
455
+ #onAdvanceClock = async () => {
456
+ const ms = this.#parseClock();
457
+ if (ms === null || !this.relay) return;
458
+ this._actionInFlight = 'advance-clock';
459
+ this._actionError = null;
460
+ try {
461
+ const result = await this.relay.advanceClock(ms);
462
+ if (result.ok) {
463
+ this._status = result.value;
464
+ } else {
465
+ this._actionError = result.error;
466
+ }
467
+ } catch (error) {
468
+ this._actionError = getErrorMessage(error, 'advance-clock failed');
469
+ } finally {
470
+ this._actionInFlight = null;
471
+ }
472
+ };
473
+
474
+ #onAdvanceCheckpoint = async () => {
475
+ const count = this.#parseCheckpointCount();
476
+ if (count === null || !this.relay) return;
477
+ this._actionInFlight = 'advance-checkpoint';
478
+ this._actionError = null;
479
+ try {
480
+ const result = await this.relay.advanceCheckpoint(count);
481
+ if (result.ok) {
482
+ this._status = result.value;
483
+ } else {
484
+ this._actionError = result.error;
485
+ }
486
+ } catch (error) {
487
+ this._actionError = getErrorMessage(error, 'advance-checkpoint failed');
488
+ } finally {
489
+ this._actionInFlight = null;
490
+ }
491
+ };
492
+
493
+ #onToggleSlot = async (slot: ForkImpersonationSlot) => {
494
+ if (!this.relay || this._slotToggleInFlight !== null) return;
495
+ this._slotToggleInFlight = slot.address;
496
+ this._slotsError = null;
497
+ try {
498
+ const result = await this.relay.setImpersonation(slot.address, !slot.active);
499
+ if (result.ok) {
500
+ this._slots = result.value;
501
+ // Notify dapp-level integrations (`wallet-controller`) so the
502
+ // signing modal flips its impersonation footnote in lock-step
503
+ // with the toggle. Composed/bubbling so the host element sees
504
+ // it regardless of where the panel was mounted.
505
+ this.dispatchEvent(
506
+ new CustomEvent('fork-impersonation-changed', {
507
+ bubbles: true,
508
+ composed: true,
509
+ detail: {
510
+ address: slot.address,
511
+ active: !slot.active,
512
+ slots: result.value,
513
+ },
514
+ }),
515
+ );
516
+ } else {
517
+ this._slotsError = result.error;
518
+ }
519
+ } catch (error) {
520
+ this._slotsError = getErrorMessage(error, 'toggle failed');
521
+ } finally {
522
+ this._slotToggleInFlight = null;
523
+ }
524
+ };
525
+
526
+ // ── Polling ───────────────────────────────────────────────────────────
527
+
528
+ #startPolling() {
529
+ this.#stopPolling();
530
+ this.#pollTimer = setInterval(() => {
531
+ void this.#refreshStatus();
532
+ }, STATUS_POLL_MS);
533
+ }
534
+
535
+ #stopPolling() {
536
+ if (this.#pollTimer !== null) {
537
+ clearInterval(this.#pollTimer);
538
+ this.#pollTimer = null;
539
+ }
540
+ }
541
+
542
+ async #refreshAll(): Promise<void> {
543
+ await Promise.all([this.#refreshStatus(), this.#refreshSlots()]);
544
+ }
545
+
546
+ async #refreshStatus(): Promise<void> {
547
+ if (!this.relay) return;
548
+ this._statusLoading = true;
549
+ try {
550
+ const result = await this.relay.getStatus();
551
+ if (result.ok) {
552
+ this._status = result.value;
553
+ this._statusError = null;
554
+ } else {
555
+ this._statusError = result.error;
556
+ }
557
+ } catch (error) {
558
+ this._statusError = getErrorMessage(error, 'status refresh failed');
559
+ } finally {
560
+ this._statusLoading = false;
561
+ }
562
+ }
563
+
564
+ async #refreshSlots(): Promise<void> {
565
+ if (!this.relay) return;
566
+ try {
567
+ const result = await this.relay.listImpersonations();
568
+ if (result.ok) {
569
+ this._slots = result.value;
570
+ this._slotsError = null;
571
+ } else {
572
+ this._slotsError = result.error;
573
+ }
574
+ } catch (error) {
575
+ this._slotsError = getErrorMessage(error, 'slot refresh failed');
576
+ }
577
+ }
578
+
579
+ /** Public hook for tests + external consumers (e.g. signing-modal
580
+ * wants to re-read slots after a transaction lands). Resolves once
581
+ * both status + slots have refreshed. */
582
+ async refresh(): Promise<void> {
583
+ await this.#refreshAll();
584
+ }
585
+ }
586
+
587
+ declare global {
588
+ interface HTMLElementTagNameMap {
589
+ 'dev-wallet-fork-panel': DevWalletForkPanel;
590
+ }
591
+ }