@mysten-incubation/dev-wallet 0.0.0 → 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 +32 -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 +125 -123
  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
@@ -0,0 +1,224 @@
1
+ import { buildManagedAccount } from "./build-managed-account.mjs";
2
+ import { BaseSignerAdapter } from "./base-adapter.mjs";
3
+ import { DEVSTACK_WALLET_HTTP_PATH } from "./devstack-paths.mjs";
4
+ import { fromBase64, toBase64 } from "@mysten/sui/utils";
5
+ import { Ed25519PublicKey } from "@mysten/sui/keypairs/ed25519";
6
+ import { Signer } from "@mysten/sui/cryptography";
7
+ import { Secp256k1PublicKey } from "@mysten/sui/keypairs/secp256k1";
8
+ import { Secp256r1PublicKey } from "@mysten/sui/keypairs/secp256r1";
9
+ //#region src/adapters/devstack-adapter.ts
10
+ const DEVSTACK_WALLET_FEATURES = [
11
+ "sui:signTransaction",
12
+ "sui:signAndExecuteTransaction",
13
+ "sui:signPersonalMessage"
14
+ ];
15
+ var AuthError = class extends Error {
16
+ constructor() {
17
+ super("Unauthorized");
18
+ this.name = "AuthError";
19
+ }
20
+ };
21
+ function publicKeyForScheme(base64, scheme) {
22
+ const bytes = fromBase64(base64);
23
+ switch (String(scheme).toLowerCase()) {
24
+ case "ed25519": return new Ed25519PublicKey(bytes);
25
+ case "secp256k1": return new Secp256k1PublicKey(bytes);
26
+ case "secp256r1": return new Secp256r1PublicKey(bytes);
27
+ default: throw new Error(`Unsupported key scheme: ${scheme}`);
28
+ }
29
+ }
30
+ /**
31
+ * Signer that delegates transaction signing to the devstack wallet-app
32
+ * over HTTP. Mirrors {@link CliProxySigner} but talks to the
33
+ * `wallet()` plugin's endpoints under `/api/v1/devstack/*`.
34
+ */
35
+ var DevstackProxySigner = class extends Signer {
36
+ #address;
37
+ #publicKey;
38
+ #scheme;
39
+ #serverOrigin;
40
+ #authToken;
41
+ constructor(options) {
42
+ super();
43
+ this.#address = options.address;
44
+ this.#publicKey = options.publicKey;
45
+ this.#scheme = options.scheme;
46
+ this.#serverOrigin = options.serverOrigin;
47
+ this.#authToken = options.authToken ?? null;
48
+ }
49
+ async sign(_bytes) {
50
+ throw new Error("DevstackProxySigner does not support raw digest signing. Use signTransaction (the wallet-app signs BCS-serialized TransactionData).");
51
+ }
52
+ getKeyScheme() {
53
+ return this.#scheme;
54
+ }
55
+ getPublicKey() {
56
+ return this.#publicKey;
57
+ }
58
+ toSuiAddress() {
59
+ return this.#address;
60
+ }
61
+ async signTransaction(bytes) {
62
+ const headers = { "Content-Type": "application/json" };
63
+ if (this.#authToken !== null) headers["Authorization"] = `Bearer ${this.#authToken}`;
64
+ const requestBytes = toBase64(bytes);
65
+ const res = await fetch(`${this.#serverOrigin}${DEVSTACK_WALLET_HTTP_PATH.SIGN_TRANSACTION}`, {
66
+ method: "POST",
67
+ headers,
68
+ body: JSON.stringify({
69
+ address: this.#address,
70
+ bytes: requestBytes
71
+ })
72
+ });
73
+ if (!res.ok) {
74
+ const message = (await res.json().catch(() => ({}))).error;
75
+ throw new Error(`Devstack signing failed: ${typeof message === "string" ? message : res.statusText}`);
76
+ }
77
+ const { bytes: responseBytes, signature } = await res.json();
78
+ if (typeof responseBytes !== "string" || responseBytes.length === 0) throw new Error("Devstack signing failed: server returned invalid signed bytes");
79
+ if (typeof signature !== "string" || signature.length === 0) throw new Error("Devstack signing failed: server returned invalid signature");
80
+ return {
81
+ bytes: responseBytes,
82
+ signature
83
+ };
84
+ }
85
+ async signPersonalMessage(bytes) {
86
+ const headers = { "Content-Type": "application/json" };
87
+ if (this.#authToken !== null) headers["Authorization"] = `Bearer ${this.#authToken}`;
88
+ const res = await fetch(`${this.#serverOrigin}${DEVSTACK_WALLET_HTTP_PATH.SIGN_PERSONAL_MESSAGE}`, {
89
+ method: "POST",
90
+ headers,
91
+ body: JSON.stringify({
92
+ address: this.#address,
93
+ bytes: toBase64(bytes)
94
+ })
95
+ });
96
+ if (!res.ok) {
97
+ const message = (await res.json().catch(() => ({}))).error;
98
+ throw new Error(`Devstack personal-message signing failed: ${typeof message === "string" ? message : res.statusText}`);
99
+ }
100
+ const { bytes: responseBytes, signature } = await res.json();
101
+ if (typeof responseBytes !== "string" || responseBytes.length === 0) throw new Error("Devstack personal-message signing failed: server returned invalid signed bytes");
102
+ if (typeof signature !== "string" || signature.length === 0) throw new Error("Devstack personal-message signing failed: server returned no signature");
103
+ return {
104
+ bytes: responseBytes,
105
+ signature
106
+ };
107
+ }
108
+ };
109
+ /**
110
+ * {@link SignerAdapter} that surfaces every account resolved by `devstack up`
111
+ * (including `cliSigner`/`envSigner`/`generatedKeypair` slots) without
112
+ * shipping their private keys into the frontend bundle. All signing happens
113
+ * server-side via the `wallet()` plugin.
114
+ *
115
+ * The simplest construction uses devstack's generated `dapp-kit-config.ts`,
116
+ * which bakes the wallet adapter wiring at codegen time — apps spread
117
+ * `devstackDappKitConfig` into `createDAppKit(...)` and never touch the
118
+ * adapter directly.
119
+ *
120
+ * To construct one manually (e.g. from a non-manifest source):
121
+ *
122
+ * ```ts
123
+ * const adapter = new DevstackSignerAdapter({
124
+ * serverOrigin: 'http://localhost:9420',
125
+ * token: '<bearer hex>',
126
+ * });
127
+ * ```
128
+ */
129
+ var DevstackSignerAdapter = class extends BaseSignerAdapter {
130
+ #serverOrigin;
131
+ #authToken;
132
+ constructor(options) {
133
+ super();
134
+ this.id = "devstack";
135
+ this.allowAutoSign = true;
136
+ try {
137
+ new URL(options.serverOrigin);
138
+ } catch {
139
+ throw new Error(`DevstackSignerAdapter: invalid serverOrigin "${options.serverOrigin}"`);
140
+ }
141
+ this.#serverOrigin = options.serverOrigin;
142
+ this.#authToken = options.token ?? null;
143
+ this.name = options.name ?? "Devstack";
144
+ }
145
+ async initialize() {
146
+ try {
147
+ const accounts = await this.#fetchAccounts();
148
+ this.setInitialAccounts(accounts);
149
+ } catch (error) {
150
+ if (error instanceof AuthError) {
151
+ console.warn("[dev-wallet] devstack adapter: unauthorized — skipping account import");
152
+ return;
153
+ }
154
+ console.warn("[dev-wallet] devstack adapter: initialization failed:", error);
155
+ }
156
+ }
157
+ destroy() {
158
+ this.#authToken = null;
159
+ super.destroy();
160
+ }
161
+ async #fetchAccounts() {
162
+ const headers = { "Content-Type": "application/json" };
163
+ if (this.#authToken !== null) headers["Authorization"] = `Bearer ${this.#authToken}`;
164
+ const res = await fetch(`${this.#serverOrigin}${DEVSTACK_WALLET_HTTP_PATH.ACCOUNTS}`, { headers });
165
+ if (res.status === 401 || res.status === 403) throw new AuthError();
166
+ if (!res.ok) throw new Error(`Failed to fetch devstack accounts: ${res.statusText}`);
167
+ const { accounts } = await res.json();
168
+ return accounts.map((info) => {
169
+ const publicKey = publicKeyForScheme(info.publicKey, info.scheme);
170
+ return buildManagedAccount(new DevstackProxySigner({
171
+ address: info.address,
172
+ publicKey,
173
+ scheme: info.scheme,
174
+ serverOrigin: this.#serverOrigin,
175
+ authToken: this.#authToken
176
+ }), info.address, info.name, DEVSTACK_WALLET_FEATURES);
177
+ });
178
+ }
179
+ };
180
+ /**
181
+ * Pull the bearer token off a paired URL produced by the `wallet()`
182
+ * plugin. Returns `null` if the input is undefined, malformed, or
183
+ * carries a `<redacted>` placeholder (the manifest's pairUrl carries
184
+ * the redacted form; the real token lives in a sibling 0o600 file the
185
+ * consumer reads separately).
186
+ *
187
+ * The token rides in the URL fragment as `#token=<hex>`.
188
+ */
189
+ function parseDevstackToken(pairedUrl) {
190
+ if (pairedUrl === void 0) return null;
191
+ try {
192
+ const hash = new URL(pairedUrl).hash;
193
+ if (hash.length <= 1) return null;
194
+ const token = new URLSearchParams(hash.slice(1)).get("token");
195
+ if (token === null || token === "<redacted>") return null;
196
+ return token;
197
+ } catch {
198
+ return null;
199
+ }
200
+ }
201
+ /**
202
+ * Convenience: read the wallet-app endpoint off a devstack manifest and
203
+ * build a configured adapter, or return `null` when the entry isn't
204
+ * present (no `wallet(...)` in the stack, or it hasn't come up yet).
205
+ *
206
+ * The wallet entry's `pairUrl` carries the `#token=…` fragment used to
207
+ * extract the bearer token. The full `Manifest` shape from
208
+ * `@mysten-incubation/devstack` is structurally compatible with
209
+ * {@link DevstackAdapterManifest}, so
210
+ * `createDevstackAdapterFromManifest(devstackManifest)` typechecks
211
+ * without a cast.
212
+ */
213
+ function createDevstackAdapterFromManifest(manifest) {
214
+ const wallet = manifest.app?.wallet;
215
+ if (wallet === void 0) return null;
216
+ return new DevstackSignerAdapter({
217
+ serverOrigin: wallet.url,
218
+ token: parseDevstackToken(wallet.pairUrl)
219
+ });
220
+ }
221
+ //#endregion
222
+ export { DevstackProxySigner, DevstackSignerAdapter, createDevstackAdapterFromManifest, parseDevstackToken };
223
+
224
+ //# sourceMappingURL=devstack-adapter.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devstack-adapter.mjs","names":["#address","#publicKey","#scheme","#serverOrigin","#authToken","#fetchAccounts"],"sources":["../../src/adapters/devstack-adapter.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n//\n// Devstack signer adapter — exposes accounts resolved by `devstack up`'s\n// `wallet()` plugin to dApp Kit, signing transactions over HTTP so\n// private keys never enter the frontend bundle. Mirrors RemoteCliAdapter's\n// out-of-process model; the difference is the source of accounts (devstack\n// resolved signers vs `sui keytool list`) and the endpoint paths.\n\nimport { Signer } from '@mysten/sui/cryptography';\nimport type { PublicKey, SignatureScheme } from '@mysten/sui/cryptography';\nimport { Ed25519PublicKey } from '@mysten/sui/keypairs/ed25519';\nimport { Secp256k1PublicKey } from '@mysten/sui/keypairs/secp256k1';\nimport { Secp256r1PublicKey } from '@mysten/sui/keypairs/secp256r1';\nimport { fromBase64, toBase64 } from '@mysten/sui/utils';\n\nimport type { ManagedAccount } from '../types.js';\nimport { BaseSignerAdapter } from './base-adapter.js';\nimport { buildManagedAccount } from './build-managed-account.js';\nimport { DEVSTACK_WALLET_HTTP_PATH } from './devstack-paths.js';\n\nconst DEVSTACK_WALLET_FEATURES = [\n\t'sui:signTransaction',\n\t'sui:signAndExecuteTransaction',\n\t'sui:signPersonalMessage',\n] as const;\n\ninterface ServerAccountInfo {\n\tname: string;\n\taddress: string;\n\tscheme: SignatureScheme;\n\tpublicKey: string;\n}\n\nclass AuthError extends Error {\n\tconstructor() {\n\t\tsuper('Unauthorized');\n\t\tthis.name = 'AuthError';\n\t}\n}\n\nfunction publicKeyForScheme(base64: string, scheme: SignatureScheme | string): PublicKey {\n\tconst bytes = fromBase64(base64);\n\t// The devstack server's wallet-app endpoint surfaces the scheme in\n\t// the lowercase form it standardised at the Account boundary\n\t// (`'ed25519'`, `'secp256k1'`, `'secp256r1'`). The Sui SDK's\n\t// `SignatureScheme` union is mixed-case (`'ED25519'`, `'Secp256k1'`,\n\t// …). Normalise to lowercase before matching so either casing parses.\n\tswitch (String(scheme).toLowerCase()) {\n\t\tcase 'ed25519':\n\t\t\treturn new Ed25519PublicKey(bytes);\n\t\tcase 'secp256k1':\n\t\t\treturn new Secp256k1PublicKey(bytes);\n\t\tcase 'secp256r1':\n\t\t\treturn new Secp256r1PublicKey(bytes);\n\t\tdefault:\n\t\t\tthrow new Error(`Unsupported key scheme: ${scheme}`);\n\t}\n}\n\n/**\n * Signer that delegates transaction signing to the devstack wallet-app\n * over HTTP. Mirrors {@link CliProxySigner} but talks to the\n * `wallet()` plugin's endpoints under `/api/v1/devstack/*`.\n */\nexport class DevstackProxySigner extends Signer {\n\t#address: string;\n\t#publicKey: PublicKey;\n\t#scheme: SignatureScheme;\n\t#serverOrigin: string;\n\t#authToken: string | null;\n\n\tconstructor(options: {\n\t\taddress: string;\n\t\tpublicKey: PublicKey;\n\t\tscheme: SignatureScheme;\n\t\tserverOrigin: string;\n\t\tauthToken?: string | null;\n\t}) {\n\t\tsuper();\n\t\tthis.#address = options.address;\n\t\tthis.#publicKey = options.publicKey;\n\t\tthis.#scheme = options.scheme;\n\t\tthis.#serverOrigin = options.serverOrigin;\n\t\tthis.#authToken = options.authToken ?? null;\n\t}\n\n\tasync sign(_bytes: Uint8Array): Promise<Uint8Array<ArrayBuffer>> {\n\t\tthrow new Error(\n\t\t\t'DevstackProxySigner does not support raw digest signing. ' +\n\t\t\t\t'Use signTransaction (the wallet-app signs BCS-serialized TransactionData).',\n\t\t);\n\t}\n\n\tgetKeyScheme(): SignatureScheme {\n\t\treturn this.#scheme;\n\t}\n\n\tgetPublicKey(): PublicKey {\n\t\treturn this.#publicKey;\n\t}\n\n\toverride toSuiAddress(): string {\n\t\treturn this.#address;\n\t}\n\n\toverride async signTransaction(bytes: Uint8Array) {\n\t\tconst headers: Record<string, string> = { 'Content-Type': 'application/json' };\n\t\tif (this.#authToken !== null) {\n\t\t\theaders['Authorization'] = `Bearer ${this.#authToken}`;\n\t\t}\n\t\tconst requestBytes = toBase64(bytes);\n\t\tconst res = await fetch(`${this.#serverOrigin}${DEVSTACK_WALLET_HTTP_PATH.SIGN_TRANSACTION}`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders,\n\t\t\tbody: JSON.stringify({\n\t\t\t\taddress: this.#address,\n\t\t\t\tbytes: requestBytes,\n\t\t\t}),\n\t\t});\n\t\tif (!res.ok) {\n\t\t\tconst body = await res.json().catch(() => ({}));\n\t\t\tconst message = (body as { error?: unknown }).error;\n\t\t\tthrow new Error(\n\t\t\t\t`Devstack signing failed: ${typeof message === 'string' ? message : res.statusText}`,\n\t\t\t);\n\t\t}\n\t\tconst { bytes: responseBytes, signature } = (await res.json()) as {\n\t\t\tbytes?: string;\n\t\t\tsignature?: string;\n\t\t};\n\t\tif (typeof responseBytes !== 'string' || responseBytes.length === 0) {\n\t\t\tthrow new Error('Devstack signing failed: server returned invalid signed bytes');\n\t\t}\n\t\tif (typeof signature !== 'string' || signature.length === 0) {\n\t\t\tthrow new Error('Devstack signing failed: server returned invalid signature');\n\t\t}\n\t\treturn {\n\t\t\tbytes: responseBytes,\n\t\t\tsignature,\n\t\t};\n\t}\n\n\toverride async signPersonalMessage(bytes: Uint8Array) {\n\t\tconst headers: Record<string, string> = { 'Content-Type': 'application/json' };\n\t\tif (this.#authToken !== null) {\n\t\t\theaders['Authorization'] = `Bearer ${this.#authToken}`;\n\t\t}\n\t\tconst res = await fetch(\n\t\t\t`${this.#serverOrigin}${DEVSTACK_WALLET_HTTP_PATH.SIGN_PERSONAL_MESSAGE}`,\n\t\t\t{\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders,\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\taddress: this.#address,\n\t\t\t\t\tbytes: toBase64(bytes),\n\t\t\t\t}),\n\t\t\t},\n\t\t);\n\t\tif (!res.ok) {\n\t\t\tconst body = await res.json().catch(() => ({}));\n\t\t\tconst message = (body as { error?: unknown }).error;\n\t\t\tthrow new Error(\n\t\t\t\t`Devstack personal-message signing failed: ${\n\t\t\t\t\ttypeof message === 'string' ? message : res.statusText\n\t\t\t\t}`,\n\t\t\t);\n\t\t}\n\t\tconst { bytes: responseBytes, signature } = (await res.json()) as {\n\t\t\tbytes?: string;\n\t\t\tsignature?: string;\n\t\t};\n\t\tif (typeof responseBytes !== 'string' || responseBytes.length === 0) {\n\t\t\tthrow new Error(\n\t\t\t\t'Devstack personal-message signing failed: server returned invalid signed bytes',\n\t\t\t);\n\t\t}\n\t\tif (typeof signature !== 'string' || signature.length === 0) {\n\t\t\tthrow new Error('Devstack personal-message signing failed: server returned no signature');\n\t\t}\n\t\treturn { bytes: responseBytes, signature };\n\t}\n}\n\nexport interface DevstackSignerAdapterOptions {\n\t/** Wallet-app origin (e.g. `http://localhost:9420`). Read from the\n\t * matching endpoint entry on the active manifest. */\n\tserverOrigin: string;\n\t/** Bearer token. Parse `?token=<hex>` from the manifest entry's\n\t * `pairUrl`. Omit if the wallet-app runs without auth. */\n\ttoken?: string | null;\n\t/** Override the adapter's display name. Defaults to `'Devstack'`. */\n\tname?: string;\n}\n\n/**\n * {@link SignerAdapter} that surfaces every account resolved by `devstack up`\n * (including `cliSigner`/`envSigner`/`generatedKeypair` slots) without\n * shipping their private keys into the frontend bundle. All signing happens\n * server-side via the `wallet()` plugin.\n *\n * The simplest construction uses devstack's generated `dapp-kit-config.ts`,\n * which bakes the wallet adapter wiring at codegen time — apps spread\n * `devstackDappKitConfig` into `createDAppKit(...)` and never touch the\n * adapter directly.\n *\n * To construct one manually (e.g. from a non-manifest source):\n *\n * ```ts\n * const adapter = new DevstackSignerAdapter({\n * serverOrigin: 'http://localhost:9420',\n * token: '<bearer hex>',\n * });\n * ```\n */\nexport class DevstackSignerAdapter extends BaseSignerAdapter {\n\treadonly id = 'devstack';\n\treadonly name: string;\n\treadonly allowAutoSign = true;\n\n\t#serverOrigin: string;\n\t#authToken: string | null;\n\n\tconstructor(options: DevstackSignerAdapterOptions) {\n\t\tsuper();\n\t\ttry {\n\t\t\tnew URL(options.serverOrigin);\n\t\t} catch {\n\t\t\tthrow new Error(`DevstackSignerAdapter: invalid serverOrigin \"${options.serverOrigin}\"`);\n\t\t}\n\t\tthis.#serverOrigin = options.serverOrigin;\n\t\tthis.#authToken = options.token ?? null;\n\t\tthis.name = options.name ?? 'Devstack';\n\t}\n\n\tasync initialize(): Promise<void> {\n\t\ttry {\n\t\t\tconst accounts = await this.#fetchAccounts();\n\t\t\tthis.setInitialAccounts(accounts);\n\t\t} catch (error) {\n\t\t\tif (error instanceof AuthError) {\n\t\t\t\tconsole.warn('[dev-wallet] devstack adapter: unauthorized — skipping account import');\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconsole.warn('[dev-wallet] devstack adapter: initialization failed:', error);\n\t\t}\n\t}\n\n\toverride destroy(): void {\n\t\tthis.#authToken = null;\n\t\tsuper.destroy();\n\t}\n\n\tasync #fetchAccounts(): Promise<ManagedAccount[]> {\n\t\tconst headers: Record<string, string> = { 'Content-Type': 'application/json' };\n\t\tif (this.#authToken !== null) {\n\t\t\theaders['Authorization'] = `Bearer ${this.#authToken}`;\n\t\t}\n\t\tconst res = await fetch(`${this.#serverOrigin}${DEVSTACK_WALLET_HTTP_PATH.ACCOUNTS}`, {\n\t\t\theaders,\n\t\t});\n\t\tif (res.status === 401 || res.status === 403) throw new AuthError();\n\t\tif (!res.ok) {\n\t\t\tthrow new Error(`Failed to fetch devstack accounts: ${res.statusText}`);\n\t\t}\n\t\tconst { accounts } = (await res.json()) as { accounts: ServerAccountInfo[] };\n\t\treturn accounts.map((info) => {\n\t\t\tconst publicKey = publicKeyForScheme(info.publicKey, info.scheme);\n\t\t\tconst signer = new DevstackProxySigner({\n\t\t\t\taddress: info.address,\n\t\t\t\tpublicKey,\n\t\t\t\tscheme: info.scheme,\n\t\t\t\tserverOrigin: this.#serverOrigin,\n\t\t\t\tauthToken: this.#authToken,\n\t\t\t});\n\t\t\treturn buildManagedAccount(signer, info.address, info.name, DEVSTACK_WALLET_FEATURES);\n\t\t});\n\t}\n}\n\n/**\n * Pull the bearer token off a paired URL produced by the `wallet()`\n * plugin. Returns `null` if the input is undefined, malformed, or\n * carries a `<redacted>` placeholder (the manifest's pairUrl carries\n * the redacted form; the real token lives in a sibling 0o600 file the\n * consumer reads separately).\n *\n * The token rides in the URL fragment as `#token=<hex>`.\n */\nexport function parseDevstackToken(pairedUrl: string | undefined): string | null {\n\tif (pairedUrl === undefined) return null;\n\ttry {\n\t\tconst url = new URL(pairedUrl);\n\t\tconst hash = url.hash; // e.g. \"#token=abcd\"\n\t\tif (hash.length <= 1) return null;\n\t\tconst params = new URLSearchParams(hash.slice(1));\n\t\tconst token = params.get('token');\n\t\tif (token === null || token === '<redacted>') return null;\n\t\treturn token;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/** Narrow input — the only field the adapter consumes is\n * `app.wallet.{url, pairUrl}`. Codegen emits exactly this shape so\n * generated `dapp-kit-config.ts` doesn't have to fabricate placeholder\n * manifest fields (`stack.app`, `coins`, etc.) just to satisfy the\n * full `Manifest` type. Mirrors a slice of `AppManifest` from\n * `@mysten-incubation/devstack` without importing it (dev-wallet\n * doesn't depend on devstack — circular). */\nexport interface DevstackAdapterManifest {\n\tapp?: {\n\t\twallet?: {\n\t\t\turl: string;\n\t\t\tpairUrl?: string;\n\t\t};\n\t};\n}\n\n/**\n * Convenience: read the wallet-app endpoint off a devstack manifest and\n * build a configured adapter, or return `null` when the entry isn't\n * present (no `wallet(...)` in the stack, or it hasn't come up yet).\n *\n * The wallet entry's `pairUrl` carries the `#token=…` fragment used to\n * extract the bearer token. The full `Manifest` shape from\n * `@mysten-incubation/devstack` is structurally compatible with\n * {@link DevstackAdapterManifest}, so\n * `createDevstackAdapterFromManifest(devstackManifest)` typechecks\n * without a cast.\n */\nexport function createDevstackAdapterFromManifest(\n\tmanifest: DevstackAdapterManifest,\n): DevstackSignerAdapter | null {\n\tconst wallet = manifest.app?.wallet;\n\tif (wallet === undefined) return null;\n\treturn new DevstackSignerAdapter({\n\t\tserverOrigin: wallet.url,\n\t\ttoken: parseDevstackToken(wallet.pairUrl),\n\t});\n}\n"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ //#region src/adapters/devstack-paths.d.ts
2
+ declare const DEVSTACK_WALLET_HTTP_PATH: {
3
+ readonly HEALTH: "/api/v1/devstack/health";
4
+ readonly ACCOUNTS: "/api/v1/devstack/accounts";
5
+ readonly SIGN_TRANSACTION: "/api/v1/devstack/sign-transaction";
6
+ readonly SIGN_PERSONAL_MESSAGE: "/api/v1/devstack/sign-personal-message";
7
+ readonly EXECUTE: "/api/v1/devstack/execute";
8
+ };
9
+ type DevstackWalletHttpPathValue = (typeof DEVSTACK_WALLET_HTTP_PATH)[keyof typeof DEVSTACK_WALLET_HTTP_PATH];
10
+ //#endregion
11
+ export { DEVSTACK_WALLET_HTTP_PATH, DevstackWalletHttpPathValue };
12
+ //# sourceMappingURL=devstack-paths.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devstack-paths.d.mts","names":[],"sources":["../../src/adapters/devstack-paths.ts"],"mappings":";cAMa,yBAAA;EAAA;;;;;;KAQD,2BAAA,WACH,yBAAA,eAAwC,yBAAA"}
@@ -0,0 +1,12 @@
1
+ //#region src/adapters/devstack-paths.ts
2
+ const DEVSTACK_WALLET_HTTP_PATH = {
3
+ HEALTH: "/api/v1/devstack/health",
4
+ ACCOUNTS: "/api/v1/devstack/accounts",
5
+ SIGN_TRANSACTION: "/api/v1/devstack/sign-transaction",
6
+ SIGN_PERSONAL_MESSAGE: "/api/v1/devstack/sign-personal-message",
7
+ EXECUTE: "/api/v1/devstack/execute"
8
+ };
9
+ //#endregion
10
+ export { DEVSTACK_WALLET_HTTP_PATH };
11
+
12
+ //# sourceMappingURL=devstack-paths.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devstack-paths.mjs","names":[],"sources":["../../src/adapters/devstack-paths.ts"],"sourcesContent":["// Mirror of `packages/devstack/src/plugins/wallet/protocol.ts` `WalletHttpPath`.\n// Duplicated here rather than imported because devstack already\n// (peer-)depends on `@mysten-incubation/dev-wallet` for codegen-emitted\n// wallet glue, and adding the reverse edge would close the workspace\n// cycle and break turbo's build ordering.\n\nexport const DEVSTACK_WALLET_HTTP_PATH = {\n\tHEALTH: '/api/v1/devstack/health',\n\tACCOUNTS: '/api/v1/devstack/accounts',\n\tSIGN_TRANSACTION: '/api/v1/devstack/sign-transaction',\n\tSIGN_PERSONAL_MESSAGE: '/api/v1/devstack/sign-personal-message',\n\tEXECUTE: '/api/v1/devstack/execute',\n} as const;\n\nexport type DevstackWalletHttpPathValue =\n\t(typeof DEVSTACK_WALLET_HTTP_PATH)[keyof typeof DEVSTACK_WALLET_HTTP_PATH];\n"],"mappings":";AAMA,MAAa,4BAA4B;CACxC,QAAQ;CACR,UAAU;CACV,kBAAkB;CAClB,uBAAuB;CACvB,SAAS;CACT"}
@@ -0,0 +1,109 @@
1
+ //#region src/adapters/fork-relay.d.ts
2
+ declare const FORK_RELAY_HTTP_PATH: {
3
+ readonly STATUS: "/api/v1/devstack/fork/status";
4
+ readonly ADVANCE_CLOCK: "/api/v1/devstack/fork/advance-clock";
5
+ readonly ADVANCE_CHECKPOINT: "/api/v1/devstack/fork/advance-checkpoint";
6
+ readonly IMPERSONATIONS: "/api/v1/devstack/fork/impersonations";
7
+ };
8
+ /** Snapshot of the fork's current head state.
9
+ *
10
+ * Mirrors the body of `ForkControl.getStatus()` minus the unused
11
+ * heartbeat fields. `clockMs` is a Unix-ms timestamp (the value
12
+ * `clock::timestamp_ms()` returns on chain); `checkpoint` is the
13
+ * latest sealed sequence number.
14
+ */
15
+ interface ForkStatus {
16
+ readonly checkpoint: bigint;
17
+ readonly clockMs: bigint;
18
+ readonly autoTickMs?: number;
19
+ /** Best-effort label of the upstream the fork is pointed at, e.g.
20
+ * `'mainnet'` or `'testnet'`. Sourced from the devstack manifest. */
21
+ readonly upstream?: string;
22
+ }
23
+ /** A single impersonation slot resolved by devstack — typically every
24
+ * address that was passed to `Sui({fork:{impersonate:[…]}})`. */
25
+ interface ForkImpersonationSlot {
26
+ readonly address: string;
27
+ readonly label?: string;
28
+ /** Whether the wallet currently routes user-driven tx signing through
29
+ * the `executeImpersonated` path for this address. The dev-wallet
30
+ * server stores the toggle so it persists across page reloads. */
31
+ readonly active: boolean;
32
+ }
33
+ interface ForkRelayOptions {
34
+ /** Wallet-app origin (e.g. `http://localhost:9420`). Same value
35
+ * `DevstackSignerAdapter` consumes — read from the manifest entry's
36
+ * `app.wallet.url`. */
37
+ readonly serverOrigin: string;
38
+ /** Bearer token. Same parsing rules as `DevstackSignerAdapter`. */
39
+ readonly token?: string | null;
40
+ }
41
+ declare class ForkRelayHttpError extends Error {
42
+ readonly status: number;
43
+ constructor(status: number, message: string);
44
+ }
45
+ /** Tagged result so callers can render an inline error without throwing. */
46
+ type ForkRelayResult<T> = {
47
+ readonly ok: true;
48
+ readonly value: T;
49
+ } | {
50
+ readonly ok: false;
51
+ readonly error: string;
52
+ readonly status?: number;
53
+ };
54
+ /**
55
+ * Thin HTTP client for the fork admin relay. Stateless — each call
56
+ * issues a fresh `fetch`. The UI layer (`dev-wallet-fork-panel`)
57
+ * polls `getStatus` on a low cadence and only refreshes after action
58
+ * verbs land, so the lack of caching here is intentional.
59
+ *
60
+ * @example
61
+ * const relay = new ForkRelay({ serverOrigin, token });
62
+ * const status = await relay.getStatus();
63
+ * if (status.ok) console.log(`clock=${status.value.clockMs}ms`);
64
+ */
65
+ declare class ForkRelay {
66
+ #private;
67
+ constructor(options: ForkRelayOptions);
68
+ get serverOrigin(): string;
69
+ getStatus(): Promise<ForkRelayResult<ForkStatus>>;
70
+ advanceClock(durationMs: number): Promise<ForkRelayResult<ForkStatus>>;
71
+ advanceCheckpoint(count: number): Promise<ForkRelayResult<ForkStatus>>;
72
+ listImpersonations(): Promise<ForkRelayResult<ForkImpersonationSlot[]>>;
73
+ /** Toggle the impersonation slot identified by `address`. The server
74
+ * is the source of truth — the response carries the resulting slot
75
+ * list so the panel re-renders consistently. */
76
+ setImpersonation(address: string, active: boolean): Promise<ForkRelayResult<ForkImpersonationSlot[]>>;
77
+ }
78
+ /** Narrow input mirroring `DevstackAdapterManifest` — enough to
79
+ * discover the wallet endpoint + fork-mode flag without pulling in
80
+ * the full devstack `Manifest` type (no reverse dependency edge,
81
+ * same reasoning as `devstack-adapter.ts`). */
82
+ interface ForkRelayManifest {
83
+ readonly app?: {
84
+ readonly wallet?: {
85
+ readonly url: string;
86
+ readonly pairUrl?: string;
87
+ };
88
+ };
89
+ readonly meta?: {
90
+ /** Devstack's `meta.runtime` is `'bundled' | 'forked'`. Only
91
+ * `'forked'` triggers fork-controls construction. */
92
+ readonly runtime?: string;
93
+ /** Optional upstream label (`'mainnet'`, `'testnet'`, …) used
94
+ * by the panel banner. */
95
+ readonly upstream?: string;
96
+ };
97
+ }
98
+ /**
99
+ * Build a configured {@link ForkRelay} from a devstack manifest, or
100
+ * `null` when fork-mode isn't active (bundled stack, no wallet entry,
101
+ * or the manifest hasn't loaded yet).
102
+ *
103
+ * Codegen-emitted browser glue calls this and forwards the result to
104
+ * `<dev-wallet-panel forkRelay={…}>`.
105
+ */
106
+ declare function createForkRelayFromManifest(manifest: ForkRelayManifest): ForkRelay | null;
107
+ //#endregion
108
+ export { ForkImpersonationSlot, ForkRelay, ForkRelayHttpError, ForkRelayManifest, ForkRelayOptions, ForkRelayResult, ForkStatus, createForkRelayFromManifest };
109
+ //# sourceMappingURL=fork-relay.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fork-relay.d.mts","names":[],"sources":["../../src/adapters/fork-relay.ts"],"mappings":";cASa,oBAAA;EAAA;;;;;;;;;;AAcb;;UAAiB,UAAA;EAAA,SACP,UAAA;EAAA,SACA,OAAA;EAAA,SACA,UAAA;EAAA;;EAAA,SAGA,QAAA;AAAA;AAKV;;AAAA,UAAiB,qBAAA;EAAA,SACP,OAAA;EAAA,SACA,KAAA;EAAA;;;EAAA,SAIA,MAAA;AAAA;AAAA,UAGO,gBAAA;;;;WAIP,YAAA;EAKe;EAAA,SAHf,KAAA;AAAA;AAAA,cAGJ,kBAAA,SAA2B,KAAA;EAAA,SAEf,MAAA;cAAA,MAAA,UAChB,OAAA;AAAA;;KAQU,eAAA;EAAA,SACE,EAAA;EAAA,SAAmB,KAAA,EAAO,CAAA;AAAA;EAAA,SAC1B,EAAA;EAAA,SAAoB,KAAA;EAAA,SAAwB,MAAA;AAAA;;;;;;;;AA6B1D;;;;cAAa,SAAA;EAAA;cAIA,OAAA,EAAS,gBAAA;EAAA,IAUjB,YAAA,CAAA;EAIE,SAAA,CAAA,GAAa,OAAA,CAAQ,eAAA,CAAgB,UAAA;EAcrC,YAAA,CAAa,UAAA,WAAqB,OAAA,CAAQ,eAAA,CAAgB,UAAA;EAoB1D,iBAAA,CAAkB,KAAA,WAAgB,OAAA,CAAQ,eAAA,CAAgB,UAAA;EAoB1D,kBAAA,CAAA,GAAsB,OAAA,CAAQ,eAAA,CAAgB,qBAAA;EApBZ;;;EAoClC,gBAAA,CACL,OAAA,UACA,MAAA,YACE,OAAA,CAAQ,eAAA,CAAgB,qBAAA;AAAA;;;;;UAyEX,iBAAA;EAAA,SACP,GAAA;IAAA,SACC,MAAA;MAAA,SACC,GAAA;MAAA,SACA,OAAA;IAAA;EAAA;EAAA,SAGF,IAAA;IA3IH;;IAAA,SA8II,OAAA;IA9IsC;;IAAA,SAiJtC,QAAA;EAAA;AAAA;;;;;;;;;iBAkCK,2BAAA,CAA4B,QAAA,EAAU,iBAAA,GAAoB,SAAA"}
@@ -0,0 +1,208 @@
1
+ //#region src/adapters/fork-relay.ts
2
+ const FORK_RELAY_HTTP_PATH = {
3
+ STATUS: "/api/v1/devstack/fork/status",
4
+ ADVANCE_CLOCK: "/api/v1/devstack/fork/advance-clock",
5
+ ADVANCE_CHECKPOINT: "/api/v1/devstack/fork/advance-checkpoint",
6
+ IMPERSONATIONS: "/api/v1/devstack/fork/impersonations"
7
+ };
8
+ var ForkRelayHttpError = class extends Error {
9
+ constructor(status, message) {
10
+ super(message);
11
+ this.status = status;
12
+ this.name = "ForkRelayHttpError";
13
+ }
14
+ };
15
+ function isForkStatusBody(value) {
16
+ if (value === null || typeof value !== "object") return false;
17
+ const v = value;
18
+ return typeof v.checkpoint === "string" && typeof v.clockMs === "string";
19
+ }
20
+ function isImpersonationsBody(value) {
21
+ if (value === null || typeof value !== "object") return false;
22
+ const slots = value.slots;
23
+ return Array.isArray(slots);
24
+ }
25
+ /**
26
+ * Thin HTTP client for the fork admin relay. Stateless — each call
27
+ * issues a fresh `fetch`. The UI layer (`dev-wallet-fork-panel`)
28
+ * polls `getStatus` on a low cadence and only refreshes after action
29
+ * verbs land, so the lack of caching here is intentional.
30
+ *
31
+ * @example
32
+ * const relay = new ForkRelay({ serverOrigin, token });
33
+ * const status = await relay.getStatus();
34
+ * if (status.ok) console.log(`clock=${status.value.clockMs}ms`);
35
+ */
36
+ var ForkRelay = class {
37
+ #serverOrigin;
38
+ #authToken;
39
+ constructor(options) {
40
+ try {
41
+ new URL(options.serverOrigin);
42
+ } catch {
43
+ throw new Error(`ForkRelay: invalid serverOrigin "${options.serverOrigin}"`);
44
+ }
45
+ this.#serverOrigin = options.serverOrigin;
46
+ this.#authToken = options.token ?? null;
47
+ }
48
+ get serverOrigin() {
49
+ return this.#serverOrigin;
50
+ }
51
+ async getStatus() {
52
+ return this.#request("GET", FORK_RELAY_HTTP_PATH.STATUS, void 0, (body) => {
53
+ if (!isForkStatusBody(body)) throw new Error("Malformed fork status response");
54
+ return {
55
+ checkpoint: BigInt(body.checkpoint),
56
+ clockMs: BigInt(body.clockMs),
57
+ autoTickMs: body.autoTickMs,
58
+ upstream: body.upstream
59
+ };
60
+ });
61
+ }
62
+ async advanceClock(durationMs) {
63
+ if (!Number.isFinite(durationMs) || durationMs <= 0 || !Number.isInteger(durationMs)) return {
64
+ ok: false,
65
+ error: `advanceClock: durationMs must be a positive integer (got ${durationMs})`
66
+ };
67
+ return this.#request("POST", FORK_RELAY_HTTP_PATH.ADVANCE_CLOCK, { durationMs }, (body) => {
68
+ if (!isForkStatusBody(body)) throw new Error("Malformed advance-clock response");
69
+ return {
70
+ checkpoint: BigInt(body.checkpoint),
71
+ clockMs: BigInt(body.clockMs),
72
+ autoTickMs: body.autoTickMs,
73
+ upstream: body.upstream
74
+ };
75
+ });
76
+ }
77
+ async advanceCheckpoint(count) {
78
+ if (!Number.isFinite(count) || count <= 0 || !Number.isInteger(count)) return {
79
+ ok: false,
80
+ error: `advanceCheckpoint: count must be a positive integer (got ${count})`
81
+ };
82
+ return this.#request("POST", FORK_RELAY_HTTP_PATH.ADVANCE_CHECKPOINT, { count }, (body) => {
83
+ if (!isForkStatusBody(body)) throw new Error("Malformed advance-checkpoint response");
84
+ return {
85
+ checkpoint: BigInt(body.checkpoint),
86
+ clockMs: BigInt(body.clockMs),
87
+ autoTickMs: body.autoTickMs,
88
+ upstream: body.upstream
89
+ };
90
+ });
91
+ }
92
+ async listImpersonations() {
93
+ return this.#request("GET", FORK_RELAY_HTTP_PATH.IMPERSONATIONS, void 0, (body) => {
94
+ if (!isImpersonationsBody(body)) throw new Error("Malformed impersonations response");
95
+ return body.slots.map((s) => ({
96
+ address: s.address,
97
+ label: s.label,
98
+ active: s.active
99
+ }));
100
+ });
101
+ }
102
+ /** Toggle the impersonation slot identified by `address`. The server
103
+ * is the source of truth — the response carries the resulting slot
104
+ * list so the panel re-renders consistently. */
105
+ async setImpersonation(address, active) {
106
+ return this.#request("POST", FORK_RELAY_HTTP_PATH.IMPERSONATIONS, {
107
+ address,
108
+ active
109
+ }, (body) => {
110
+ if (!isImpersonationsBody(body)) throw new Error("Malformed impersonations response");
111
+ return body.slots.map((s) => ({
112
+ address: s.address,
113
+ label: s.label,
114
+ active: s.active
115
+ }));
116
+ });
117
+ }
118
+ async #request(method, path, body, parse) {
119
+ const headers = { "Content-Type": "application/json" };
120
+ if (this.#authToken !== null) headers["Authorization"] = `Bearer ${this.#authToken}`;
121
+ const init = {
122
+ method,
123
+ headers
124
+ };
125
+ if (method === "POST" && body !== void 0) init.body = JSON.stringify(body);
126
+ let res;
127
+ try {
128
+ res = await fetch(`${this.#serverOrigin}${path}`, init);
129
+ } catch (cause) {
130
+ return {
131
+ ok: false,
132
+ error: `Network error: ${cause instanceof Error ? cause.message : String(cause)}`
133
+ };
134
+ }
135
+ if (!res.ok) return {
136
+ ok: false,
137
+ error: await res.text().catch(() => res.statusText) || res.statusText,
138
+ status: res.status
139
+ };
140
+ let parsedBody;
141
+ try {
142
+ parsedBody = await res.json();
143
+ } catch (cause) {
144
+ return {
145
+ ok: false,
146
+ error: `Invalid JSON response: ${cause instanceof Error ? cause.message : String(cause)}`,
147
+ status: res.status
148
+ };
149
+ }
150
+ try {
151
+ return {
152
+ ok: true,
153
+ value: parse(parsedBody)
154
+ };
155
+ } catch (cause) {
156
+ return {
157
+ ok: false,
158
+ error: cause instanceof Error ? cause.message : String(cause),
159
+ status: res.status
160
+ };
161
+ }
162
+ }
163
+ };
164
+ /** Parse the bearer token off the manifest's `pairUrl`. Duplicates the
165
+ * logic in `devstack-adapter.ts` rather than importing it because the
166
+ * fork-relay module must remain dependency-free from the adapter
167
+ * layer (it's also consumed by codegen-emitted glue that doesn't pull
168
+ * in the signer adapter bundle). */
169
+ function tokenFromPairUrl(pairedUrl) {
170
+ if (pairedUrl === void 0) return null;
171
+ try {
172
+ const url = new URL(pairedUrl);
173
+ if (url.hash.length > 1) {
174
+ const fromHash = new URLSearchParams(url.hash.slice(1)).get("token");
175
+ if (fromHash !== null && fromHash !== "<redacted>") return fromHash;
176
+ }
177
+ const fromQuery = url.searchParams.get("token");
178
+ if (fromQuery !== null && fromQuery !== "<redacted>") return fromQuery;
179
+ return null;
180
+ } catch {
181
+ return null;
182
+ }
183
+ }
184
+ /**
185
+ * Build a configured {@link ForkRelay} from a devstack manifest, or
186
+ * `null` when fork-mode isn't active (bundled stack, no wallet entry,
187
+ * or the manifest hasn't loaded yet).
188
+ *
189
+ * Codegen-emitted browser glue calls this and forwards the result to
190
+ * `<dev-wallet-panel forkRelay={…}>`.
191
+ */
192
+ function createForkRelayFromManifest(manifest) {
193
+ if (manifest.meta?.runtime !== "forked") return null;
194
+ const wallet = manifest.app?.wallet;
195
+ if (wallet === void 0) return null;
196
+ try {
197
+ return new ForkRelay({
198
+ serverOrigin: wallet.url,
199
+ token: tokenFromPairUrl(wallet.pairUrl)
200
+ });
201
+ } catch {
202
+ return null;
203
+ }
204
+ }
205
+ //#endregion
206
+ export { ForkRelay, ForkRelayHttpError, createForkRelayFromManifest };
207
+
208
+ //# sourceMappingURL=fork-relay.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fork-relay.mjs","names":["#serverOrigin","#authToken","#request"],"sources":["../../src/adapters/fork-relay.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\n//\n// Browser-side relay for sui-fork admin RPCs. A compatible devstack\n// wallet endpoint can proxy a curated subset of the fork-control surface\n// (status read, advance-clock, advance-checkpoint, impersonation slot\n// list) over HTTP so the fork panel does not have to ship a gRPC\n// client into the browser bundle.\n\nexport const FORK_RELAY_HTTP_PATH = {\n\tSTATUS: '/api/v1/devstack/fork/status',\n\tADVANCE_CLOCK: '/api/v1/devstack/fork/advance-clock',\n\tADVANCE_CHECKPOINT: '/api/v1/devstack/fork/advance-checkpoint',\n\tIMPERSONATIONS: '/api/v1/devstack/fork/impersonations',\n} as const;\n\n/** Snapshot of the fork's current head state.\n *\n * Mirrors the body of `ForkControl.getStatus()` minus the unused\n * heartbeat fields. `clockMs` is a Unix-ms timestamp (the value\n * `clock::timestamp_ms()` returns on chain); `checkpoint` is the\n * latest sealed sequence number.\n */\nexport interface ForkStatus {\n\treadonly checkpoint: bigint;\n\treadonly clockMs: bigint;\n\treadonly autoTickMs?: number;\n\t/** Best-effort label of the upstream the fork is pointed at, e.g.\n\t * `'mainnet'` or `'testnet'`. Sourced from the devstack manifest. */\n\treadonly upstream?: string;\n}\n\n/** A single impersonation slot resolved by devstack — typically every\n * address that was passed to `Sui({fork:{impersonate:[…]}})`. */\nexport interface ForkImpersonationSlot {\n\treadonly address: string;\n\treadonly label?: string;\n\t/** Whether the wallet currently routes user-driven tx signing through\n\t * the `executeImpersonated` path for this address. The dev-wallet\n\t * server stores the toggle so it persists across page reloads. */\n\treadonly active: boolean;\n}\n\nexport interface ForkRelayOptions {\n\t/** Wallet-app origin (e.g. `http://localhost:9420`). Same value\n\t * `DevstackSignerAdapter` consumes — read from the manifest entry's\n\t * `app.wallet.url`. */\n\treadonly serverOrigin: string;\n\t/** Bearer token. Same parsing rules as `DevstackSignerAdapter`. */\n\treadonly token?: string | null;\n}\n\nclass ForkRelayHttpError extends Error {\n\tconstructor(\n\t\tpublic readonly status: number,\n\t\tmessage: string,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = 'ForkRelayHttpError';\n\t}\n}\n\n/** Tagged result so callers can render an inline error without throwing. */\nexport type ForkRelayResult<T> =\n\t| { readonly ok: true; readonly value: T }\n\t| { readonly ok: false; readonly error: string; readonly status?: number };\n\nfunction isForkStatusBody(\n\tvalue: unknown,\n): value is { checkpoint: string; clockMs: string; autoTickMs?: number; upstream?: string } {\n\tif (value === null || typeof value !== 'object') return false;\n\tconst v = value as Record<string, unknown>;\n\treturn typeof v.checkpoint === 'string' && typeof v.clockMs === 'string';\n}\n\nfunction isImpersonationsBody(\n\tvalue: unknown,\n): value is { slots: { address: string; label?: string; active: boolean }[] } {\n\tif (value === null || typeof value !== 'object') return false;\n\tconst slots = (value as { slots?: unknown }).slots;\n\treturn Array.isArray(slots);\n}\n\n/**\n * Thin HTTP client for the fork admin relay. Stateless — each call\n * issues a fresh `fetch`. The UI layer (`dev-wallet-fork-panel`)\n * polls `getStatus` on a low cadence and only refreshes after action\n * verbs land, so the lack of caching here is intentional.\n *\n * @example\n * const relay = new ForkRelay({ serverOrigin, token });\n * const status = await relay.getStatus();\n * if (status.ok) console.log(`clock=${status.value.clockMs}ms`);\n */\nexport class ForkRelay {\n\t#serverOrigin: string;\n\t#authToken: string | null;\n\n\tconstructor(options: ForkRelayOptions) {\n\t\ttry {\n\t\t\tnew URL(options.serverOrigin);\n\t\t} catch {\n\t\t\tthrow new Error(`ForkRelay: invalid serverOrigin \"${options.serverOrigin}\"`);\n\t\t}\n\t\tthis.#serverOrigin = options.serverOrigin;\n\t\tthis.#authToken = options.token ?? null;\n\t}\n\n\tget serverOrigin(): string {\n\t\treturn this.#serverOrigin;\n\t}\n\n\tasync getStatus(): Promise<ForkRelayResult<ForkStatus>> {\n\t\treturn this.#request('GET', FORK_RELAY_HTTP_PATH.STATUS, undefined, (body) => {\n\t\t\tif (!isForkStatusBody(body)) {\n\t\t\t\tthrow new Error('Malformed fork status response');\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tcheckpoint: BigInt(body.checkpoint),\n\t\t\t\tclockMs: BigInt(body.clockMs),\n\t\t\t\tautoTickMs: body.autoTickMs,\n\t\t\t\tupstream: body.upstream,\n\t\t\t};\n\t\t});\n\t}\n\n\tasync advanceClock(durationMs: number): Promise<ForkRelayResult<ForkStatus>> {\n\t\tif (!Number.isFinite(durationMs) || durationMs <= 0 || !Number.isInteger(durationMs)) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: `advanceClock: durationMs must be a positive integer (got ${durationMs})`,\n\t\t\t};\n\t\t}\n\t\treturn this.#request('POST', FORK_RELAY_HTTP_PATH.ADVANCE_CLOCK, { durationMs }, (body) => {\n\t\t\tif (!isForkStatusBody(body)) {\n\t\t\t\tthrow new Error('Malformed advance-clock response');\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tcheckpoint: BigInt(body.checkpoint),\n\t\t\t\tclockMs: BigInt(body.clockMs),\n\t\t\t\tautoTickMs: body.autoTickMs,\n\t\t\t\tupstream: body.upstream,\n\t\t\t};\n\t\t});\n\t}\n\n\tasync advanceCheckpoint(count: number): Promise<ForkRelayResult<ForkStatus>> {\n\t\tif (!Number.isFinite(count) || count <= 0 || !Number.isInteger(count)) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: `advanceCheckpoint: count must be a positive integer (got ${count})`,\n\t\t\t};\n\t\t}\n\t\treturn this.#request('POST', FORK_RELAY_HTTP_PATH.ADVANCE_CHECKPOINT, { count }, (body) => {\n\t\t\tif (!isForkStatusBody(body)) {\n\t\t\t\tthrow new Error('Malformed advance-checkpoint response');\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tcheckpoint: BigInt(body.checkpoint),\n\t\t\t\tclockMs: BigInt(body.clockMs),\n\t\t\t\tautoTickMs: body.autoTickMs,\n\t\t\t\tupstream: body.upstream,\n\t\t\t};\n\t\t});\n\t}\n\n\tasync listImpersonations(): Promise<ForkRelayResult<ForkImpersonationSlot[]>> {\n\t\treturn this.#request('GET', FORK_RELAY_HTTP_PATH.IMPERSONATIONS, undefined, (body) => {\n\t\t\tif (!isImpersonationsBody(body)) {\n\t\t\t\tthrow new Error('Malformed impersonations response');\n\t\t\t}\n\t\t\treturn body.slots.map((s) => ({\n\t\t\t\taddress: s.address,\n\t\t\t\tlabel: s.label,\n\t\t\t\tactive: s.active,\n\t\t\t}));\n\t\t});\n\t}\n\n\t/** Toggle the impersonation slot identified by `address`. The server\n\t * is the source of truth — the response carries the resulting slot\n\t * list so the panel re-renders consistently. */\n\tasync setImpersonation(\n\t\taddress: string,\n\t\tactive: boolean,\n\t): Promise<ForkRelayResult<ForkImpersonationSlot[]>> {\n\t\treturn this.#request(\n\t\t\t'POST',\n\t\t\tFORK_RELAY_HTTP_PATH.IMPERSONATIONS,\n\t\t\t{ address, active },\n\t\t\t(body) => {\n\t\t\t\tif (!isImpersonationsBody(body)) {\n\t\t\t\t\tthrow new Error('Malformed impersonations response');\n\t\t\t\t}\n\t\t\t\treturn body.slots.map((s) => ({\n\t\t\t\t\taddress: s.address,\n\t\t\t\t\tlabel: s.label,\n\t\t\t\t\tactive: s.active,\n\t\t\t\t}));\n\t\t\t},\n\t\t);\n\t}\n\n\tasync #request<T>(\n\t\tmethod: 'GET' | 'POST',\n\t\tpath: string,\n\t\tbody: unknown,\n\t\tparse: (raw: unknown) => T,\n\t): Promise<ForkRelayResult<T>> {\n\t\tconst headers: Record<string, string> = { 'Content-Type': 'application/json' };\n\t\tif (this.#authToken !== null) {\n\t\t\theaders['Authorization'] = `Bearer ${this.#authToken}`;\n\t\t}\n\t\tconst init: RequestInit = { method, headers };\n\t\tif (method === 'POST' && body !== undefined) {\n\t\t\tinit.body = JSON.stringify(body);\n\t\t}\n\t\tlet res: Response;\n\t\ttry {\n\t\t\tres = await fetch(`${this.#serverOrigin}${path}`, init);\n\t\t} catch (cause) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: `Network error: ${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t\t};\n\t\t}\n\t\tif (!res.ok) {\n\t\t\tconst text = await res.text().catch(() => res.statusText);\n\t\t\treturn { ok: false, error: text || res.statusText, status: res.status };\n\t\t}\n\t\tlet parsedBody: unknown;\n\t\ttry {\n\t\t\tparsedBody = await res.json();\n\t\t} catch (cause) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: `Invalid JSON response: ${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t\t\tstatus: res.status,\n\t\t\t};\n\t\t}\n\t\ttry {\n\t\t\treturn { ok: true, value: parse(parsedBody) };\n\t\t} catch (cause) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: cause instanceof Error ? cause.message : String(cause),\n\t\t\t\tstatus: res.status,\n\t\t\t};\n\t\t}\n\t}\n}\n\nexport { ForkRelayHttpError };\n\n/** Narrow input mirroring `DevstackAdapterManifest` — enough to\n * discover the wallet endpoint + fork-mode flag without pulling in\n * the full devstack `Manifest` type (no reverse dependency edge,\n * same reasoning as `devstack-adapter.ts`). */\nexport interface ForkRelayManifest {\n\treadonly app?: {\n\t\treadonly wallet?: {\n\t\t\treadonly url: string;\n\t\t\treadonly pairUrl?: string;\n\t\t};\n\t};\n\treadonly meta?: {\n\t\t/** Devstack's `meta.runtime` is `'bundled' | 'forked'`. Only\n\t\t * `'forked'` triggers fork-controls construction. */\n\t\treadonly runtime?: string;\n\t\t/** Optional upstream label (`'mainnet'`, `'testnet'`, …) used\n\t\t * by the panel banner. */\n\t\treadonly upstream?: string;\n\t};\n}\n\n/** Parse the bearer token off the manifest's `pairUrl`. Duplicates the\n * logic in `devstack-adapter.ts` rather than importing it because the\n * fork-relay module must remain dependency-free from the adapter\n * layer (it's also consumed by codegen-emitted glue that doesn't pull\n * in the signer adapter bundle). */\nfunction tokenFromPairUrl(pairedUrl: string | undefined): string | null {\n\tif (pairedUrl === undefined) return null;\n\ttry {\n\t\tconst url = new URL(pairedUrl);\n\t\tif (url.hash.length > 1) {\n\t\t\tconst params = new URLSearchParams(url.hash.slice(1));\n\t\t\tconst fromHash = params.get('token');\n\t\t\tif (fromHash !== null && fromHash !== '<redacted>') return fromHash;\n\t\t}\n\t\tconst fromQuery = url.searchParams.get('token');\n\t\tif (fromQuery !== null && fromQuery !== '<redacted>') return fromQuery;\n\t\treturn null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Build a configured {@link ForkRelay} from a devstack manifest, or\n * `null` when fork-mode isn't active (bundled stack, no wallet entry,\n * or the manifest hasn't loaded yet).\n *\n * Codegen-emitted browser glue calls this and forwards the result to\n * `<dev-wallet-panel forkRelay={…}>`.\n */\nexport function createForkRelayFromManifest(manifest: ForkRelayManifest): ForkRelay | null {\n\tif (manifest.meta?.runtime !== 'forked') return null;\n\tconst wallet = manifest.app?.wallet;\n\tif (wallet === undefined) return null;\n\ttry {\n\t\treturn new ForkRelay({\n\t\t\tserverOrigin: wallet.url,\n\t\t\ttoken: tokenFromPairUrl(wallet.pairUrl),\n\t\t});\n\t} catch {\n\t\t// `new URL(…)` failure surfaces as a thrown Error — swallow it\n\t\t// here rather than propagating, the calling app shouldn't\n\t\t// blow up just because the manifest's wallet URL is malformed.\n\t\treturn null;\n\t}\n}\n"],"mappings":"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"}