@thru/wallet 0.2.25 → 0.2.28

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 (47) hide show
  1. package/README.md +1 -0
  2. package/dist/{BrowserSDK-CpRFiJsW.d.ts → BrowserSDK-CRQTOT8S.d.ts} +178 -3
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.js +376 -12
  5. package/dist/index.js.map +1 -1
  6. package/dist/native/react/transparent.d.ts +104 -0
  7. package/dist/native/react/transparent.js +2210 -0
  8. package/dist/native/react/transparent.js.map +1 -0
  9. package/dist/native/react.d.ts +5 -90
  10. package/dist/native/react.js +765 -32
  11. package/dist/native/react.js.map +1 -1
  12. package/dist/native.d.ts +105 -1
  13. package/dist/native.js +521 -31
  14. package/dist/native.js.map +1 -1
  15. package/dist/react-ui.js +5 -0
  16. package/dist/react-ui.js.map +1 -1
  17. package/dist/react.d.ts +2 -2
  18. package/dist/react.js +376 -12
  19. package/dist/react.js.map +1 -1
  20. package/package.json +8 -2
  21. package/src/BrowserSDK.ts +32 -1
  22. package/src/encoding.ts +39 -0
  23. package/src/index.ts +5 -1
  24. package/src/interfaces/IThruChain.ts +50 -1
  25. package/src/interfaces/types.ts +52 -0
  26. package/src/native/NativeSDK.test.ts +200 -1
  27. package/src/native/NativeSDK.ts +124 -10
  28. package/src/native/index.ts +12 -0
  29. package/src/native/provider/NativeProvider.ts +106 -5
  30. package/src/native/provider/WebViewBridge.test.ts +22 -1
  31. package/src/native/provider/WebViewBridge.ts +17 -7
  32. package/src/native/provider/chains/ThruChain.ts +215 -5
  33. package/src/native/react/ThruContext.ts +3 -1
  34. package/src/native/react/ThruProvider.tsx +25 -0
  35. package/src/native/react/ThruTransparentWalletBridge.tsx +281 -0
  36. package/src/native/react/hooks/useWallet.ts +12 -1
  37. package/src/native/react/index.ts +11 -0
  38. package/src/native/react/transparent.ts +35 -0
  39. package/src/protocol/postMessage.ts +127 -2
  40. package/src/provider/EmbeddedProvider.ts +7 -1
  41. package/src/provider/IframeManager.test.ts +18 -0
  42. package/src/provider/IframeManager.ts +8 -1
  43. package/src/provider/chains/ThruChain.ts +210 -4
  44. package/src/provider/types/messages.ts +16 -0
  45. package/src/react/index.ts +6 -0
  46. package/src/signing-sessions.test.ts +182 -0
  47. package/src/signing-sessions.ts +204 -0
package/dist/native.js CHANGED
@@ -42,14 +42,20 @@ var ThruTransactionEncoding = {
42
42
  // src/protocol/postMessage.ts
43
43
  var POST_MESSAGE_REQUEST_TYPES = {
44
44
  CONNECT: "connect",
45
+ CREATE_ACCOUNT: "createAccount",
45
46
  DISCONNECT: "disconnect",
46
47
  SIGN_MESSAGE: "signMessage",
47
48
  SIGN_TRANSACTION: "signTransaction",
49
+ SIGN_PASSKEY_CHALLENGE: "signPasskeyChallenge",
48
50
  GET_ACCOUNTS: "getAccounts",
49
51
  GET_CONNECTION_STATE: "getConnectionState",
50
52
  GET_SIGNING_CONTEXT: "getSigningContext",
51
53
  SELECT_ACCOUNT: "selectAccount",
52
- MANAGE_ACCOUNTS: "manageAccounts"
54
+ MANAGE_ACCOUNTS: "manageAccounts",
55
+ CREATE_SIGNING_SESSION: "createSigningSession",
56
+ CREATE_SIGNING_SESSION_INSTRUCTION: "createSigningSessionInstruction",
57
+ CONFIRM_SIGNING_SESSION: "confirmSigningSession",
58
+ REVOKE_SIGNING_SESSION: "revokeSigningSession"
53
59
  };
54
60
  var EMBEDDED_PROVIDER_EVENTS = {
55
61
  CONNECT_START: "connect_start",
@@ -91,12 +97,195 @@ function normalizeConnectionStateResult(result) {
91
97
  return normalizeWalletAccountResult(result);
92
98
  }
93
99
 
100
+ // src/encoding.ts
101
+ var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
102
+ var BASE64_LOOKUP = new Map(
103
+ [...BASE64_ALPHABET].map((char, index) => [char, index])
104
+ );
105
+ function base64ToBytes(value) {
106
+ const normalized = value.replace(/\s+/g, "");
107
+ if (normalized.length === 0) return new Uint8Array();
108
+ if (normalized.length % 4 === 1) {
109
+ throw new Error("Invalid base64 data");
110
+ }
111
+ const padded = normalized.padEnd(
112
+ normalized.length + (4 - normalized.length % 4) % 4,
113
+ "="
114
+ );
115
+ const padding = padded.endsWith("==") ? 2 : padded.endsWith("=") ? 1 : 0;
116
+ const output = new Uint8Array(padded.length / 4 * 3 - padding);
117
+ let outIdx = 0;
118
+ for (let i = 0; i < padded.length; i += 4) {
119
+ const chars = padded.slice(i, i + 4);
120
+ const a = BASE64_LOOKUP.get(chars[0]);
121
+ const b = BASE64_LOOKUP.get(chars[1]);
122
+ const c = chars[2] === "=" ? 0 : BASE64_LOOKUP.get(chars[2]);
123
+ const d = chars[3] === "=" ? 0 : BASE64_LOOKUP.get(chars[3]);
124
+ if (a === void 0 || b === void 0 || c === void 0 || d === void 0) {
125
+ throw new Error("Invalid base64 data");
126
+ }
127
+ const chunk = a << 18 | b << 12 | c << 6 | d;
128
+ if (outIdx < output.length) output[outIdx++] = chunk >> 16 & 255;
129
+ if (outIdx < output.length) output[outIdx++] = chunk >> 8 & 255;
130
+ if (outIdx < output.length) output[outIdx++] = chunk & 255;
131
+ }
132
+ return output;
133
+ }
134
+
135
+ // src/signing-sessions.ts
136
+ var STORAGE_VERSION = 1;
137
+ var KEY_PREFIX = "thru.wallet.signing-sessions.v1";
138
+ function encodeKeyPart(input) {
139
+ return encodeURIComponent(input).replace(
140
+ /[!'()*]/g,
141
+ (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`
142
+ );
143
+ }
144
+ function nowSeconds() {
145
+ return Math.floor(Date.now() / 1e3);
146
+ }
147
+ function resolveSigningSessionStorageKey(params) {
148
+ if (params.storageKey) return params.storageKey;
149
+ return `${KEY_PREFIX}:${encodeKeyPart(params.walletOrigin)}:${encodeKeyPart(params.appOrigin)}`;
150
+ }
151
+ function normalizeExpiresAt(value, label = "expiresAt") {
152
+ if (value instanceof Date) {
153
+ const millis = value.getTime();
154
+ if (!Number.isFinite(millis)) throw new Error(`${label} must be a valid Date`);
155
+ return Math.floor(millis / 1e3);
156
+ }
157
+ if (typeof value === "bigint") {
158
+ if (value < 0n || value > BigInt(Number.MAX_SAFE_INTEGER)) {
159
+ throw new Error(`${label} must fit in a JavaScript safe integer`);
160
+ }
161
+ return Number(value);
162
+ }
163
+ if (typeof value === "string") {
164
+ const trimmed = value.trim();
165
+ if (!/^\d+$/.test(trimmed)) {
166
+ throw new Error(`${label} must be a Unix timestamp in seconds`);
167
+ }
168
+ return normalizeExpiresAt(BigInt(trimmed), label);
169
+ }
170
+ if (!Number.isFinite(value) || value < 0) {
171
+ throw new Error(`${label} must be a finite positive Unix timestamp`);
172
+ }
173
+ return Math.floor(value);
174
+ }
175
+ function resolveSessionExpirySeconds(options) {
176
+ const hasDuration = options.durationSeconds !== void 0;
177
+ const hasExpiresAt = options.expiresAt !== void 0;
178
+ if (hasDuration === hasExpiresAt) {
179
+ throw new Error("Provide exactly one of durationSeconds or expiresAt");
180
+ }
181
+ if (hasDuration) {
182
+ const duration = options.durationSeconds;
183
+ if (typeof duration !== "number" || !Number.isFinite(duration) || duration <= 0) {
184
+ throw new Error("durationSeconds must be a positive number");
185
+ }
186
+ return nowSeconds() + Math.floor(duration);
187
+ }
188
+ return normalizeExpiresAt(options.expiresAt, "expiresAt");
189
+ }
190
+ function assertSigningSessionWalletAccountIdx(walletAccountIdx) {
191
+ if (!Number.isInteger(walletAccountIdx) || walletAccountIdx < 2 || walletAccountIdx > 65535) {
192
+ throw new Error("walletAccountIdx must be an account index between 2 and 65535");
193
+ }
194
+ }
195
+ function normalizeDescriptor(descriptor) {
196
+ return {
197
+ id: descriptor.id,
198
+ walletAddress: descriptor.walletAddress,
199
+ publicKey: descriptor.publicKey,
200
+ authIdx: Number(descriptor.authIdx),
201
+ expiresAt: normalizeExpiresAt(descriptor.expiresAt, "descriptor.expiresAt"),
202
+ createdAt: normalizeExpiresAt(descriptor.createdAt, "descriptor.createdAt")
203
+ };
204
+ }
205
+ function isActive(descriptor) {
206
+ return nowSeconds() < descriptor.expiresAt;
207
+ }
208
+ var SigningSessionDescriptorStore = class {
209
+ constructor(storage, key) {
210
+ this.storage = storage;
211
+ this.key = key;
212
+ }
213
+ async list() {
214
+ const sessions = await this.read();
215
+ const active = sessions.filter(isActive);
216
+ if (active.length !== sessions.length) {
217
+ await this.write(active);
218
+ }
219
+ return active;
220
+ }
221
+ async get(id) {
222
+ const sessions = await this.list();
223
+ return sessions.find((session) => session.id === id) ?? null;
224
+ }
225
+ async save(descriptor) {
226
+ const normalized = normalizeDescriptor(descriptor);
227
+ const sessions = (await this.list()).filter((session) => session.id !== normalized.id);
228
+ sessions.push(normalized);
229
+ await this.write(sessions);
230
+ }
231
+ async saveReplacingWalletSessions(descriptor) {
232
+ const normalized = normalizeDescriptor(descriptor);
233
+ const sessions = (await this.list()).filter(
234
+ (session) => session.id === normalized.id || session.walletAddress !== normalized.walletAddress
235
+ );
236
+ const withoutCurrent = sessions.filter((session) => session.id !== normalized.id);
237
+ withoutCurrent.push(normalized);
238
+ await this.write(withoutCurrent);
239
+ }
240
+ async remove(id) {
241
+ const sessions = (await this.list()).filter((session) => session.id !== id);
242
+ if (sessions.length === 0) {
243
+ await this.storage.removeItem(this.key);
244
+ return;
245
+ }
246
+ await this.write(sessions);
247
+ }
248
+ async read() {
249
+ const raw = await this.storage.getItem(this.key);
250
+ if (!raw) return [];
251
+ try {
252
+ const parsed = JSON.parse(raw);
253
+ if (parsed.version !== STORAGE_VERSION || !Array.isArray(parsed.sessions)) {
254
+ await this.storage.removeItem(this.key);
255
+ return [];
256
+ }
257
+ return parsed.sessions.map(normalizeDescriptor);
258
+ } catch {
259
+ await this.storage.removeItem(this.key);
260
+ return [];
261
+ }
262
+ }
263
+ async write(sessions) {
264
+ const payload = {
265
+ version: STORAGE_VERSION,
266
+ sessions: sessions.map(normalizeDescriptor)
267
+ };
268
+ await this.storage.setItem(this.key, JSON.stringify(payload));
269
+ }
270
+ };
271
+
94
272
  // src/native/provider/chains/ThruChain.ts
273
+ function descriptorFromWire(session) {
274
+ return {
275
+ id: session.id,
276
+ walletAddress: session.walletAddress,
277
+ publicKey: session.publicKey,
278
+ authIdx: session.authIdx,
279
+ expiresAt: Number(BigInt(session.expiresAt)),
280
+ createdAt: Number(BigInt(session.createdAt))
281
+ };
282
+ }
95
283
  var NativeThruChain = class {
96
- constructor(bridge, provider, origin) {
284
+ constructor(bridge, provider, origin, signingSessions) {
97
285
  this.bridge = bridge;
98
286
  this.provider = provider;
99
287
  this.origin = origin;
288
+ this.signingSessions = signingSessions;
100
289
  }
101
290
  get connected() {
102
291
  return this.provider.isConnected();
@@ -114,7 +303,7 @@ var NativeThruChain = class {
114
303
  await this.provider.disconnect();
115
304
  }
116
305
  async getSigningContext() {
117
- if (!this.provider.isConnected()) {
306
+ if (!this.provider.isConnected() && !this.provider.isTransparent()) {
118
307
  throw new Error("Wallet not connected");
119
308
  }
120
309
  const response = await this.bridge.sendMessage({
@@ -125,33 +314,179 @@ var NativeThruChain = class {
125
314
  return response.result.signingContext;
126
315
  }
127
316
  async signTransaction(transaction) {
128
- if (!this.provider.isConnected()) {
317
+ const signingSessionId = transaction.signingSessionId;
318
+ if (!signingSessionId && !this.provider.isConnected() && !this.provider.isTransparent()) {
129
319
  throw new Error("Wallet not connected");
130
320
  }
131
- this.provider.requestShow();
321
+ const session = signingSessionId ? await this.requireSigningSession(signingSessionId) : null;
322
+ const shouldShowWallet = !signingSessionId;
323
+ if (shouldShowWallet) {
324
+ await this.provider.requestShow();
325
+ }
132
326
  try {
133
327
  const response = await this.bridge.sendMessage({
134
328
  id: createRequestId(),
135
329
  type: POST_MESSAGE_REQUEST_TYPES.SIGN_TRANSACTION,
136
330
  payload: {
137
- walletAddress: transaction.walletAddress,
331
+ walletAddress: transaction.walletAddress ?? session?.walletAddress,
138
332
  programAddress: transaction.programAddress,
139
333
  instructionData: transaction.instructionData,
140
334
  readWriteAddresses: transaction.readWriteAddresses,
141
335
  readOnlyAddresses: transaction.readOnlyAddresses,
142
- review: transaction.review
336
+ review: transaction.review,
337
+ signingSessionId
143
338
  },
144
339
  origin: this.origin
145
340
  });
146
341
  return response.result.signedTransaction;
342
+ } finally {
343
+ if (shouldShowWallet) {
344
+ this.provider.requestHide();
345
+ }
346
+ }
347
+ }
348
+ async signPasskeyChallenge(challenge) {
349
+ if (!this.provider.isConnected() && !this.provider.isTransparent()) {
350
+ throw new Error("Wallet not connected");
351
+ }
352
+ await this.provider.requestShow();
353
+ try {
354
+ const response = await this.bridge.sendMessage({
355
+ id: createRequestId(),
356
+ type: POST_MESSAGE_REQUEST_TYPES.SIGN_PASSKEY_CHALLENGE,
357
+ payload: {
358
+ challenge: challenge.challenge,
359
+ walletAddress: challenge.walletAddress
360
+ },
361
+ origin: this.origin
362
+ });
363
+ return response.result;
364
+ } finally {
365
+ this.provider.requestHide();
366
+ }
367
+ }
368
+ async createSigningSession(options) {
369
+ if (!this.provider.isConnected()) {
370
+ throw new Error("Wallet not connected");
371
+ }
372
+ if (!this.signingSessions) {
373
+ throw new Error("NativeSDKStorage is required for signing sessions");
374
+ }
375
+ const expiresAt = resolveSessionExpirySeconds(options);
376
+ await this.provider.requestShow();
377
+ try {
378
+ const response = await this.bridge.sendMessage({
379
+ id: createRequestId(),
380
+ type: POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION,
381
+ payload: {
382
+ walletAddress: options.walletAddress,
383
+ expiresAt: String(expiresAt),
384
+ review: options.review
385
+ },
386
+ origin: this.origin
387
+ });
388
+ const descriptor = descriptorFromWire(response.result.session);
389
+ await this.signingSessions.saveReplacingWalletSessions(descriptor);
390
+ return this.toSigningSession(descriptor);
147
391
  } finally {
148
392
  this.provider.requestHide();
149
393
  }
150
394
  }
395
+ async createSigningSessionInstruction(options) {
396
+ if (!this.provider.isConnected()) {
397
+ throw new Error("Wallet not connected");
398
+ }
399
+ if (!this.signingSessions) {
400
+ throw new Error("NativeSDKStorage is required for signing sessions");
401
+ }
402
+ const expiresAt = resolveSessionExpirySeconds(options);
403
+ assertSigningSessionWalletAccountIdx(options.walletAccountIdx);
404
+ const response = await this.bridge.sendMessage({
405
+ id: createRequestId(),
406
+ type: POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION_INSTRUCTION,
407
+ payload: {
408
+ walletAddress: options.walletAddress,
409
+ expiresAt: String(expiresAt),
410
+ walletAccountIdx: options.walletAccountIdx
411
+ },
412
+ origin: this.origin
413
+ });
414
+ const descriptor = descriptorFromWire(response.result.session);
415
+ return {
416
+ session: this.toSigningSession(descriptor),
417
+ programAddress: response.result.programAddress,
418
+ instructionData: base64ToBytes(response.result.instructionData)
419
+ };
420
+ }
421
+ async confirmSigningSession(id) {
422
+ if (!this.provider.isConnected()) {
423
+ throw new Error("Wallet not connected");
424
+ }
425
+ if (!this.signingSessions) {
426
+ throw new Error("NativeSDKStorage is required for signing sessions");
427
+ }
428
+ const response = await this.bridge.sendMessage({
429
+ id: createRequestId(),
430
+ type: POST_MESSAGE_REQUEST_TYPES.CONFIRM_SIGNING_SESSION,
431
+ payload: { sessionId: id },
432
+ origin: this.origin
433
+ });
434
+ const descriptor = descriptorFromWire(response.result.session);
435
+ await this.signingSessions.saveReplacingWalletSessions(descriptor);
436
+ return this.toSigningSession(descriptor);
437
+ }
438
+ async getSigningSession(id) {
439
+ if (!this.signingSessions) return null;
440
+ const descriptor = await this.signingSessions.get(id);
441
+ return descriptor ? this.toSigningSession(descriptor) : null;
442
+ }
443
+ async getSigningSessions() {
444
+ if (!this.signingSessions) return [];
445
+ return (await this.signingSessions.list()).map(
446
+ (descriptor) => this.toSigningSession(descriptor)
447
+ );
448
+ }
449
+ async revokeSigningSession(id) {
450
+ try {
451
+ await this.bridge.sendMessage({
452
+ id: createRequestId(),
453
+ type: POST_MESSAGE_REQUEST_TYPES.REVOKE_SIGNING_SESSION,
454
+ payload: { sessionId: id },
455
+ origin: this.origin
456
+ });
457
+ } finally {
458
+ await this.signingSessions?.remove(id);
459
+ }
460
+ }
461
+ async requireSigningSession(id) {
462
+ if (!this.signingSessions) {
463
+ throw new Error("NativeSDKStorage is required for signing sessions");
464
+ }
465
+ const session = await this.signingSessions.get(id);
466
+ if (!session) {
467
+ throw new Error("Signing session is not known to this app");
468
+ }
469
+ return session;
470
+ }
471
+ toSigningSession(descriptor) {
472
+ return {
473
+ ...descriptor,
474
+ signTransaction: (transaction) => this.signTransaction({
475
+ ...transaction,
476
+ walletAddress: transaction.walletAddress ?? descriptor.walletAddress,
477
+ signingSessionId: descriptor.id
478
+ }),
479
+ revoke: () => this.revokeSigningSession(descriptor.id),
480
+ toJSON: () => ({ ...descriptor })
481
+ };
482
+ }
151
483
  };
152
484
 
153
485
  // src/native/provider/WebViewBridge.ts
154
- var PRODUCTION_WALLET_ORIGINS = ["https://wallet.thru.org"];
486
+ var PRODUCTION_WALLET_ORIGINS = [
487
+ "https://wallet.thru.org",
488
+ "https://wallet.tid.sh"
489
+ ];
155
490
  function isDevelopmentBuild() {
156
491
  const runtime = globalThis;
157
492
  const devFlag = runtime.__DEV__;
@@ -189,14 +524,23 @@ function validateWalletOrigin(walletUrl) {
189
524
  );
190
525
  }
191
526
  }
527
+ function isNativeEmbeddedWalletPath(pathname) {
528
+ const normalized = pathname.replace(/\/+$/, "") || "/";
529
+ return normalized === "/embedded/native" || normalized.startsWith("/embedded/native/");
530
+ }
192
531
  var READY_TIMEOUT_MS = 1e4;
193
532
  var SLOW_REQUEST_TIMEOUT_MS = 5 * 60 * 1e3;
194
533
  var FAST_REQUEST_TIMEOUT_MS = 30 * 1e3;
195
534
  var SLOW_REQUEST_TYPES = /* @__PURE__ */ new Set([
196
535
  POST_MESSAGE_REQUEST_TYPES.CONNECT,
536
+ POST_MESSAGE_REQUEST_TYPES.CREATE_ACCOUNT,
197
537
  POST_MESSAGE_REQUEST_TYPES.SIGN_MESSAGE,
198
538
  POST_MESSAGE_REQUEST_TYPES.SIGN_TRANSACTION,
199
- POST_MESSAGE_REQUEST_TYPES.MANAGE_ACCOUNTS
539
+ POST_MESSAGE_REQUEST_TYPES.SIGN_PASSKEY_CHALLENGE,
540
+ POST_MESSAGE_REQUEST_TYPES.MANAGE_ACCOUNTS,
541
+ POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION,
542
+ POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION_INSTRUCTION,
543
+ POST_MESSAGE_REQUEST_TYPES.CONFIRM_SIGNING_SESSION
200
544
  ]);
201
545
  var WebViewBridge = class {
202
546
  constructor(options) {
@@ -218,7 +562,7 @@ var WebViewBridge = class {
218
562
  */
219
563
  getIframeSrc() {
220
564
  const url = new URL(this.walletUrl);
221
- if (!url.pathname.endsWith("/native")) {
565
+ if (!isNativeEmbeddedWalletPath(url.pathname)) {
222
566
  url.pathname = `${url.pathname.replace(/\/$/, "")}/native`;
223
567
  }
224
568
  url.searchParams.set("tn_frame_id", this.frameId);
@@ -298,14 +642,11 @@ var WebViewBridge = class {
298
642
  }
299
643
  });
300
644
  const script = `try {
301
- var msg = ${JSON.stringify(request)};
645
+ var msg = ${JSON.stringify({ ...request, frameId: this.frameId })};
302
646
  if (window.__pushIn) {
303
647
  window.__pushIn(msg);
304
648
  } else {
305
- window.dispatchEvent(new MessageEvent('message', {
306
- data: msg,
307
- origin: msg.origin || ''
308
- }));
649
+ window.postMessage(msg, window.location.origin);
309
650
  }
310
651
  } catch (e) {} ; true;`;
311
652
  this.webView.injectJavaScript(script);
@@ -381,11 +722,13 @@ var WebViewBridge = class {
381
722
  // src/native/provider/NativeProvider.ts
382
723
  var DEFAULT_WALLET_URL = "https://wallet.thru.org/embedded/native";
383
724
  var DEFAULT_ORIGIN = "thru-mobile://app";
725
+ var TRANSPARENT_FOCUS_SETTLE_MS = 500;
384
726
  var NativeProvider = class {
385
727
  constructor(config = {}) {
386
728
  this.connected = false;
387
729
  this.accounts = [];
388
730
  this.selectedAccount = null;
731
+ this.isSurfaceShown = false;
389
732
  this.eventListeners = /* @__PURE__ */ new Map();
390
733
  /** Pass through the WebView's `onMessage` event handler. */
391
734
  this.onMessage = (event) => {
@@ -393,8 +736,12 @@ var NativeProvider = class {
393
736
  };
394
737
  const walletUrl = config.walletUrl ?? DEFAULT_WALLET_URL;
395
738
  this.origin = config.origin ?? DEFAULT_ORIGIN;
739
+ this.transparent = config.walletExperience === "transparent";
396
740
  this.bridge = new WebViewBridge({ walletUrl });
397
741
  this.bridge.onEvent = (eventType, payload) => {
742
+ if (this.transparent && eventType === EMBEDDED_PROVIDER_EVENTS.UI_SHOW) {
743
+ return;
744
+ }
398
745
  this.emit(eventType, payload);
399
746
  if (eventType === EMBEDDED_PROVIDER_EVENTS.UI_SHOW) {
400
747
  this.requestShow();
@@ -412,7 +759,12 @@ var NativeProvider = class {
412
759
  };
413
760
  const addressTypes = config.addressTypes ?? [AddressType.THRU];
414
761
  if (addressTypes.includes(AddressType.THRU)) {
415
- this._thruChain = new NativeThruChain(this.bridge, this, this.origin);
762
+ this._thruChain = new NativeThruChain(
763
+ this.bridge,
764
+ this,
765
+ this.origin,
766
+ config.signingSessions
767
+ );
416
768
  }
417
769
  }
418
770
  /** Hand the bridge a WebView ref. Required before connect/sign. */
@@ -437,12 +789,27 @@ var NativeProvider = class {
437
789
  async initialize() {
438
790
  await this.bridge.awaitReady();
439
791
  }
440
- /** Open the wallet UI (called internally; also exposed for host). */
441
- requestShow() {
792
+ /** Open or focus the wallet host surface. Transparent hosts use this
793
+ to give WKWebView a focused document for WebAuthn without showing
794
+ wallet UI. */
795
+ async requestShow() {
796
+ if (this.transparent) {
797
+ if (!this.isSurfaceShown) {
798
+ this.isSurfaceShown = true;
799
+ this.onShowRequested?.();
800
+ }
801
+ await new Promise(
802
+ (resolve) => setTimeout(resolve, TRANSPARENT_FOCUS_SETTLE_MS)
803
+ );
804
+ return;
805
+ }
806
+ if (this.isSurfaceShown) return;
807
+ this.isSurfaceShown = true;
442
808
  this.onShowRequested?.();
443
809
  }
444
810
  /** Close the wallet UI (called internally; also exposed for host). */
445
811
  requestHide() {
812
+ this.isSurfaceShown = false;
446
813
  this.onHideRequested?.();
447
814
  }
448
815
  /** Reject pending requests after a user-driven native sheet dismiss. */
@@ -452,7 +819,7 @@ var NativeProvider = class {
452
819
  async connect(options) {
453
820
  this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT_START, {});
454
821
  try {
455
- this.requestShow();
822
+ await this.requestShow();
456
823
  const payload = {};
457
824
  if (options?.metadata) payload.metadata = options.metadata;
458
825
  if (options?.preferredAccountAddress) {
@@ -466,6 +833,9 @@ var NativeProvider = class {
466
833
  origin: this.origin
467
834
  });
468
835
  const result = normalizeWalletAccountResult(response.result);
836
+ if (!result.selectedAccount) {
837
+ throw new Error("Wallet did not return an account");
838
+ }
469
839
  this.connected = true;
470
840
  this.accounts = result.accounts;
471
841
  this.selectedAccount = result.selectedAccount;
@@ -478,6 +848,52 @@ var NativeProvider = class {
478
848
  throw error;
479
849
  }
480
850
  }
851
+ async createAccount(options) {
852
+ try {
853
+ await this.requestShow();
854
+ const payload = {};
855
+ if (options?.accountName) payload.accountName = options.accountName;
856
+ if (options?.metadata) payload.metadata = options.metadata;
857
+ const response = await this.bridge.sendMessage({
858
+ id: createRequestId(),
859
+ type: POST_MESSAGE_REQUEST_TYPES.CREATE_ACCOUNT,
860
+ payload,
861
+ origin: this.origin
862
+ });
863
+ const normalized = normalizeWalletAccountResult(
864
+ response.result,
865
+ response.result.selectedAccount ?? response.result.account
866
+ );
867
+ const selectedAccount = normalized.selectedAccount ?? response.result.account;
868
+ if (!selectedAccount) {
869
+ throw new Error("Wallet did not return a created account");
870
+ }
871
+ const result = {
872
+ ...response.result,
873
+ accounts: normalized.accounts,
874
+ selectedAccount,
875
+ account: selectedAccount
876
+ };
877
+ this.connected = true;
878
+ this.accounts = result.accounts;
879
+ this.selectedAccount = result.selectedAccount;
880
+ this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT, {
881
+ accounts: result.accounts,
882
+ selectedAccount: result.selectedAccount,
883
+ status: "completed",
884
+ metadata: options?.metadata
885
+ });
886
+ this.emit(EMBEDDED_PROVIDER_EVENTS.ACCOUNT_CHANGED, {
887
+ account: result.selectedAccount
888
+ });
889
+ this.requestHide();
890
+ return result;
891
+ } catch (error) {
892
+ this.requestHide();
893
+ this.emit(EMBEDDED_PROVIDER_EVENTS.ERROR, { error });
894
+ throw error;
895
+ }
896
+ }
481
897
  async getConnectionState(options) {
482
898
  const payload = {};
483
899
  if (options?.metadata) payload.metadata = options.metadata;
@@ -526,6 +942,9 @@ var NativeProvider = class {
526
942
  isConnected() {
527
943
  return this.connected;
528
944
  }
945
+ isTransparent() {
946
+ return this.transparent;
947
+ }
529
948
  hydrateConnection(result, selectedAccountAddress) {
530
949
  const selectedAccount = resolveWalletAccountByAddress(result.accounts, selectedAccountAddress) ?? result.selectedAccount ?? null;
531
950
  const normalized = normalizeWalletAccountResult(result, selectedAccount);
@@ -560,7 +979,7 @@ var NativeProvider = class {
560
979
  async manageAccounts() {
561
980
  if (!this.connected) throw new Error("Wallet not connected");
562
981
  try {
563
- this.requestShow();
982
+ await this.requestShow();
564
983
  const response = await this.bridge.sendMessage({
565
984
  id: createRequestId(),
566
985
  type: POST_MESSAGE_REQUEST_TYPES.MANAGE_ACCOUNTS,
@@ -622,6 +1041,9 @@ var NativeProvider = class {
622
1041
  };
623
1042
  var DEFAULT_STORAGE_KEY = "thru.native-sdk.connection.v1";
624
1043
  var SELECTED_ACCOUNT_STORAGE_KEY_SUFFIX = ".selected-account.v1";
1044
+ var SIGNING_SESSION_STORAGE_KEY_SUFFIX = ".signing-sessions.v1";
1045
+ var DEFAULT_NATIVE_WALLET_URL = "https://wallet.thru.org/embedded/native";
1046
+ var DEFAULT_TRANSPARENT_WALLET_URL = "https://wallet.thru.org/embedded/native/transparent";
625
1047
  var CHECKING_WALLET_AVAILABILITY = {
626
1048
  status: "checking",
627
1049
  isAuthorized: false,
@@ -634,6 +1056,17 @@ var CHECKING_WALLET_AVAILABILITY = {
634
1056
  metadata: null,
635
1057
  error: null
636
1058
  };
1059
+ function completeAppMetadata(metadata) {
1060
+ if (!metadata?.appId || !metadata.appName || !metadata.appUrl) {
1061
+ return void 0;
1062
+ }
1063
+ return {
1064
+ appId: metadata.appId,
1065
+ appName: metadata.appName,
1066
+ appUrl: metadata.appUrl,
1067
+ ...metadata.imageUrl ? { imageUrl: metadata.imageUrl } : {}
1068
+ };
1069
+ }
637
1070
  var NativeSDK = class {
638
1071
  constructor(config = {}) {
639
1072
  this.eventListeners = /* @__PURE__ */ new Map();
@@ -652,10 +1085,25 @@ var NativeSDK = class {
652
1085
  this.storageKey = config.storageKey ?? DEFAULT_STORAGE_KEY;
653
1086
  this.selectedAccountStorageKey = config.selectedAccountStorageKey ?? `${this.storageKey}${SELECTED_ACCOUNT_STORAGE_KEY_SUFFIX}`;
654
1087
  this.iosWebViewMode = config.iosWebViewMode ?? "shell-iframe";
1088
+ this.walletExperience = config.walletExperience ?? "standard";
1089
+ this.defaultMetadata = config.metadata;
1090
+ const walletUrl = config.walletUrl ?? (this.walletExperience === "transparent" ? DEFAULT_TRANSPARENT_WALLET_URL : DEFAULT_NATIVE_WALLET_URL);
1091
+ const walletOrigin = new URL(walletUrl).origin;
1092
+ const signingSessions = this.storage ? new SigningSessionDescriptorStore(
1093
+ this.storage,
1094
+ resolveSigningSessionStorageKey({
1095
+ walletOrigin,
1096
+ appOrigin: this.origin,
1097
+ storageKey: config.signingSessionStorageKey ?? `${this.storageKey}${SIGNING_SESSION_STORAGE_KEY_SUFFIX}`
1098
+ })
1099
+ ) : void 0;
655
1100
  this.provider = new NativeProvider({
656
- walletUrl: config.walletUrl,
1101
+ walletUrl,
657
1102
  origin: this.origin,
658
- addressTypes: config.addressTypes ?? [AddressType.THRU]
1103
+ metadata: this.defaultMetadata ? this.resolveMetadata(this.defaultMetadata) : void 0,
1104
+ addressTypes: config.addressTypes ?? [AddressType.THRU],
1105
+ signingSessions,
1106
+ walletExperience: this.walletExperience
659
1107
  });
660
1108
  this.setupEventForwarding();
661
1109
  }
@@ -706,10 +1154,10 @@ var NativeSDK = class {
706
1154
  this.emit("connect", { status: "connecting" });
707
1155
  const inFlight = (async () => {
708
1156
  try {
709
- this.provider.requestShow();
1157
+ await this.provider.requestShow();
710
1158
  if (!this.initialized) await this.initialize();
711
1159
  const metadata = this.resolveMetadata(options?.metadata);
712
- const preferredAccountAddress = isAccountSwitch ? null : await this.readSelectedAccountAddress();
1160
+ const preferredAccountAddress = isAccountSwitch ? null : options?.preferredAccountAddress ?? await this.readSelectedAccountAddress();
713
1161
  const providerOptions = metadata || preferredAccountAddress || options?.intent ? {
714
1162
  ...metadata ? { metadata } : {},
715
1163
  ...preferredAccountAddress ? { preferredAccountAddress } : {},
@@ -762,11 +1210,52 @@ var NativeSDK = class {
762
1210
  ...options.intent ? { intent: options.intent } : {}
763
1211
  });
764
1212
  }
1213
+ async createAccount(options = {}) {
1214
+ this.emit("connect", { status: "connecting" });
1215
+ try {
1216
+ await this.provider.requestShow();
1217
+ if (!this.initialized) await this.initialize();
1218
+ const metadata = this.resolveMetadata(options.metadata);
1219
+ const result = await this.provider.createAccount({
1220
+ ...options.accountName ? { accountName: options.accountName } : {},
1221
+ ...metadata ? { metadata } : {}
1222
+ });
1223
+ const selectedAccount = result.selectedAccount ?? result.account;
1224
+ const activeResult = {
1225
+ ...result,
1226
+ accounts: this.provider.getAccounts(),
1227
+ selectedAccount,
1228
+ account: selectedAccount
1229
+ };
1230
+ const completedResult = {
1231
+ accounts: activeResult.accounts,
1232
+ selectedAccount: activeResult.selectedAccount,
1233
+ status: "completed",
1234
+ metadata: completeAppMetadata(metadata)
1235
+ };
1236
+ this.lastConnectResult = completedResult;
1237
+ await this.persistSelectedAccountAddress(
1238
+ activeResult.selectedAccount.address
1239
+ );
1240
+ await this.clearPersistedConnection();
1241
+ this.setWalletAvailability(
1242
+ walletAvailabilityFromConnectResult(completedResult)
1243
+ );
1244
+ this.emit("connect", completedResult);
1245
+ this.emit("accountChanged", activeResult.selectedAccount);
1246
+ return activeResult;
1247
+ } catch (error) {
1248
+ this.provider.requestHide();
1249
+ this.emit("error", error);
1250
+ throw error;
1251
+ }
1252
+ }
765
1253
  async disconnect() {
766
1254
  try {
767
1255
  await this.provider.disconnect();
768
1256
  this.emit("disconnect", {});
769
1257
  this.lastConnectResult = null;
1258
+ await this.persistSelectedAccountAddress(null);
770
1259
  await this.clearPersistedConnection();
771
1260
  this.clearAuthorizedAvailability();
772
1261
  } catch (error) {
@@ -911,9 +1400,9 @@ var NativeSDK = class {
911
1400
  }
912
1401
  async requestConnectionState(options) {
913
1402
  if (!this.initialized) await this.initialize();
914
- const metadata = options?.metadata ?? this.lastConnectResult?.metadata ?? void 0;
1403
+ const metadata = options?.metadata ?? this.lastConnectResult?.metadata ?? this.defaultMetadata ?? void 0;
915
1404
  const providerOptions = metadata ? { metadata: this.resolveMetadata(metadata) } : void 0;
916
- const preferredAccountAddress = await this.readSelectedAccountAddress();
1405
+ const preferredAccountAddress = options?.preferredAccountAddress ?? await this.readSelectedAccountAddress();
917
1406
  const nextProviderOptions = providerOptions || preferredAccountAddress ? {
918
1407
  ...providerOptions ?? {},
919
1408
  ...preferredAccountAddress ? { preferredAccountAddress } : {}
@@ -966,15 +1455,16 @@ var NativeSDK = class {
966
1455
  });
967
1456
  }
968
1457
  resolveMetadata(input) {
969
- if (!input) {
1458
+ const effectiveInput = input ?? this.defaultMetadata;
1459
+ if (!effectiveInput) {
970
1460
  return { appId: this.origin };
971
1461
  }
972
1462
  const metadata = {
973
- appId: input.appId ?? this.origin
1463
+ appId: effectiveInput.appId ?? this.origin
974
1464
  };
975
- if (input.appUrl) metadata.appUrl = input.appUrl;
976
- if (input.appName) metadata.appName = input.appName;
977
- if (input.imageUrl) metadata.imageUrl = input.imageUrl;
1465
+ if (effectiveInput.appUrl) metadata.appUrl = effectiveInput.appUrl;
1466
+ if (effectiveInput.appName) metadata.appName = effectiveInput.appName;
1467
+ if (effectiveInput.imageUrl) metadata.imageUrl = effectiveInput.imageUrl;
978
1468
  return metadata;
979
1469
  }
980
1470
  resolveSignInMetadata(options) {