@thru/wallet 0.2.27 → 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
@@ -0,0 +1,281 @@
1
+ /* Hidden native wallet WebView for transparent integrations. Hosts compose
2
+ this alongside <ThruProvider config={{ walletExperience: "transparent" }}>
3
+ so wallet requests can run without opening bottom-sheet UI. */
4
+
5
+ import {
6
+ useCallback,
7
+ useContext,
8
+ useEffect,
9
+ useMemo,
10
+ useRef,
11
+ useState,
12
+ type ComponentProps,
13
+ } from "react";
14
+ import {
15
+ Platform,
16
+ StyleSheet,
17
+ View,
18
+ type LayoutChangeEvent,
19
+ type StyleProp,
20
+ type ViewStyle,
21
+ } from "react-native";
22
+ import {
23
+ WebView,
24
+ type WebViewMessageEvent,
25
+ type WebView as WebViewType,
26
+ } from "react-native-webview";
27
+ import { getShellHtml } from "../provider/shell";
28
+ import type { NativeSDK } from "../NativeSDK";
29
+ import type { WebViewRefLike } from "../provider/WebViewBridge";
30
+ import { enableWebAuthnSupport } from "./android-webauthn";
31
+ import { ThruContext } from "./ThruContext";
32
+
33
+ type WebViewLoadEndEvent = Parameters<
34
+ NonNullable<ComponentProps<typeof WebView>["onLoadEnd"]>
35
+ >[0];
36
+
37
+ export interface ThruTransparentWalletBridgeProps {
38
+ wallet?: NativeSDK | null;
39
+ style?: StyleProp<ViewStyle>;
40
+ webViewProps?: Partial<ComponentProps<typeof WebView>>;
41
+ }
42
+
43
+ export function ThruTransparentWalletBridge({
44
+ wallet: walletProp,
45
+ style,
46
+ webViewProps,
47
+ }: ThruTransparentWalletBridgeProps) {
48
+ const thruContext = useContext(ThruContext);
49
+ const wallet = walletProp ?? thruContext?.wallet ?? null;
50
+ const webViewRef = useRef<WebViewType | null>(null);
51
+ const webViewNativeTagRef = useRef<number | null>(null);
52
+ const didRefreshWalletAvailabilityRef = useRef(false);
53
+ const [isFocusSurfaceActive, setIsFocusSurfaceActive] = useState(false);
54
+
55
+ const attachIfReady = useCallback(() => {
56
+ if (!wallet || !webViewRef.current) return;
57
+ const ref: WebViewRefLike = {
58
+ injectJavaScript: (script: string) => {
59
+ webViewRef.current?.injectJavaScript(script);
60
+ },
61
+ };
62
+ wallet.attachWebView(ref);
63
+ }, [wallet]);
64
+
65
+ const enableAndroidWebAuthnIfNeeded = useCallback(async () => {
66
+ if (Platform.OS !== "android") return false;
67
+ const enabled = await enableWebAuthnSupport(webViewNativeTagRef.current);
68
+ webViewRef.current?.injectJavaScript(
69
+ "window.dispatchEvent(new Event('thru:native-webauthn-ready')); true;",
70
+ );
71
+ return enabled;
72
+ }, []);
73
+
74
+ const focusWebViewDocument = useCallback(() => {
75
+ const webView = webViewRef.current as (WebViewType & {
76
+ requestFocus?: () => void;
77
+ }) | null;
78
+ webView?.requestFocus?.();
79
+ webViewRef.current?.injectJavaScript(
80
+ "try { window.focus(); document.body && document.body.focus && document.body.focus(); } catch (_) {} true;",
81
+ );
82
+ }, []);
83
+
84
+ const refreshWalletAvailabilityIfReady = useCallback(() => {
85
+ if (!wallet || didRefreshWalletAvailabilityRef.current) return;
86
+ didRefreshWalletAvailabilityRef.current = true;
87
+ void wallet.refreshWalletAvailability();
88
+ }, [wallet]);
89
+
90
+ useEffect(() => {
91
+ if (!wallet) return;
92
+ wallet.setUiHandlers({
93
+ onShowRequested: () => {
94
+ setIsFocusSurfaceActive(true);
95
+ },
96
+ onHideRequested: () => {
97
+ setIsFocusSurfaceActive(false);
98
+ },
99
+ });
100
+ return () => {
101
+ wallet.clearUiHandlers();
102
+ };
103
+ }, [focusWebViewDocument, wallet]);
104
+
105
+ useEffect(() => {
106
+ if (!isFocusSurfaceActive) return;
107
+ const timers = [0, 50, 120, 250, 500].map((delay) =>
108
+ setTimeout(focusWebViewDocument, delay),
109
+ );
110
+ return () => {
111
+ timers.forEach(clearTimeout);
112
+ };
113
+ }, [focusWebViewDocument, isFocusSurfaceActive]);
114
+
115
+ const webViewSource = useMemo(() => {
116
+ if (!wallet) return null;
117
+ if (Platform.OS === "ios" && wallet.getIosWebViewMode() === "direct") {
118
+ return { uri: wallet.getIframeSrc() };
119
+ }
120
+ return {
121
+ html: getShellHtml({
122
+ walletUrl: wallet.getIframeSrc(),
123
+ walletOrigin: wallet.getWalletOrigin(),
124
+ }),
125
+ baseUrl: wallet.getWalletOrigin(),
126
+ };
127
+ }, [wallet]);
128
+
129
+ const isDirectWalletSource = Boolean(
130
+ wallet &&
131
+ Platform.OS === "ios" &&
132
+ wallet.getIosWebViewMode() === "direct",
133
+ );
134
+
135
+ useEffect(() => {
136
+ didRefreshWalletAvailabilityRef.current = false;
137
+ }, [webViewSource]);
138
+
139
+ const handleWebViewLayout = useCallback(
140
+ (event: LayoutChangeEvent) => {
141
+ const target = (event.nativeEvent as { target?: unknown }).target;
142
+ webViewNativeTagRef.current =
143
+ typeof target === "number" ? target : webViewNativeTagRef.current;
144
+ void enableAndroidWebAuthnIfNeeded();
145
+ webViewProps?.onLayout?.(event);
146
+ },
147
+ [enableAndroidWebAuthnIfNeeded, webViewProps],
148
+ );
149
+
150
+ const handleLoadEnd = useCallback(
151
+ (event: WebViewLoadEndEvent) => {
152
+ attachIfReady();
153
+ if (isDirectWalletSource) {
154
+ void enableAndroidWebAuthnIfNeeded().finally(
155
+ refreshWalletAvailabilityIfReady,
156
+ );
157
+ } else {
158
+ void enableAndroidWebAuthnIfNeeded();
159
+ }
160
+ webViewProps?.onLoadEnd?.(event);
161
+ },
162
+ [
163
+ attachIfReady,
164
+ enableAndroidWebAuthnIfNeeded,
165
+ isDirectWalletSource,
166
+ refreshWalletAvailabilityIfReady,
167
+ webViewProps,
168
+ ],
169
+ );
170
+
171
+ const handleMessage = useCallback(
172
+ (event: WebViewMessageEvent) => {
173
+ let shouldRefreshAfterBridgeReady = false;
174
+ let shouldCollapseFocusSurface = false;
175
+ try {
176
+ const data = JSON.parse(event.nativeEvent.data) as {
177
+ id?: unknown;
178
+ success?: unknown;
179
+ type?: string;
180
+ };
181
+ shouldRefreshAfterBridgeReady = data.type === "iframe:ready";
182
+ shouldCollapseFocusSurface =
183
+ typeof data.id === "string" && typeof data.success === "boolean";
184
+ } catch {
185
+ /* Let the bridge ignore malformed messages. */
186
+ }
187
+
188
+ if (shouldCollapseFocusSurface) {
189
+ setIsFocusSurfaceActive(false);
190
+ }
191
+
192
+ wallet?.onMessage({
193
+ nativeEvent: { data: event.nativeEvent.data },
194
+ });
195
+ webViewProps?.onMessage?.(event);
196
+
197
+ if (shouldRefreshAfterBridgeReady) {
198
+ void enableAndroidWebAuthnIfNeeded().finally(
199
+ refreshWalletAvailabilityIfReady,
200
+ );
201
+ }
202
+ },
203
+ [
204
+ enableAndroidWebAuthnIfNeeded,
205
+ refreshWalletAvailabilityIfReady,
206
+ wallet,
207
+ webViewProps,
208
+ ],
209
+ );
210
+
211
+ if (!webViewSource) return null;
212
+
213
+ return (
214
+ <View
215
+ collapsable={false}
216
+ pointerEvents={isFocusSurfaceActive ? "auto" : "none"}
217
+ style={[
218
+ styles.container,
219
+ isFocusSurfaceActive ? styles.activeContainer : null,
220
+ style,
221
+ ]}
222
+ >
223
+ <WebView
224
+ {...webViewProps}
225
+ ref={webViewRef}
226
+ source={webViewSource}
227
+ originWhitelist={["*"]}
228
+ javaScriptEnabled
229
+ domStorageEnabled
230
+ webviewDebuggingEnabled={__DEV__}
231
+ sharedCookiesEnabled
232
+ allowsInlineMediaPlayback
233
+ mediaPlaybackRequiresUserAction={false}
234
+ limitsNavigationsToAppBoundDomains={isDirectWalletSource}
235
+ onLoadStart={(event) => {
236
+ attachIfReady();
237
+ void enableAndroidWebAuthnIfNeeded();
238
+ webViewProps?.onLoadStart?.(event);
239
+ }}
240
+ onLoadEnd={handleLoadEnd}
241
+ onLayout={handleWebViewLayout}
242
+ onMessage={handleMessage}
243
+ style={[
244
+ styles.webview,
245
+ isFocusSurfaceActive ? styles.activeWebview : null,
246
+ webViewProps?.style,
247
+ ]}
248
+ />
249
+ </View>
250
+ );
251
+ }
252
+
253
+ const styles = StyleSheet.create({
254
+ container: {
255
+ height: 1,
256
+ left: 0,
257
+ opacity: 0,
258
+ overflow: "hidden",
259
+ position: "absolute",
260
+ top: 0,
261
+ width: 1,
262
+ },
263
+ activeContainer: {
264
+ bottom: 0,
265
+ height: "100%",
266
+ opacity: 1,
267
+ right: 0,
268
+ width: "100%",
269
+ zIndex: 2147483647,
270
+ },
271
+ webview: {
272
+ backgroundColor: "transparent",
273
+ height: 1,
274
+ width: 1,
275
+ },
276
+ activeWebview: {
277
+ flex: 1,
278
+ height: "100%",
279
+ width: "100%",
280
+ },
281
+ });
@@ -1,6 +1,7 @@
1
1
  import { useCallback, useEffect, useRef } from 'react';
2
2
  import type { ConnectResult, IThruChain } from "../../../interfaces";
3
- import type { ConnectOptions, SignInOptions } from "../../NativeSDK";
3
+ import type { CreateAccountResult } from "../../../protocol";
4
+ import type { ConnectOptions, CreateAccountOptions, SignInOptions } from "../../NativeSDK";
4
5
  import { useThru } from './useThru';
5
6
  import { waitForWallet } from './waitForWallet';
6
7
 
@@ -38,6 +39,15 @@ export function useWallet() {
38
39
  return ready.signIn(options);
39
40
  }, []);
40
41
 
42
+ const createTransparentAccount = useCallback(
43
+ async (options?: CreateAccountOptions): Promise<CreateAccountResult> => {
44
+ const ready =
45
+ walletRef.current ?? (await waitForWallet(() => walletRef.current));
46
+ return ready.createAccount(options);
47
+ },
48
+ [],
49
+ );
50
+
41
51
  const disconnect = useCallback(async (): Promise<void> => {
42
52
  const ready =
43
53
  walletRef.current ?? (await waitForWallet(() => walletRef.current));
@@ -56,6 +66,7 @@ export function useWallet() {
56
66
  accounts,
57
67
  connect,
58
68
  signIn,
69
+ createAccount: createTransparentAccount,
59
70
  disconnect,
60
71
  isConnected: isConnected && !!wallet,
61
72
  isConnecting,
@@ -8,6 +8,8 @@ export type {
8
8
  ThruWalletSheetProps,
9
9
  ThruWalletSheetHandle,
10
10
  } from './ThruWalletSheet';
11
+ export { ThruTransparentWalletBridge } from './ThruTransparentWalletBridge';
12
+ export type { ThruTransparentWalletBridgeProps } from './ThruTransparentWalletBridge';
11
13
 
12
14
  export { useWallet } from './hooks/useWallet';
13
15
  export { useWalletAvailability } from './hooks/useWalletAvailability';
@@ -19,11 +21,20 @@ export { enableWebAuthnSupport } from './android-webauthn';
19
21
  export type {
20
22
  WalletAccount,
21
23
  ConnectResult,
24
+ ThruSigningSession,
25
+ ThruSigningSessionCreateOptions,
26
+ ThruSigningSessionDescriptor,
27
+ ThruSigningSessionInstruction,
28
+ ThruSigningSessionInstructionCreateOptions,
29
+ ThruSigningSessionTimestamp,
30
+ ThruTransactionIntent,
22
31
  } from "../../interfaces";
32
+ export type { SigningSessionStorage } from "../../signing-sessions";
23
33
 
24
34
  export type {
25
35
  IosWebViewMode,
26
36
  NativeSDKStorage,
37
+ NativeWalletExperience,
27
38
  WalletAvailability,
28
39
  } from "../NativeSDK";
29
40
  export type { ManageAccountsResult } from "../../protocol";
@@ -0,0 +1,35 @@
1
+ export { ThruProvider } from './ThruProvider';
2
+ export type { ThruProviderProps } from './ThruProvider';
3
+ export { ThruContext } from './ThruContext';
4
+ export type { ThruContextValue } from './ThruContext';
5
+
6
+ export { ThruTransparentWalletBridge } from './ThruTransparentWalletBridge';
7
+ export type { ThruTransparentWalletBridgeProps } from './ThruTransparentWalletBridge';
8
+
9
+ export { useWallet } from './hooks/useWallet';
10
+ export { useWalletAvailability } from './hooks/useWalletAvailability';
11
+ export { useAccounts } from './hooks/useAccounts';
12
+ export { useThru } from './hooks/useThru';
13
+
14
+ export { enableWebAuthnSupport } from './android-webauthn';
15
+
16
+ export type {
17
+ WalletAccount,
18
+ ConnectResult,
19
+ ThruSigningSession,
20
+ ThruSigningSessionCreateOptions,
21
+ ThruSigningSessionDescriptor,
22
+ ThruSigningSessionInstruction,
23
+ ThruSigningSessionInstructionCreateOptions,
24
+ ThruSigningSessionTimestamp,
25
+ ThruTransactionIntent,
26
+ } from "../../interfaces";
27
+ export type { SigningSessionStorage } from "../../signing-sessions";
28
+
29
+ export type {
30
+ IosWebViewMode,
31
+ NativeSDKStorage,
32
+ NativeWalletExperience,
33
+ WalletAvailability,
34
+ } from "../NativeSDK";
35
+ export type { ManageAccountsResult } from "../../protocol";
@@ -7,14 +7,20 @@ import type {
7
7
 
8
8
  export const POST_MESSAGE_REQUEST_TYPES = {
9
9
  CONNECT: "connect",
10
+ CREATE_ACCOUNT: "createAccount",
10
11
  DISCONNECT: "disconnect",
11
12
  SIGN_MESSAGE: "signMessage",
12
13
  SIGN_TRANSACTION: "signTransaction",
14
+ SIGN_PASSKEY_CHALLENGE: "signPasskeyChallenge",
13
15
  GET_ACCOUNTS: "getAccounts",
14
16
  GET_CONNECTION_STATE: "getConnectionState",
15
17
  GET_SIGNING_CONTEXT: "getSigningContext",
16
18
  SELECT_ACCOUNT: "selectAccount",
17
19
  MANAGE_ACCOUNTS: "manageAccounts",
20
+ CREATE_SIGNING_SESSION: "createSigningSession",
21
+ CREATE_SIGNING_SESSION_INSTRUCTION: "createSigningSessionInstruction",
22
+ CONFIRM_SIGNING_SESSION: "confirmSigningSession",
23
+ REVOKE_SIGNING_SESSION: "revokeSigningSession",
18
24
  } as const;
19
25
 
20
26
  export type RequestType =
@@ -38,7 +44,7 @@ export const POST_MESSAGE_EVENT_TYPE = "event" as const;
38
44
 
39
45
  export const IFRAME_READY_EVENT = "iframe:ready" as const;
40
46
 
41
- export const DEFAULT_IFRAME_URL = "http://localhost:3000/embedded";
47
+ export const DEFAULT_IFRAME_URL = "http://localhost:3010/embedded";
42
48
 
43
49
  const REQUEST_ID_PREFIX = "req";
44
50
 
@@ -62,6 +68,11 @@ export interface DisconnectRequestMessage extends BaseRequest {
62
68
  payload?: undefined;
63
69
  }
64
70
 
71
+ export interface CreateAccountRequestMessage extends BaseRequest {
72
+ type: typeof POST_MESSAGE_REQUEST_TYPES.CREATE_ACCOUNT;
73
+ payload: CreateAccountPayload;
74
+ }
75
+
65
76
  export interface SignMessageRequestMessage extends BaseRequest {
66
77
  type: typeof POST_MESSAGE_REQUEST_TYPES.SIGN_MESSAGE;
67
78
  payload: SignMessagePayload;
@@ -72,6 +83,11 @@ export interface SignTransactionRequestMessage extends BaseRequest {
72
83
  payload: SignTransactionPayload;
73
84
  }
74
85
 
86
+ export interface SignPasskeyChallengeRequestMessage extends BaseRequest {
87
+ type: typeof POST_MESSAGE_REQUEST_TYPES.SIGN_PASSKEY_CHALLENGE;
88
+ payload: SignPasskeyChallengePayload;
89
+ }
90
+
75
91
  export interface GetAccountsRequestMessage extends BaseRequest {
76
92
  type: typeof POST_MESSAGE_REQUEST_TYPES.GET_ACCOUNTS;
77
93
  payload?: undefined;
@@ -97,21 +113,62 @@ export interface ManageAccountsRequestMessage extends BaseRequest {
97
113
  payload?: undefined;
98
114
  }
99
115
 
116
+ export interface CreateSigningSessionRequestMessage extends BaseRequest {
117
+ type: typeof POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION;
118
+ payload: CreateSigningSessionPayload;
119
+ }
120
+
121
+ export interface CreateSigningSessionInstructionRequestMessage extends BaseRequest {
122
+ type: typeof POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION_INSTRUCTION;
123
+ payload: CreateSigningSessionInstructionPayload;
124
+ }
125
+
126
+ export interface ConfirmSigningSessionRequestMessage extends BaseRequest {
127
+ type: typeof POST_MESSAGE_REQUEST_TYPES.CONFIRM_SIGNING_SESSION;
128
+ payload: ConfirmSigningSessionPayload;
129
+ }
130
+
131
+ export interface RevokeSigningSessionRequestMessage extends BaseRequest {
132
+ type: typeof POST_MESSAGE_REQUEST_TYPES.REVOKE_SIGNING_SESSION;
133
+ payload: RevokeSigningSessionPayload;
134
+ }
135
+
100
136
  export type PostMessageRequest =
101
137
  | ConnectRequestMessage
138
+ | CreateAccountRequestMessage
102
139
  | DisconnectRequestMessage
103
140
  | SignMessageRequestMessage
104
141
  | SignTransactionRequestMessage
142
+ | SignPasskeyChallengeRequestMessage
105
143
  | GetAccountsRequestMessage
106
144
  | GetConnectionStateRequestMessage
107
145
  | GetSigningContextRequestMessage
108
146
  | SelectAccountRequestMessage
109
- | ManageAccountsRequestMessage;
147
+ | ManageAccountsRequestMessage
148
+ | CreateSigningSessionRequestMessage
149
+ | CreateSigningSessionInstructionRequestMessage
150
+ | ConfirmSigningSessionRequestMessage
151
+ | RevokeSigningSessionRequestMessage;
110
152
 
111
153
  export interface DisconnectResult {
112
154
  // Empty object keeps compatibility with existing consumers expecting a success payload
113
155
  }
114
156
 
157
+ export interface CreateAccountPayload {
158
+ accountName?: string;
159
+ metadata?: ConnectMetadataInput;
160
+ }
161
+
162
+ export interface CreateAccountResult {
163
+ account: WalletAccount;
164
+ accounts: WalletAccount[];
165
+ selectedAccount: WalletAccount;
166
+ signature: string | null;
167
+ vmError: string | null;
168
+ userErrorCode: string | null;
169
+ executionResult: string | null;
170
+ }
171
+
115
172
  export interface GetAccountsResult {
116
173
  accounts: WalletAccount[];
117
174
  }
@@ -142,14 +199,20 @@ export interface ManageAccountsResult {
142
199
 
143
200
  type RequestResultMap = {
144
201
  [POST_MESSAGE_REQUEST_TYPES.CONNECT]: ConnectResult;
202
+ [POST_MESSAGE_REQUEST_TYPES.CREATE_ACCOUNT]: CreateAccountResult;
145
203
  [POST_MESSAGE_REQUEST_TYPES.DISCONNECT]: DisconnectResult;
146
204
  [POST_MESSAGE_REQUEST_TYPES.SIGN_MESSAGE]: SignMessageResult;
147
205
  [POST_MESSAGE_REQUEST_TYPES.SIGN_TRANSACTION]: SignTransactionResult;
206
+ [POST_MESSAGE_REQUEST_TYPES.SIGN_PASSKEY_CHALLENGE]: SignPasskeyChallengeResult;
148
207
  [POST_MESSAGE_REQUEST_TYPES.GET_ACCOUNTS]: GetAccountsResult;
149
208
  [POST_MESSAGE_REQUEST_TYPES.GET_CONNECTION_STATE]: GetConnectionStateResult;
150
209
  [POST_MESSAGE_REQUEST_TYPES.GET_SIGNING_CONTEXT]: GetSigningContextResult;
151
210
  [POST_MESSAGE_REQUEST_TYPES.SELECT_ACCOUNT]: SelectAccountResult;
152
211
  [POST_MESSAGE_REQUEST_TYPES.MANAGE_ACCOUNTS]: ManageAccountsResult;
212
+ [POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION]: CreateSigningSessionResult;
213
+ [POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION_INSTRUCTION]: CreateSigningSessionInstructionResult;
214
+ [POST_MESSAGE_REQUEST_TYPES.CONFIRM_SIGNING_SESSION]: ConfirmSigningSessionResult;
215
+ [POST_MESSAGE_REQUEST_TYPES.REVOKE_SIGNING_SESSION]: RevokeSigningSessionResult;
153
216
  };
154
217
 
155
218
  interface ResponseErrorPayload {
@@ -248,12 +311,74 @@ export interface SignTransactionPayload {
248
311
  readWriteAddresses?: string[];
249
312
  readOnlyAddresses?: string[];
250
313
  review?: TransactionReviewPayload;
314
+ signingSessionId?: string;
251
315
  }
252
316
 
253
317
  export interface SignTransactionResult {
254
318
  signedTransaction: string;
255
319
  }
256
320
 
321
+ export interface SignPasskeyChallengePayload {
322
+ /** base64url-encoded challenge bytes from a backend passkey-manager flow. */
323
+ challenge: string;
324
+ /** Optional expected wallet address for the selected transparent account. */
325
+ walletAddress?: string;
326
+ }
327
+
328
+ export interface SignPasskeyChallengeResult {
329
+ signatureR: string;
330
+ signatureS: string;
331
+ authenticatorData: string;
332
+ clientDataJSON: string;
333
+ }
334
+
335
+ export interface CreateSigningSessionPayload {
336
+ walletAddress?: string;
337
+ expiresAt: string;
338
+ review?: TransactionReviewPayload;
339
+ }
340
+
341
+ export interface CreateSigningSessionInstructionPayload {
342
+ walletAddress?: string;
343
+ expiresAt: string;
344
+ walletAccountIdx: number;
345
+ }
346
+
347
+ export interface SigningSessionDescriptorPayload {
348
+ id: string;
349
+ walletAddress: string;
350
+ publicKey: string;
351
+ authIdx: number;
352
+ expiresAt: string;
353
+ createdAt: string;
354
+ }
355
+
356
+ export interface CreateSigningSessionResult {
357
+ session: SigningSessionDescriptorPayload;
358
+ }
359
+
360
+ export interface CreateSigningSessionInstructionResult {
361
+ session: SigningSessionDescriptorPayload;
362
+ programAddress: string;
363
+ instructionData: string;
364
+ }
365
+
366
+ export interface ConfirmSigningSessionPayload {
367
+ sessionId: string;
368
+ }
369
+
370
+ export interface ConfirmSigningSessionResult {
371
+ session: SigningSessionDescriptorPayload;
372
+ }
373
+
374
+ export interface RevokeSigningSessionPayload {
375
+ sessionId: string;
376
+ }
377
+
378
+ export interface RevokeSigningSessionResult {
379
+ // Empty object keeps compatibility with existing consumers expecting a success payload
380
+ }
381
+
257
382
  export interface TransactionReviewSimulation {
258
383
  before?: string;
259
384
  after?: string;
@@ -20,10 +20,12 @@ import {
20
20
  } from '../protocol';
21
21
  import { IframeManager } from './IframeManager';
22
22
  import { EmbeddedThruChain } from './chains/ThruChain';
23
+ import type { SigningSessionDescriptorStore } from '../signing-sessions';
23
24
 
24
25
  export interface EmbeddedProviderConfig {
25
26
  iframeUrl?: string;
26
27
  addressTypes?: AddressTypeValue[];
28
+ signingSessions?: SigningSessionDescriptorStore;
27
29
  }
28
30
 
29
31
  export interface ConnectOptions {
@@ -77,7 +79,11 @@ export class EmbeddedProvider {
77
79
  // Create chain instances
78
80
  const addressTypes = config.addressTypes || [AddressType.THRU];
79
81
  if (addressTypes.includes(AddressType.THRU)) {
80
- this._thruChain = new EmbeddedThruChain(this.iframeManager, this);
82
+ this._thruChain = new EmbeddedThruChain(
83
+ this.iframeManager,
84
+ this,
85
+ config.signingSessions,
86
+ );
81
87
  }
82
88
  }
83
89
 
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { IframeManager } from './IframeManager';
3
+
4
+ describe('IframeManager', () => {
5
+ it('allows trusted production wallet origins', () => {
6
+ const thruBridge = new IframeManager('https://wallet.thru.org/embedded');
7
+ const tidBridge = new IframeManager('https://wallet.tid.sh/embedded');
8
+
9
+ expect(thruBridge).toBeInstanceOf(IframeManager);
10
+ expect(tidBridge).toBeInstanceOf(IframeManager);
11
+ });
12
+
13
+ it('rejects untrusted production wallet origins', () => {
14
+ expect(
15
+ () => new IframeManager('https://evil.example.com/embedded')
16
+ ).toThrow(/Untrusted iframe origin/);
17
+ });
18
+ });
@@ -16,7 +16,10 @@ import {
16
16
  * Development builds additionally allow localhost, LAN, and Tailscale
17
17
  * origins so local HTTPS RP-ID testing can use the hosted wallet path.
18
18
  */
19
- const PRODUCTION_IFRAME_ORIGINS = ['https://wallet.thru.org'];
19
+ const PRODUCTION_IFRAME_ORIGINS = [
20
+ 'https://wallet.thru.org',
21
+ 'https://wallet.tid.sh',
22
+ ];
20
23
 
21
24
  const SLOW_REQUEST_TIMEOUT_MS = 5 * 60 * 1000;
22
25
  const FAST_REQUEST_TIMEOUT_MS = 30 * 1000;
@@ -26,7 +29,11 @@ const SLOW_REQUEST_TYPES: ReadonlySet<string> = new Set([
26
29
  POST_MESSAGE_REQUEST_TYPES.CONNECT,
27
30
  POST_MESSAGE_REQUEST_TYPES.SIGN_MESSAGE,
28
31
  POST_MESSAGE_REQUEST_TYPES.SIGN_TRANSACTION,
32
+ POST_MESSAGE_REQUEST_TYPES.SIGN_PASSKEY_CHALLENGE,
29
33
  POST_MESSAGE_REQUEST_TYPES.MANAGE_ACCOUNTS,
34
+ POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION,
35
+ POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION_INSTRUCTION,
36
+ POST_MESSAGE_REQUEST_TYPES.CONFIRM_SIGNING_SESSION,
30
37
  ]);
31
38
 
32
39
  function isPrivateIpv4Host(hostname: string): boolean {