@dynamic-labs-sdk/solana 0.13.0 → 0.14.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 (110) hide show
  1. package/dist/{addSolanaWalletStandardExtension-DikNvaRF.esm.js → addSolanaWalletStandardExtension-B7GpGFx3.esm.js} +2 -2
  2. package/dist/{addSolanaWalletStandardExtension-DikNvaRF.esm.js.map → addSolanaWalletStandardExtension-B7GpGFx3.esm.js.map} +1 -1
  3. package/dist/{addSolanaWalletStandardExtension-D5uAzwwi.cjs.js → addSolanaWalletStandardExtension-Bv39eDZc.cjs.js} +2 -2
  4. package/dist/{addSolanaWalletStandardExtension-D5uAzwwi.cjs.js.map → addSolanaWalletStandardExtension-Bv39eDZc.cjs.js.map} +1 -1
  5. package/dist/{addWaasSolanaExtension-DBfiocjd.cjs.js → addWaasSolanaExtension-BaxzWnYL.cjs.js} +3 -3
  6. package/dist/{addWaasSolanaExtension-DBfiocjd.cjs.js.map → addWaasSolanaExtension-BaxzWnYL.cjs.js.map} +1 -1
  7. package/dist/{addWaasSolanaExtension-FUlrXwXm.esm.js → addWaasSolanaExtension-ofYQDYyc.esm.js} +2 -2
  8. package/dist/{addWaasSolanaExtension-FUlrXwXm.esm.js.map → addWaasSolanaExtension-ofYQDYyc.esm.js.map} +1 -1
  9. package/dist/exports/index.d.ts +6 -2
  10. package/dist/exports/index.d.ts.map +1 -1
  11. package/dist/index.cjs.js +980 -3
  12. package/dist/index.cjs.js.map +1 -1
  13. package/dist/index.esm.js +979 -7
  14. package/dist/index.esm.js.map +1 -1
  15. package/dist/{isVersionedTransaction-s73AwAKu.esm.js → isVersionedTransaction-CecentWi.esm.js} +2 -2
  16. package/dist/{isVersionedTransaction-s73AwAKu.esm.js.map → isVersionedTransaction-CecentWi.esm.js.map} +1 -1
  17. package/dist/{isVersionedTransaction-CSkcEh-G.cjs.js → isVersionedTransaction-DW-V1tUS.cjs.js} +3 -3
  18. package/dist/{isVersionedTransaction-CSkcEh-G.cjs.js.map → isVersionedTransaction-DW-V1tUS.cjs.js.map} +1 -1
  19. package/dist/phantomRedirect/PhantomRedirectWalletProvider.types.d.ts +112 -0
  20. package/dist/phantomRedirect/PhantomRedirectWalletProvider.types.d.ts.map +1 -0
  21. package/dist/phantomRedirect/addPhantomRedirectSolanaExtension/addPhantomRedirectSolanaExtension.d.ts +115 -0
  22. package/dist/phantomRedirect/addPhantomRedirectSolanaExtension/addPhantomRedirectSolanaExtension.d.ts.map +1 -0
  23. package/dist/phantomRedirect/addPhantomRedirectSolanaExtension/index.d.ts +3 -0
  24. package/dist/phantomRedirect/addPhantomRedirectSolanaExtension/index.d.ts.map +1 -0
  25. package/dist/phantomRedirect/completePhantomRedirect/completePhantomRedirect.d.ts +32 -0
  26. package/dist/phantomRedirect/completePhantomRedirect/completePhantomRedirect.d.ts.map +1 -0
  27. package/dist/phantomRedirect/completePhantomRedirect/index.d.ts +3 -0
  28. package/dist/phantomRedirect/completePhantomRedirect/index.d.ts.map +1 -0
  29. package/dist/phantomRedirect/createPhantomRedirectWalletProvider/createPhantomRedirectWalletProvider.d.ts +17 -0
  30. package/dist/phantomRedirect/createPhantomRedirectWalletProvider/createPhantomRedirectWalletProvider.d.ts.map +1 -0
  31. package/dist/phantomRedirect/createPhantomRedirectWalletProvider/index.d.ts +3 -0
  32. package/dist/phantomRedirect/createPhantomRedirectWalletProvider/index.d.ts.map +1 -0
  33. package/dist/phantomRedirect/detectPhantomRedirect/detectPhantomRedirect.d.ts +17 -0
  34. package/dist/phantomRedirect/detectPhantomRedirect/detectPhantomRedirect.d.ts.map +1 -0
  35. package/dist/phantomRedirect/detectPhantomRedirect/index.d.ts +3 -0
  36. package/dist/phantomRedirect/detectPhantomRedirect/index.d.ts.map +1 -0
  37. package/dist/phantomRedirect/errors/NoPendingPhantomRequestError.d.ts +5 -0
  38. package/dist/phantomRedirect/errors/NoPendingPhantomRequestError.d.ts.map +1 -0
  39. package/dist/phantomRedirect/errors/NoPhantomSessionError.d.ts +5 -0
  40. package/dist/phantomRedirect/errors/NoPhantomSessionError.d.ts.map +1 -0
  41. package/dist/phantomRedirect/errors/PhantomMissingEncryptionParamsError.d.ts +5 -0
  42. package/dist/phantomRedirect/errors/PhantomMissingEncryptionParamsError.d.ts.map +1 -0
  43. package/dist/phantomRedirect/errors/PhantomRedirectDecryptionError.d.ts +5 -0
  44. package/dist/phantomRedirect/errors/PhantomRedirectDecryptionError.d.ts.map +1 -0
  45. package/dist/phantomRedirect/errors/PhantomRedirectRejectedError.d.ts +9 -0
  46. package/dist/phantomRedirect/errors/PhantomRedirectRejectedError.d.ts.map +1 -0
  47. package/dist/phantomRedirect/errors/PhantomRedirectTimeoutError.d.ts +5 -0
  48. package/dist/phantomRedirect/errors/PhantomRedirectTimeoutError.d.ts.map +1 -0
  49. package/dist/phantomRedirect/phantomRedirect.broadcast.types.d.ts +6 -0
  50. package/dist/phantomRedirect/phantomRedirect.broadcast.types.d.ts.map +1 -0
  51. package/dist/phantomRedirect/phantomRedirect.constants.d.ts +5 -0
  52. package/dist/phantomRedirect/phantomRedirect.constants.d.ts.map +1 -0
  53. package/dist/phantomRedirect/phantomRedirect.events.d.ts +152 -0
  54. package/dist/phantomRedirect/phantomRedirect.events.d.ts.map +1 -0
  55. package/dist/phantomRedirect/storage/pendingRequestStorageSchema/index.d.ts +2 -0
  56. package/dist/phantomRedirect/storage/pendingRequestStorageSchema/index.d.ts.map +1 -0
  57. package/dist/phantomRedirect/storage/pendingRequestStorageSchema/pendingRequestStorageSchema.d.ts +15 -0
  58. package/dist/phantomRedirect/storage/pendingRequestStorageSchema/pendingRequestStorageSchema.d.ts.map +1 -0
  59. package/dist/phantomRedirect/storage/phantomSessionStorageSchema/index.d.ts +2 -0
  60. package/dist/phantomRedirect/storage/phantomSessionStorageSchema/index.d.ts.map +1 -0
  61. package/dist/phantomRedirect/storage/phantomSessionStorageSchema/phantomSessionStorageSchema.d.ts +14 -0
  62. package/dist/phantomRedirect/storage/phantomSessionStorageSchema/phantomSessionStorageSchema.d.ts.map +1 -0
  63. package/dist/phantomRedirect/utils/broadcastPhantomEvent/broadcastPhantomEvent.d.ts +16 -0
  64. package/dist/phantomRedirect/utils/broadcastPhantomEvent/broadcastPhantomEvent.d.ts.map +1 -0
  65. package/dist/phantomRedirect/utils/broadcastPhantomEvent/index.d.ts +2 -0
  66. package/dist/phantomRedirect/utils/broadcastPhantomEvent/index.d.ts.map +1 -0
  67. package/dist/phantomRedirect/utils/buildPhantomDeepLink/buildPhantomDeepLink.d.ts +23 -0
  68. package/dist/phantomRedirect/utils/buildPhantomDeepLink/buildPhantomDeepLink.d.ts.map +1 -0
  69. package/dist/phantomRedirect/utils/buildPhantomDeepLink/index.d.ts +3 -0
  70. package/dist/phantomRedirect/utils/buildPhantomDeepLink/index.d.ts.map +1 -0
  71. package/dist/phantomRedirect/utils/cleanPhantomParamsFromUrl/cleanPhantomParamsFromUrl.d.ts +15 -0
  72. package/dist/phantomRedirect/utils/cleanPhantomParamsFromUrl/cleanPhantomParamsFromUrl.d.ts.map +1 -0
  73. package/dist/phantomRedirect/utils/cleanPhantomParamsFromUrl/index.d.ts +3 -0
  74. package/dist/phantomRedirect/utils/cleanPhantomParamsFromUrl/index.d.ts.map +1 -0
  75. package/dist/phantomRedirect/utils/crypto/createNaClSharedSecret/createNaClSharedSecret.d.ts +13 -0
  76. package/dist/phantomRedirect/utils/crypto/createNaClSharedSecret/createNaClSharedSecret.d.ts.map +1 -0
  77. package/dist/phantomRedirect/utils/crypto/createNaClSharedSecret/index.d.ts +3 -0
  78. package/dist/phantomRedirect/utils/crypto/createNaClSharedSecret/index.d.ts.map +1 -0
  79. package/dist/phantomRedirect/utils/crypto/decryptPayload/decryptPayload.d.ts +16 -0
  80. package/dist/phantomRedirect/utils/crypto/decryptPayload/decryptPayload.d.ts.map +1 -0
  81. package/dist/phantomRedirect/utils/crypto/decryptPayload/index.d.ts +3 -0
  82. package/dist/phantomRedirect/utils/crypto/decryptPayload/index.d.ts.map +1 -0
  83. package/dist/phantomRedirect/utils/crypto/encryptPayload/encryptPayload.d.ts +17 -0
  84. package/dist/phantomRedirect/utils/crypto/encryptPayload/encryptPayload.d.ts.map +1 -0
  85. package/dist/phantomRedirect/utils/crypto/encryptPayload/index.d.ts +3 -0
  86. package/dist/phantomRedirect/utils/crypto/encryptPayload/index.d.ts.map +1 -0
  87. package/dist/phantomRedirect/utils/crypto/generateNaClKeyPair/generateNaClKeyPair.d.ts +11 -0
  88. package/dist/phantomRedirect/utils/crypto/generateNaClKeyPair/generateNaClKeyPair.d.ts.map +1 -0
  89. package/dist/phantomRedirect/utils/crypto/generateNaClKeyPair/index.d.ts +3 -0
  90. package/dist/phantomRedirect/utils/crypto/generateNaClKeyPair/index.d.ts.map +1 -0
  91. package/dist/phantomRedirect/utils/getPhantomCluster/getPhantomCluster.d.ts +23 -0
  92. package/dist/phantomRedirect/utils/getPhantomCluster/getPhantomCluster.d.ts.map +1 -0
  93. package/dist/phantomRedirect/utils/getPhantomOriginTab/getPhantomOriginTab.d.ts +19 -0
  94. package/dist/phantomRedirect/utils/getPhantomOriginTab/getPhantomOriginTab.d.ts.map +1 -0
  95. package/dist/phantomRedirect/utils/getPhantomOriginTab/index.d.ts +3 -0
  96. package/dist/phantomRedirect/utils/getPhantomOriginTab/index.d.ts.map +1 -0
  97. package/dist/phantomRedirect/utils/listenForPhantomBroadcast/index.d.ts +2 -0
  98. package/dist/phantomRedirect/utils/listenForPhantomBroadcast/index.d.ts.map +1 -0
  99. package/dist/phantomRedirect/utils/listenForPhantomBroadcast/listenForPhantomBroadcast.d.ts +15 -0
  100. package/dist/phantomRedirect/utils/listenForPhantomBroadcast/listenForPhantomBroadcast.d.ts.map +1 -0
  101. package/dist/phantomRedirect/utils/parsePhantomRedirectParams/index.d.ts +3 -0
  102. package/dist/phantomRedirect/utils/parsePhantomRedirectParams/index.d.ts.map +1 -0
  103. package/dist/phantomRedirect/utils/parsePhantomRedirectParams/parsePhantomRedirectParams.d.ts +22 -0
  104. package/dist/phantomRedirect/utils/parsePhantomRedirectParams/parsePhantomRedirectParams.d.ts.map +1 -0
  105. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  106. package/dist/waas.cjs.js +3 -3
  107. package/dist/waas.esm.js +2 -2
  108. package/dist/walletStandard.cjs.js +2 -2
  109. package/dist/walletStandard.esm.js +2 -2
  110. package/package.json +7 -5
package/dist/index.cjs.js CHANGED
@@ -1,12 +1,17 @@
1
- const require_addSolanaWalletStandardExtension = require('./addSolanaWalletStandardExtension-D5uAzwwi.cjs.js');
2
- const require_isVersionedTransaction = require('./isVersionedTransaction-CSkcEh-G.cjs.js');
3
- const require_addWaasSolanaExtension = require('./addWaasSolanaExtension-DBfiocjd.cjs.js');
1
+ const require_addSolanaWalletStandardExtension = require('./addSolanaWalletStandardExtension-Bv39eDZc.cjs.js');
2
+ const require_isVersionedTransaction = require('./isVersionedTransaction-DW-V1tUS.cjs.js');
3
+ const require_addWaasSolanaExtension = require('./addWaasSolanaExtension-BaxzWnYL.cjs.js');
4
4
  let _dynamic_labs_sdk_assert_package_version = require("@dynamic-labs-sdk/assert-package-version");
5
5
  let _dynamic_labs_sdk_client_core = require("@dynamic-labs-sdk/client/core");
6
6
  let _solana_web3_js = require("@solana/web3.js");
7
7
  let _dynamic_labs_sdk_client = require("@dynamic-labs-sdk/client");
8
+ let _dynamic_labs_sdk_api_core = require("@dynamic-labs/sdk-api-core");
8
9
  let bs58 = require("bs58");
9
10
  bs58 = require_addSolanaWalletStandardExtension.__toESM(bs58);
11
+ let zod_mini = require("zod/mini");
12
+ zod_mini = require_addSolanaWalletStandardExtension.__toESM(zod_mini);
13
+ let tweetnacl = require("tweetnacl");
14
+ tweetnacl = require_addSolanaWalletStandardExtension.__toESM(tweetnacl);
10
15
 
11
16
  //#region src/addSolanaExtension/addSolanaExtension.ts
12
17
  /**
@@ -223,6 +228,975 @@ const simulateSolanaTransaction = async ({ walletAccount, transaction, includeFe
223
228
  }
224
229
  };
225
230
 
231
+ //#endregion
232
+ //#region src/phantomRedirect/errors/NoPendingPhantomRequestError.ts
233
+ var NoPendingPhantomRequestError = class extends _dynamic_labs_sdk_client.BaseError {
234
+ constructor() {
235
+ super({
236
+ cause: null,
237
+ code: "no_pending_phantom_request_error",
238
+ docsUrl: null,
239
+ name: "NoPendingPhantomRequestError",
240
+ shortMessage: "No pending Phantom request found. The request may have already been processed or expired."
241
+ });
242
+ }
243
+ };
244
+
245
+ //#endregion
246
+ //#region src/phantomRedirect/errors/NoPhantomSessionError.ts
247
+ var NoPhantomSessionError = class extends _dynamic_labs_sdk_client.BaseError {
248
+ constructor() {
249
+ super({
250
+ cause: null,
251
+ code: "no_phantom_session_error",
252
+ docsUrl: null,
253
+ name: "NoPhantomSessionError",
254
+ shortMessage: "No Phantom session found. Please connect first."
255
+ });
256
+ }
257
+ };
258
+
259
+ //#endregion
260
+ //#region src/phantomRedirect/errors/PhantomMissingEncryptionParamsError.ts
261
+ var PhantomMissingEncryptionParamsError = class extends _dynamic_labs_sdk_client.BaseError {
262
+ constructor() {
263
+ super({
264
+ cause: null,
265
+ code: "phantom_missing_encryption_params_error",
266
+ docsUrl: null,
267
+ name: "PhantomMissingEncryptionParamsError",
268
+ shortMessage: "Phantom redirect response is missing required encryption parameters (phantomPublicKey, data, or nonce)."
269
+ });
270
+ }
271
+ };
272
+
273
+ //#endregion
274
+ //#region src/phantomRedirect/errors/PhantomRedirectRejectedError.ts
275
+ var PhantomRedirectRejectedError = class extends _dynamic_labs_sdk_client.BaseError {
276
+ constructor({ errorCode, errorMessage }) {
277
+ super({
278
+ cause: null,
279
+ code: "phantom_redirect_rejected_error",
280
+ details: errorMessage,
281
+ docsUrl: null,
282
+ metaMessages: [`Error Code: ${errorCode}`],
283
+ name: "PhantomRedirectRejectedError",
284
+ shortMessage: "User rejected the request in Phantom wallet."
285
+ });
286
+ }
287
+ };
288
+
289
+ //#endregion
290
+ //#region src/phantomRedirect/phantomRedirect.constants.ts
291
+ const PHANTOM_DEEPLINK_BASE_URL = "https://phantom.app/ul/v1";
292
+ const PHANTOM_SESSION_STORAGE_KEY = "phantom_session";
293
+ const PHANTOM_PENDING_REQUEST_STORAGE_KEY = "phantom_pending_request";
294
+
295
+ //#endregion
296
+ //#region src/phantomRedirect/storage/pendingRequestStorageSchema/pendingRequestStorageSchema.ts
297
+ /**
298
+ * Storage schema for tracking pending Phantom wallet requests.
299
+ * This temporary data is used to match redirect responses with their
300
+ * originating requests and includes the encryption keys needed for decryption.
301
+ */
302
+ const pendingRequestStorageKeySchema = (0, _dynamic_labs_sdk_client_core.createStorageKeySchema)({
303
+ key: PHANTOM_PENDING_REQUEST_STORAGE_KEY,
304
+ schema: zod_mini.object({
305
+ dappPublicKey: zod_mini.string(),
306
+ dappSecretKey: zod_mini.string(),
307
+ message: zod_mini.optional(zod_mini.string()),
308
+ method: zod_mini.string(),
309
+ requestId: zod_mini.string(),
310
+ timestamp: zod_mini.number(),
311
+ walletAddress: zod_mini.optional(zod_mini.string())
312
+ })
313
+ });
314
+
315
+ //#endregion
316
+ //#region src/phantomRedirect/storage/phantomSessionStorageSchema/phantomSessionStorageSchema.ts
317
+ /**
318
+ * Storage schema for persisting Phantom wallet session data.
319
+ * This includes the encryption keys and session information needed
320
+ * for subsequent operations after the initial connection.
321
+ */
322
+ const phantomSessionStorageKeySchema = (0, _dynamic_labs_sdk_client_core.createStorageKeySchema)({
323
+ key: PHANTOM_SESSION_STORAGE_KEY,
324
+ schema: zod_mini.object({
325
+ dappPublicKey: zod_mini.string(),
326
+ dappSecretKey: zod_mini.string(),
327
+ phantomPublicKey: zod_mini.string(),
328
+ sessionToken: zod_mini.string(),
329
+ sharedSecret: zod_mini.string(),
330
+ walletAddress: zod_mini.string()
331
+ })
332
+ });
333
+
334
+ //#endregion
335
+ //#region src/phantomRedirect/utils/broadcastPhantomEvent/broadcastPhantomEvent.ts
336
+ /**
337
+ * Broadcasts a Phantom redirect event to other tabs via the crossTabBroadcast
338
+ * service.
339
+ *
340
+ * Used by the redirect-completion tab to notify the originating tab that
341
+ * an operation has finished, since onceEvent listeners are in-memory and
342
+ * do not cross tab boundaries.
343
+ *
344
+ * @param params.event - The DynamicEvents event name to broadcast
345
+ * @param params.args - The event arguments to include in the message
346
+ * @param client - The Dynamic client instance
347
+ */
348
+ const broadcastPhantomEvent = ({ args, event }, client) => {
349
+ (0, _dynamic_labs_sdk_client_core.getCore)(client).crossTabBroadcast.send({
350
+ args,
351
+ event
352
+ });
353
+ };
354
+
355
+ //#endregion
356
+ //#region src/phantomRedirect/utils/crypto/createNaClSharedSecret/createNaClSharedSecret.ts
357
+ /**
358
+ * Creates a shared secret for NaCl box encryption using our secret key and their public key.
359
+ *
360
+ * @param params.ourSecretKey - Our base58-encoded secret key
361
+ * @param params.theirPublicKey - Their base58-encoded public key
362
+ * @returns The base58-encoded shared secret
363
+ */
364
+ const createNaClSharedSecret = ({ ourSecretKey, theirPublicKey }) => {
365
+ const sharedSecret = tweetnacl.default.box.before(bs58.default.decode(theirPublicKey), bs58.default.decode(ourSecretKey));
366
+ return bs58.default.encode(sharedSecret);
367
+ };
368
+
369
+ //#endregion
370
+ //#region src/phantomRedirect/errors/PhantomRedirectDecryptionError.ts
371
+ var PhantomRedirectDecryptionError = class extends _dynamic_labs_sdk_client.BaseError {
372
+ constructor() {
373
+ super({
374
+ cause: null,
375
+ code: "phantom_redirect_decryption_error",
376
+ docsUrl: null,
377
+ name: "PhantomRedirectDecryptionError",
378
+ shortMessage: "Failed to decrypt Phantom redirect response. The encryption keys may be invalid."
379
+ });
380
+ }
381
+ };
382
+
383
+ //#endregion
384
+ //#region src/phantomRedirect/utils/crypto/decryptPayload/decryptPayload.ts
385
+ /**
386
+ * Decrypts a payload using NaCl box encryption with a shared secret.
387
+ *
388
+ * @param params.data - The base58-encoded encrypted data
389
+ * @param params.nonce - The base58-encoded nonce
390
+ * @param params.sharedSecret - The base58-encoded shared secret
391
+ * @returns The decrypted payload object
392
+ * @throws {PhantomRedirectDecryptionError} If decryption fails
393
+ */
394
+ const decryptPayload = ({ data, nonce, sharedSecret }) => {
395
+ let decrypted;
396
+ try {
397
+ decrypted = tweetnacl.default.box.open.after(bs58.default.decode(data), bs58.default.decode(nonce), bs58.default.decode(sharedSecret));
398
+ } catch {
399
+ throw new PhantomRedirectDecryptionError();
400
+ }
401
+ if (!decrypted) throw new PhantomRedirectDecryptionError();
402
+ const decryptedString = (0, _dynamic_labs_sdk_client_core.getBuffer)().from(decrypted).toString("utf-8");
403
+ try {
404
+ return JSON.parse(decryptedString);
405
+ } catch {
406
+ throw new PhantomRedirectDecryptionError();
407
+ }
408
+ };
409
+
410
+ //#endregion
411
+ //#region src/phantomRedirect/utils/getPhantomOriginTab/getPhantomOriginTab.ts
412
+ /**
413
+ * Per-client service that tracks whether this JavaScript context is the
414
+ * **originating tab** that initiated a Phantom redirect request.
415
+ *
416
+ * Calling `getPhantomOriginTab(client)` for the first time lazily creates the
417
+ * service with `isOriginTab: false`. `setupPhantomListeners` sets it to `true`
418
+ * immediately when a request is initiated, so `completePhantomRedirect` can
419
+ * detect clone tabs by checking this flag.
420
+ *
421
+ * Because the service is stored in the client's `runtimeServices` Map (one
422
+ * entry per client instance), it is naturally scoped to the current JavaScript
423
+ * context — no browser globals (`sessionStorage`, `window`) are needed, making
424
+ * this approach compatible with SSR and React Native.
425
+ */
426
+ const getPhantomOriginTab = (0, _dynamic_labs_sdk_client_core.createRuntimeServiceAccessKey)("phantomOriginTab", () => ({ isOriginTab: false }));
427
+
428
+ //#endregion
429
+ //#region src/phantomRedirect/utils/parsePhantomRedirectParams/parsePhantomRedirectParams.ts
430
+ /**
431
+ * Parses Phantom wallet redirect parameters from a URL.
432
+ *
433
+ * Extracts both success and error parameters from the redirect URL,
434
+ * including encrypted response data or error information.
435
+ *
436
+ * @param params.url - The redirect URL containing Phantom parameters
437
+ * @returns Object containing the parsed redirect parameters
438
+ */
439
+ const parsePhantomRedirectParams = ({ url }) => {
440
+ const searchParams = url.searchParams;
441
+ return {
442
+ data: searchParams.get("data") ?? void 0,
443
+ errorCode: searchParams.get("errorCode") ?? void 0,
444
+ errorMessage: searchParams.get("errorMessage") ?? void 0,
445
+ nonce: searchParams.get("nonce") ?? void 0,
446
+ phantomPublicKey: searchParams.get("phantom_encryption_public_key") ?? void 0,
447
+ requestId: searchParams.get("request_id") ?? void 0
448
+ };
449
+ };
450
+
451
+ //#endregion
452
+ //#region src/phantomRedirect/completePhantomRedirect/completePhantomRedirect.ts
453
+ const methodErrorEventMap = {
454
+ connect: "phantomRedirectConnectionError",
455
+ disconnect: "phantomRedirectDisconnectError",
456
+ signAllTransactions: "phantomRedirectSignAllTransactionsError",
457
+ signAndSendTransaction: "phantomRedirectSignAndSendTransactionError",
458
+ signMessage: "phantomRedirectSignMessageError",
459
+ signTransaction: "phantomRedirectSignTransactionError"
460
+ };
461
+ /**
462
+ * Returns true when the current JavaScript context is a **clone tab** —
463
+ * a new browser tab that Phantom opened to deliver the redirect callback on
464
+ * mobile browsers.
465
+ *
466
+ * ## Detection mechanism
467
+ *
468
+ * When a wallet operation is initiated (`connect`, `signMessage`, etc.)
469
+ * `setupPhantomListeners` sets `isOriginTab = true` on the per-client
470
+ * `phantomOriginTabService`. Because each browser tab (and each native mobile
471
+ * context) has its own JavaScript heap, the clone tab's client starts with
472
+ * `isOriginTab = false` and will never have it set to `true`.
473
+ *
474
+ * - `isOriginTab === false` → clone tab (mobile browser redirect) → should close
475
+ * - `isOriginTab === true` → originating tab or native mobile context → should stay open
476
+ */
477
+ const isCloneTab = (client) => !getPhantomOriginTab(client).isOriginTab;
478
+ /**
479
+ * Removes the pending request from storage, emits a Phantom redirect event to
480
+ * the local client, broadcasts it to the originating tab via `BroadcastChannel`,
481
+ * and — when running in a clone tab — emits `phantomRedirectCloseTab` so that
482
+ * the customer's `onCloseTab` callback can close the tab.
483
+ *
484
+ * This is the single point of truth for all cleanup and event delivery in the
485
+ * redirect flow. Every code path calls this exactly once, ensuring a consistent
486
+ * ordering: remove pending request → emit → broadcast → (maybe) close tab.
487
+ */
488
+ const completeAndEmitPhantomRedirectEvent = async ({ args, event }, client) => {
489
+ const core = (0, _dynamic_labs_sdk_client_core.getCore)(client);
490
+ const cloneTab = isCloneTab(client);
491
+ core.logger.debug("[PHANTOM] completeAndEmitPhantomRedirectEvent: start", {
492
+ event,
493
+ isCloneTab: cloneTab
494
+ });
495
+ await core.storage.removeItem(pendingRequestStorageKeySchema);
496
+ core.logger.debug("[PHANTOM] completeAndEmitPhantomRedirectEvent: emitting event locally", { event });
497
+ (0, _dynamic_labs_sdk_client_core.emitEvent)({
498
+ args,
499
+ event
500
+ }, client);
501
+ core.logger.debug("[PHANTOM] completeAndEmitPhantomRedirectEvent: broadcasting", { event });
502
+ broadcastPhantomEvent({
503
+ args,
504
+ event
505
+ }, client);
506
+ if (cloneTab) {
507
+ core.logger.debug("[PHANTOM] completeAndEmitPhantomRedirectEvent: emitting closeTab (clone tab)");
508
+ (0, _dynamic_labs_sdk_client_core.emitEvent)({
509
+ args: {},
510
+ event: "phantomRedirectCloseTab"
511
+ }, client);
512
+ }
513
+ };
514
+ /**
515
+ * Completes the Phantom wallet redirect flow after the deep link callback.
516
+ *
517
+ * This function is called on the page that Phantom redirects back to after the
518
+ * user approves or rejects a wallet operation. It processes the redirect URL,
519
+ * decrypts the response, updates session storage, and emits the appropriate
520
+ * events so that waiting callers (in this tab or the originating tab) can
521
+ * resolve or reject their pending promises.
522
+ *
523
+ * ## Cross-tab delivery
524
+ *
525
+ * On mobile browsers, Phantom opens the redirect URL in a **new tab** (the
526
+ * clone tab). Events are delivered to the originating tab via the
527
+ * `BroadcastChannel` API. Once delivery is complete this function emits
528
+ * `phantomRedirectCloseTab` so that the clone tab can be closed and the user
529
+ * is returned to the originating tab. See the `phantomRedirectCloseTab` event
530
+ * JSDoc for details on how to handle tab closing.
531
+ *
532
+ * @param params.url - The callback URL received from Phantom
533
+ * @param client - The Dynamic client instance
534
+ * @throws {NoPendingPhantomRequestError} If no pending request is found in storage
535
+ * @throws {PhantomMissingEncryptionParamsError} If the URL is missing the
536
+ * required encryption parameters (`phantom_encryption_public_key`, `data`,
537
+ * `nonce`) and does not carry an error code either
538
+ * @throws {PhantomRedirectRejectedError} If the user rejected the request in Phantom
539
+ */
540
+ const completePhantomRedirect = async ({ url }, client = (0, _dynamic_labs_sdk_client_core.getDefaultClient)()) => {
541
+ const core = (0, _dynamic_labs_sdk_client_core.getCore)(client);
542
+ core.logger.debug("[PHANTOM] completePhantomRedirect: start", { search: url.search });
543
+ const params = parsePhantomRedirectParams({ url });
544
+ const pendingRequest = await core.storage.getItem(pendingRequestStorageKeySchema);
545
+ core.logger.debug("[PHANTOM] completePhantomRedirect: pendingRequest", {
546
+ found: !!pendingRequest,
547
+ method: pendingRequest?.method
548
+ });
549
+ if (!pendingRequest) throw new NoPendingPhantomRequestError();
550
+ if (params.errorCode) {
551
+ const error = new PhantomRedirectRejectedError({
552
+ errorCode: params.errorCode,
553
+ errorMessage: params.errorMessage
554
+ });
555
+ const event = methodErrorEventMap[pendingRequest.method];
556
+ await completeAndEmitPhantomRedirectEvent({
557
+ args: { error },
558
+ event
559
+ }, client);
560
+ return;
561
+ }
562
+ if (pendingRequest.method === "disconnect") {
563
+ await completeAndEmitPhantomRedirectEvent({
564
+ args: {},
565
+ event: "phantomRedirectDisconnectComplete"
566
+ }, client);
567
+ return;
568
+ }
569
+ if (!params.data || !params.nonce) {
570
+ await core.storage.removeItem(pendingRequestStorageKeySchema);
571
+ throw new PhantomMissingEncryptionParamsError();
572
+ }
573
+ let sharedSecret;
574
+ if (pendingRequest.method === "connect") {
575
+ if (!params.phantomPublicKey) throw new PhantomMissingEncryptionParamsError();
576
+ sharedSecret = createNaClSharedSecret({
577
+ ourSecretKey: pendingRequest.dappSecretKey,
578
+ theirPublicKey: params.phantomPublicKey
579
+ });
580
+ } else {
581
+ const session = await core.storage.getItem(phantomSessionStorageKeySchema);
582
+ if (!session) throw new NoPhantomSessionError();
583
+ sharedSecret = session.sharedSecret;
584
+ }
585
+ const decryptedData = decryptPayload({
586
+ data: params.data,
587
+ nonce: params.nonce,
588
+ sharedSecret
589
+ });
590
+ if (pendingRequest.method === "connect") {
591
+ const connectData = decryptedData;
592
+ await core.storage.setItem(phantomSessionStorageKeySchema, {
593
+ dappPublicKey: pendingRequest.dappPublicKey,
594
+ dappSecretKey: pendingRequest.dappSecretKey,
595
+ phantomPublicKey: params.phantomPublicKey,
596
+ sessionToken: connectData.session,
597
+ sharedSecret,
598
+ walletAddress: connectData.public_key
599
+ });
600
+ await completeAndEmitPhantomRedirectEvent({
601
+ args: {
602
+ address: connectData.public_key,
603
+ publicKey: connectData.public_key
604
+ },
605
+ event: "phantomRedirectConnectionComplete"
606
+ }, client);
607
+ } else if (pendingRequest.method === "signMessage") {
608
+ const signData = decryptedData;
609
+ await completeAndEmitPhantomRedirectEvent({
610
+ args: {
611
+ message: pendingRequest.message,
612
+ signature: signData.signature
613
+ },
614
+ event: "phantomRedirectSignMessageComplete"
615
+ }, client);
616
+ } else if (pendingRequest.method === "signTransaction") await completeAndEmitPhantomRedirectEvent({
617
+ args: { transaction: decryptedData.transaction },
618
+ event: "phantomRedirectSignTransactionComplete"
619
+ }, client);
620
+ else if (pendingRequest.method === "signAllTransactions") await completeAndEmitPhantomRedirectEvent({
621
+ args: { transactions: decryptedData.transactions },
622
+ event: "phantomRedirectSignAllTransactionsComplete"
623
+ }, client);
624
+ else await completeAndEmitPhantomRedirectEvent({
625
+ args: { signature: decryptedData.signature },
626
+ event: "phantomRedirectSignAndSendTransactionComplete"
627
+ }, client);
628
+ };
629
+
630
+ //#endregion
631
+ //#region src/phantomRedirect/utils/buildPhantomDeepLink/buildPhantomDeepLink.ts
632
+ /**
633
+ * Builds a Phantom wallet deep link URL for wallet operations.
634
+ *
635
+ * @param params.method - The operation method (connect, signMessage, signTransaction, etc.)
636
+ * @param params.appUrl - Optional dApp URL shown in Phantom's UI
637
+ * @param params.dappPublicKey - The dApp's base58-encoded public encryption key
638
+ * @param params.redirectUrl - The URL to redirect back to after the operation
639
+ * @param params.cluster - Optional Solana cluster (mainnet-beta, devnet, testnet)
640
+ * @param params.payload - Optional encrypted payload for signing operations
641
+ * @param params.nonce - Optional nonce for encrypted payload
642
+ * @returns The complete Phantom deep link URL
643
+ */
644
+ const buildPhantomDeepLink = ({ appUrl, cluster, dappPublicKey, method, nonce, payload, redirectUrl }) => {
645
+ const url = new URL(`${PHANTOM_DEEPLINK_BASE_URL}/${method}`);
646
+ url.searchParams.set("dapp_encryption_public_key", dappPublicKey);
647
+ url.searchParams.set("redirect_link", redirectUrl);
648
+ if (appUrl) url.searchParams.set("app_url", appUrl);
649
+ if (cluster) url.searchParams.set("cluster", cluster);
650
+ if (payload) url.searchParams.set("payload", payload);
651
+ if (nonce) url.searchParams.set("nonce", nonce);
652
+ return url;
653
+ };
654
+
655
+ //#endregion
656
+ //#region src/phantomRedirect/utils/crypto/encryptPayload/encryptPayload.ts
657
+ /**
658
+ * Encrypts a payload using NaCl box encryption with a shared secret.
659
+ *
660
+ * @param params.payload - The payload object to encrypt
661
+ * @param params.sharedSecret - The base58-encoded shared secret
662
+ * @returns The encrypted data and nonce, both base58-encoded
663
+ */
664
+ const encryptPayload = ({ payload, sharedSecret }) => {
665
+ const nonce = tweetnacl.default.randomBytes(tweetnacl.default.box.nonceLength);
666
+ const payloadString = JSON.stringify(payload);
667
+ const payloadBytes = new Uint8Array((0, _dynamic_labs_sdk_client_core.getBuffer)().from(payloadString, "utf8"));
668
+ const encrypted = tweetnacl.default.box.after(payloadBytes, nonce, bs58.default.decode(sharedSecret));
669
+ return {
670
+ data: bs58.default.encode(encrypted),
671
+ nonce: bs58.default.encode(nonce)
672
+ };
673
+ };
674
+
675
+ //#endregion
676
+ //#region src/phantomRedirect/utils/crypto/generateNaClKeyPair/generateNaClKeyPair.ts
677
+ /**
678
+ * Generates a new NaCl key pair for encryption.
679
+ *
680
+ * @returns A key pair with base58-encoded public and secret keys
681
+ */
682
+ const generateNaClKeyPair = () => {
683
+ const keyPair = tweetnacl.default.box.keyPair();
684
+ return {
685
+ publicKey: bs58.default.encode(keyPair.publicKey),
686
+ secretKey: bs58.default.encode(keyPair.secretKey)
687
+ };
688
+ };
689
+
690
+ //#endregion
691
+ //#region src/phantomRedirect/utils/getPhantomCluster/getPhantomCluster.ts
692
+ /**
693
+ * Returns the Solana cluster name expected by Phantom's deep link protocol
694
+ * (e.g. "mainnet-beta", "devnet", "testnet").
695
+ *
696
+ * - When a `walletAccount` is provided, reads the cluster from the active network
697
+ * data for that account.
698
+ * - When no `walletAccount` is provided (e.g. during `connect` before an account
699
+ * exists), falls back to the first registered SOL network provider's cluster.
700
+ *
701
+ * Returns `undefined` if no cluster can be determined; Phantom will use the
702
+ * wallet's default in that case.
703
+ */
704
+ const getPhantomCluster = async ({ dynamicClient, walletAccount }) => {
705
+ if (walletAccount) {
706
+ const { networkData } = await (0, _dynamic_labs_sdk_client.getActiveNetworkData)({ walletAccount }, dynamicClient);
707
+ return networkData?.cluster ?? void 0;
708
+ }
709
+ return (0, _dynamic_labs_sdk_client_core.getNetworkProviders)(dynamicClient).find((p) => p.chain === "SOL")?.cluster ?? void 0;
710
+ };
711
+
712
+ //#endregion
713
+ //#region src/phantomRedirect/utils/listenForPhantomBroadcast/listenForPhantomBroadcast.ts
714
+ /**
715
+ * Registers listeners for Phantom redirect events broadcast from other tabs
716
+ * (e.g. the redirect-completion tab), using the crossTabBroadcast service.
717
+ *
718
+ * Returns a cleanup function that unregisters all listeners — call it when the
719
+ * operation is no longer pending (e.g. in a promise `.finally()`).
720
+ *
721
+ * @param listeners - Map of event names to handler functions
722
+ * @param client - The Dynamic client instance
723
+ * @returns Cleanup function that removes all registered listeners
724
+ */
725
+ const listenForPhantomBroadcast = (listeners, client) => {
726
+ const { crossTabBroadcast } = (0, _dynamic_labs_sdk_client_core.getCore)(client);
727
+ const wrappedListeners = Object.entries(listeners).map(([event, listener]) => {
728
+ const wrapped = (args) => {
729
+ listener(args);
730
+ };
731
+ crossTabBroadcast.on(event, wrapped);
732
+ return {
733
+ event,
734
+ wrapped
735
+ };
736
+ });
737
+ return () => {
738
+ wrappedListeners.forEach(({ event, wrapped }) => {
739
+ crossTabBroadcast.off(event, wrapped);
740
+ });
741
+ };
742
+ };
743
+
744
+ //#endregion
745
+ //#region src/phantomRedirect/createPhantomRedirectWalletProvider/createPhantomRedirectWalletProvider.ts
746
+ /**
747
+ * Generates a random alphanumeric string of specified length.
748
+ */
749
+ const randomString = (length) => {
750
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
751
+ const bytes = crypto.getRandomValues(new Uint8Array(length));
752
+ return Array.from(bytes, (byte) => chars[byte % 62]).join("");
753
+ };
754
+ /**
755
+ * Extracts the dApp URL from a redirect URL.
756
+ */
757
+ const extractDappUrl = (redirectUrl) => {
758
+ const url = new URL(redirectUrl);
759
+ return `${url.protocol}//${url.host}`;
760
+ };
761
+ /**
762
+ * Creates a Phantom redirect-based Solana wallet provider.
763
+ *
764
+ * This wallet provider uses Phantom's deep link protocol for mobile wallet
765
+ * interactions with end-to-end encryption using NaCl.
766
+ *
767
+ * @param params.dynamicClient - The Dynamic client instance
768
+ * @returns A Phantom redirect wallet provider
769
+ */
770
+ const createPhantomRedirectWalletProvider = ({ baseRedirectUrl, dynamicClient }) => {
771
+ const core = (0, _dynamic_labs_sdk_client_core.getCore)(dynamicClient);
772
+ const walletProviderType = _dynamic_labs_sdk_api_core.WalletProviderEnum.DeepLink;
773
+ const key = (0, _dynamic_labs_sdk_client_core.formatWalletProviderKey)({
774
+ chain: "SOL",
775
+ displayName: "Phantom",
776
+ walletProviderType
777
+ });
778
+ const groupKey = (0, _dynamic_labs_sdk_client_core.formatWalletProviderGroupKey)("Phantom");
779
+ const metadata = {
780
+ displayName: "Phantom",
781
+ icon: "https://phantom.app/img/phantom-logo.svg"
782
+ };
783
+ const getSession = async () => {
784
+ const session = await core.storage.getItem(phantomSessionStorageKeySchema);
785
+ if (!session) throw new NoPhantomSessionError();
786
+ return session;
787
+ };
788
+ const setPendingRequest = async (request) => {
789
+ await core.storage.setItem(pendingRequestStorageKeySchema, request);
790
+ };
791
+ /**
792
+ * Registers listeners for both delivery paths Phantom uses depending on platform:
793
+ * - In mobile native, we get redirected back to the same context, so onceEvent suffices.
794
+ * - In mobile browsers, we get redirected back to a new tab, so a BroadcastChannel listener
795
+ * is also needed to receive the completion event from that tab.
796
+ *
797
+ * The broadcast channel is closed once the deferred promise settles.
798
+ */
799
+ const setupPhantomListeners = ({ completeEvent, deferredPromise, errorEvent, onComplete, onError }) => {
800
+ getPhantomOriginTab(dynamicClient).isOriginTab = true;
801
+ core.logger.debug("[PHANTOM] setupPhantomListeners: isOriginTab=true, registering listeners", {
802
+ completeEvent,
803
+ errorEvent
804
+ });
805
+ (0, _dynamic_labs_sdk_client.onceEvent)({
806
+ event: completeEvent,
807
+ listener: onComplete
808
+ }, dynamicClient);
809
+ (0, _dynamic_labs_sdk_client.onceEvent)({
810
+ event: errorEvent,
811
+ listener: onError
812
+ }, dynamicClient);
813
+ const closeChannel = listenForPhantomBroadcast({
814
+ [completeEvent]: onComplete,
815
+ [errorEvent]: onError
816
+ }, dynamicClient);
817
+ deferredPromise.promise.finally(() => {
818
+ core.logger.debug("[PHANTOM] setupPhantomListeners: deferred promise settled, closing channel");
819
+ closeChannel();
820
+ }).catch(() => {});
821
+ };
822
+ const connect = async () => {
823
+ core.logger.debug("[PHANTOM] connect: start");
824
+ const redirectUrl = baseRedirectUrl;
825
+ const { publicKey: dappPublicKey, secretKey: dappSecretKey } = generateNaClKeyPair();
826
+ await setPendingRequest({
827
+ dappPublicKey,
828
+ dappSecretKey,
829
+ method: "connect",
830
+ requestId: randomString(32),
831
+ timestamp: Date.now()
832
+ });
833
+ core.logger.debug("[PHANTOM] connect: pending request stored, setting up listeners");
834
+ const deferredPromise = (0, _dynamic_labs_sdk_client_core.createDeferredPromise)();
835
+ setupPhantomListeners({
836
+ completeEvent: "phantomRedirectConnectionComplete",
837
+ deferredPromise,
838
+ errorEvent: "phantomRedirectConnectionError",
839
+ onComplete: ({ address, publicKey }) => {
840
+ core.logger.debug("[PHANTOM] connect: onComplete called", { address });
841
+ deferredPromise.resolve({ addresses: [{
842
+ address,
843
+ publicKey
844
+ }] });
845
+ },
846
+ onError: ({ error }) => {
847
+ core.logger.debug("[PHANTOM] connect: onError called", { error });
848
+ deferredPromise.reject(error);
849
+ }
850
+ });
851
+ const cluster = await getPhantomCluster({ dynamicClient });
852
+ const deepLinkUrl = buildPhantomDeepLink({
853
+ appUrl: extractDappUrl(redirectUrl),
854
+ cluster,
855
+ dappPublicKey,
856
+ method: "connect",
857
+ redirectUrl
858
+ });
859
+ core.logger.debug("[PHANTOM] connect: opening deeplink", { redirectUrl });
860
+ await core.openDeeplink(deepLinkUrl.toString());
861
+ core.logger.debug("[PHANTOM] connect: deeplink opened, awaiting deferred promise");
862
+ return deferredPromise.promise;
863
+ };
864
+ const disconnect = async () => {
865
+ const session = await getSession();
866
+ const redirectUrl = baseRedirectUrl;
867
+ const { data, nonce } = encryptPayload({
868
+ payload: { session: session.sessionToken },
869
+ sharedSecret: session.sharedSecret
870
+ });
871
+ const requestId = randomString(32);
872
+ await setPendingRequest({
873
+ dappPublicKey: session.dappPublicKey,
874
+ dappSecretKey: session.dappSecretKey,
875
+ method: "disconnect",
876
+ requestId,
877
+ timestamp: Date.now()
878
+ });
879
+ const deferredPromise = (0, _dynamic_labs_sdk_client_core.createDeferredPromise)();
880
+ setupPhantomListeners({
881
+ completeEvent: "phantomRedirectDisconnectComplete",
882
+ deferredPromise,
883
+ errorEvent: "phantomRedirectDisconnectError",
884
+ onComplete: async () => {
885
+ await core.storage.removeItem(phantomSessionStorageKeySchema);
886
+ deferredPromise.resolve();
887
+ },
888
+ onError: ({ error }) => {
889
+ deferredPromise.reject(error);
890
+ }
891
+ });
892
+ const deepLinkUrl = buildPhantomDeepLink({
893
+ appUrl: extractDappUrl(redirectUrl),
894
+ dappPublicKey: session.dappPublicKey,
895
+ method: "disconnect",
896
+ nonce,
897
+ payload: data,
898
+ redirectUrl
899
+ });
900
+ await core.openDeeplink(deepLinkUrl.toString());
901
+ return deferredPromise.promise;
902
+ };
903
+ const getActiveNetworkId = async () => (0, _dynamic_labs_sdk_client_core.getActiveNetworkIdFromLastKnownRegistry)({
904
+ client: dynamicClient,
905
+ walletProviderKey: key
906
+ });
907
+ const getConnectedAddresses = async () => {
908
+ return { addresses: [(await getSession()).walletAddress] };
909
+ };
910
+ const signMessage = async ({ message, walletAccount }) => {
911
+ (0, _dynamic_labs_sdk_client_core.assertDefined)(walletAccount, "Wallet account is required for Phantom redirect");
912
+ const redirectUrl = baseRedirectUrl;
913
+ const session = await getSession();
914
+ const { data, nonce } = encryptPayload({
915
+ payload: {
916
+ message: bs58.default.encode(new Uint8Array((0, _dynamic_labs_sdk_client_core.getBuffer)().from(message))),
917
+ session: session.sessionToken
918
+ },
919
+ sharedSecret: session.sharedSecret
920
+ });
921
+ const requestId = randomString(32);
922
+ await setPendingRequest({
923
+ dappPublicKey: session.dappPublicKey,
924
+ dappSecretKey: session.dappSecretKey,
925
+ message,
926
+ method: "signMessage",
927
+ requestId,
928
+ timestamp: Date.now(),
929
+ walletAddress: walletAccount.address
930
+ });
931
+ const deferredPromise = (0, _dynamic_labs_sdk_client_core.createDeferredPromise)();
932
+ setupPhantomListeners({
933
+ completeEvent: "phantomRedirectSignMessageComplete",
934
+ deferredPromise,
935
+ errorEvent: "phantomRedirectSignMessageError",
936
+ onComplete: ({ signature }) => {
937
+ deferredPromise.resolve({ signature });
938
+ },
939
+ onError: ({ error }) => {
940
+ deferredPromise.reject(error);
941
+ }
942
+ });
943
+ const cluster = await getPhantomCluster({
944
+ dynamicClient,
945
+ walletAccount
946
+ });
947
+ const deepLinkUrl = buildPhantomDeepLink({
948
+ appUrl: extractDappUrl(redirectUrl),
949
+ cluster,
950
+ dappPublicKey: session.dappPublicKey,
951
+ method: "signMessage",
952
+ nonce,
953
+ payload: data,
954
+ redirectUrl
955
+ });
956
+ await core.openDeeplink(deepLinkUrl.toString());
957
+ return deferredPromise.promise;
958
+ };
959
+ const signTransaction$1 = async ({ transaction, walletAccount }) => {
960
+ (0, _dynamic_labs_sdk_client_core.assertDefined)(walletAccount, "Wallet account is required for Phantom redirect");
961
+ const redirectUrl = baseRedirectUrl;
962
+ const session = await getSession();
963
+ const serialized = transaction instanceof _solana_web3_js.VersionedTransaction ? transaction.serialize() : transaction.serialize({
964
+ requireAllSignatures: false,
965
+ verifySignatures: false
966
+ });
967
+ const transactionBase58 = bs58.default.encode(new Uint8Array(serialized));
968
+ const { data, nonce } = encryptPayload({
969
+ payload: {
970
+ session: session.sessionToken,
971
+ transaction: transactionBase58
972
+ },
973
+ sharedSecret: session.sharedSecret
974
+ });
975
+ const requestId = randomString(32);
976
+ await setPendingRequest({
977
+ dappPublicKey: session.dappPublicKey,
978
+ dappSecretKey: session.dappSecretKey,
979
+ method: "signTransaction",
980
+ requestId,
981
+ timestamp: Date.now(),
982
+ walletAddress: walletAccount.address
983
+ });
984
+ const deferredPromise = (0, _dynamic_labs_sdk_client_core.createDeferredPromise)();
985
+ const deserializeTransaction = ({ transaction: signedTransactionBase58 }) => {
986
+ const decoded = bs58.default.decode(signedTransactionBase58);
987
+ const signedTransaction = transaction instanceof _solana_web3_js.VersionedTransaction ? _solana_web3_js.VersionedTransaction.deserialize(decoded) : _solana_web3_js.Transaction.from(decoded);
988
+ deferredPromise.resolve({ signedTransaction });
989
+ };
990
+ setupPhantomListeners({
991
+ completeEvent: "phantomRedirectSignTransactionComplete",
992
+ deferredPromise,
993
+ errorEvent: "phantomRedirectSignTransactionError",
994
+ onComplete: deserializeTransaction,
995
+ onError: ({ error }) => {
996
+ deferredPromise.reject(error);
997
+ }
998
+ });
999
+ const cluster = await getPhantomCluster({
1000
+ dynamicClient,
1001
+ walletAccount
1002
+ });
1003
+ const deepLinkUrl = buildPhantomDeepLink({
1004
+ appUrl: extractDappUrl(redirectUrl),
1005
+ cluster,
1006
+ dappPublicKey: session.dappPublicKey,
1007
+ method: "signTransaction",
1008
+ nonce,
1009
+ payload: data,
1010
+ redirectUrl
1011
+ });
1012
+ await core.openDeeplink(deepLinkUrl.toString());
1013
+ return deferredPromise.promise;
1014
+ };
1015
+ const signAllTransactions$1 = async ({ transactions, walletAccount }) => {
1016
+ (0, _dynamic_labs_sdk_client_core.assertDefined)(walletAccount, "Wallet account is required for Phantom redirect");
1017
+ const redirectUrl = baseRedirectUrl;
1018
+ const session = await getSession();
1019
+ const transactionsBase58 = transactions.map((tx) => tx instanceof _solana_web3_js.VersionedTransaction ? tx.serialize() : tx.serialize({
1020
+ requireAllSignatures: false,
1021
+ verifySignatures: false
1022
+ })).map((s) => bs58.default.encode(new Uint8Array(s)));
1023
+ const { data, nonce } = encryptPayload({
1024
+ payload: {
1025
+ session: session.sessionToken,
1026
+ transactions: transactionsBase58
1027
+ },
1028
+ sharedSecret: session.sharedSecret
1029
+ });
1030
+ const requestId = randomString(32);
1031
+ await setPendingRequest({
1032
+ dappPublicKey: session.dappPublicKey,
1033
+ dappSecretKey: session.dappSecretKey,
1034
+ method: "signAllTransactions",
1035
+ requestId,
1036
+ timestamp: Date.now(),
1037
+ walletAddress: walletAccount.address
1038
+ });
1039
+ const deferredPromise = (0, _dynamic_labs_sdk_client_core.createDeferredPromise)();
1040
+ const deserializeTransactions = ({ transactions: signedTransactionsBase58 }) => {
1041
+ const signedTransactions = signedTransactionsBase58.map((txBase58, i) => {
1042
+ const decoded = bs58.default.decode(txBase58);
1043
+ return transactions[i] instanceof _solana_web3_js.VersionedTransaction ? _solana_web3_js.VersionedTransaction.deserialize(decoded) : _solana_web3_js.Transaction.from(decoded);
1044
+ });
1045
+ deferredPromise.resolve({ signedTransactions });
1046
+ };
1047
+ setupPhantomListeners({
1048
+ completeEvent: "phantomRedirectSignAllTransactionsComplete",
1049
+ deferredPromise,
1050
+ errorEvent: "phantomRedirectSignAllTransactionsError",
1051
+ onComplete: deserializeTransactions,
1052
+ onError: ({ error }) => {
1053
+ deferredPromise.reject(error);
1054
+ }
1055
+ });
1056
+ const cluster = await getPhantomCluster({
1057
+ dynamicClient,
1058
+ walletAccount
1059
+ });
1060
+ const deepLinkUrl = buildPhantomDeepLink({
1061
+ appUrl: extractDappUrl(redirectUrl),
1062
+ cluster,
1063
+ dappPublicKey: session.dappPublicKey,
1064
+ method: "signAllTransactions",
1065
+ nonce,
1066
+ payload: data,
1067
+ redirectUrl
1068
+ });
1069
+ await core.openDeeplink(deepLinkUrl.toString());
1070
+ return deferredPromise.promise;
1071
+ };
1072
+ const signAndSendTransaction$1 = async ({ options, transaction, walletAccount }) => {
1073
+ const { signedTransaction } = await signTransaction$1({
1074
+ transaction,
1075
+ walletAccount
1076
+ });
1077
+ const { networkData } = await (0, _dynamic_labs_sdk_client.getActiveNetworkData)({ walletAccount }, dynamicClient);
1078
+ (0, _dynamic_labs_sdk_client_core.assertDefined)(networkData, "Network data is required to broadcast transaction");
1079
+ const connection = require_addWaasSolanaExtension.getSolanaConnection({ networkData });
1080
+ const rawTransaction = signedTransaction.serialize();
1081
+ return { signature: await connection.sendRawTransaction(rawTransaction, options) };
1082
+ };
1083
+ return {
1084
+ chain: "SOL",
1085
+ connect,
1086
+ disconnect,
1087
+ getActiveNetworkId,
1088
+ getConnectedAddresses,
1089
+ groupKey,
1090
+ key,
1091
+ metadata,
1092
+ signAllTransactions: signAllTransactions$1,
1093
+ signAndSendTransaction: signAndSendTransaction$1,
1094
+ signMessage,
1095
+ signTransaction: signTransaction$1,
1096
+ walletProviderType
1097
+ };
1098
+ };
1099
+
1100
+ //#endregion
1101
+ //#region src/phantomRedirect/detectPhantomRedirect/detectPhantomRedirect.ts
1102
+ /**
1103
+ * Detects if the current URL is a Phantom wallet redirect.
1104
+ *
1105
+ * This function examines the URL parameters to determine if it contains
1106
+ * redirect data from a Phantom wallet deep link operation, and validates
1107
+ * that there is a corresponding pending request in storage.
1108
+ *
1109
+ * @param params.url - The URL to check for Phantom redirect parameters
1110
+ * @param client - The Dynamic client instance
1111
+ * @returns A promise that resolves to true if the URL is a valid Phantom redirect, false otherwise
1112
+ */
1113
+ const detectPhantomRedirect = async ({ url }, client = (0, _dynamic_labs_sdk_client_core.getDefaultClient)()) => {
1114
+ const core = (0, _dynamic_labs_sdk_client_core.getCore)(client);
1115
+ if (!(url.searchParams.has("phantom_encryption_public_key") || url.searchParams.has("errorCode"))) return false;
1116
+ await core.initTrack.waitForAll();
1117
+ const pendingRequest = await core.storage.getItem(pendingRequestStorageKeySchema);
1118
+ return Boolean(pendingRequest);
1119
+ };
1120
+
1121
+ //#endregion
1122
+ //#region src/phantomRedirect/addPhantomRedirectSolanaExtension/addPhantomRedirectSolanaExtension.ts
1123
+ const PHANTOM_REDIRECT_SOLANA_EXTENSION_KEY = "phantomRedirectSolana";
1124
+ /**
1125
+ * Adds the Phantom redirect Solana extension to the Dynamic client.
1126
+ *
1127
+ * ### What this extension does
1128
+ *
1129
+ * This extension enables Phantom wallet integration via deep link redirects,
1130
+ * designed for mobile environments where a browser extension is not available.
1131
+ *
1132
+ * On initialization it:
1133
+ * 1. Registers the internal Dynamic extension logic handlers.
1134
+ * 2. Detects whether the current URL is a Phantom redirect callback and, if
1135
+ * so, completes the pending operation (decrypt response, emit events,
1136
+ * broadcast to originating tab).
1137
+ * 3. Sets up the `onCloseTab` listener so that the clone tab is closed after completion.
1138
+ *
1139
+ * ### Mobile browser redirect flow
1140
+ *
1141
+ * ```
1142
+ * [Tab 1 — your app] [Phantom app / PWA] [Tab 2 — clone]
1143
+ * connect()
1144
+ * openDeeplink(phantomUrl) ─────────────►
1145
+ * user approves
1146
+ * redirect to app ───────────────►
1147
+ * completePhantomRedirect()
1148
+ * broadcastPhantomEvent()
1149
+ * emits phantomRedirectCloseTab
1150
+ * onCloseTab() → window.close()
1151
+ * BroadcastChannel receives ◄──────────────────────────────────────────────
1152
+ * deferredPromise.resolve()
1153
+ * ```
1154
+ *
1155
+ * ### Mobile native flow (React Native / Capacitor)
1156
+ *
1157
+ * The deep link is delivered to the same JavaScript context, so `Tab 2` never
1158
+ * exists. `completePhantomRedirect` detects this via a `sessionStorage`
1159
+ * marker written during request initiation and does **not** emit
1160
+ * `phantomRedirectCloseTab`.
1161
+ *
1162
+ * @param params.url - The current page URL
1163
+ * @param params.onCloseTab - Callback invoked when the clone tab should close
1164
+ * @param params.disableAutoRedirectCompletion - Skip auto detection/completion
1165
+ * @param [client] - The Dynamic client. Only required for multiple clients.
1166
+ */
1167
+ const addPhantomRedirectSolanaExtension = async ({ disableAutoRedirectCompletion, onCloseTab, url }, client = (0, _dynamic_labs_sdk_client_core.getDefaultClient)()) => {
1168
+ if ((0, _dynamic_labs_sdk_client_core.hasExtension)({ extensionKey: PHANTOM_REDIRECT_SOLANA_EXTENSION_KEY }, client)) return;
1169
+ (0, _dynamic_labs_sdk_client_core.registerExtension)({ extensionKey: PHANTOM_REDIRECT_SOLANA_EXTENSION_KEY }, client);
1170
+ require_isVersionedTransaction.registerSolanaNetworkProviderBuilder(client);
1171
+ const core = (0, _dynamic_labs_sdk_client_core.getCore)(client);
1172
+ if (!disableAutoRedirectCompletion) {
1173
+ core.logger.debug("[PHANTOM] addPhantomRedirectSolanaExtension: detecting redirect", { search: url.search });
1174
+ const isRedirect = await detectPhantomRedirect({ url }, client);
1175
+ core.logger.debug("[PHANTOM] addPhantomRedirectSolanaExtension: isRedirect", { isRedirect });
1176
+ if (isRedirect) {
1177
+ (0, _dynamic_labs_sdk_client.onceEvent)({
1178
+ event: "phantomRedirectCloseTab",
1179
+ listener: onCloseTab
1180
+ }, client);
1181
+ core.initTrack.track({
1182
+ name: "phantomRedirectCompletion",
1183
+ promise: completePhantomRedirect({ url }, client).catch((error) => {
1184
+ core.logger.debug("[PHANTOM] addPhantomRedirectSolanaExtension: completion failed", { error });
1185
+ core.logger.error("Phantom redirect completion failed:", error);
1186
+ })
1187
+ });
1188
+ }
1189
+ }
1190
+ const walletProvider = createPhantomRedirectWalletProvider({
1191
+ baseRedirectUrl: `${url.protocol}//${url.host}${url.pathname}`,
1192
+ dynamicClient: client
1193
+ });
1194
+ (0, _dynamic_labs_sdk_client_core.getWalletProviderRegistry)(client).register({
1195
+ priority: _dynamic_labs_sdk_client_core.WalletProviderPriority.WINDOW_INJECT,
1196
+ walletProvider
1197
+ });
1198
+ };
1199
+
226
1200
  //#endregion
227
1201
  //#region src/isSolanaWalletProvider/isSolanaWalletProvider.ts
228
1202
  const isSolanaWalletProvider = (provider) => {
@@ -315,8 +1289,11 @@ const signTransaction = async ({ walletAccount, transaction }, client = (0, _dyn
315
1289
  //#endregion
316
1290
  exports.NotSolanaProviderError = NotSolanaProviderError;
317
1291
  exports.SponsorTransactionError = require_addWaasSolanaExtension.SponsorTransactionError;
1292
+ exports.addPhantomRedirectSolanaExtension = addPhantomRedirectSolanaExtension;
318
1293
  exports.addSolanaExtension = addSolanaExtension;
319
1294
  exports.calculateSolanaTransactionFee = calculateSolanaTransactionFee;
1295
+ exports.completePhantomRedirect = completePhantomRedirect;
1296
+ exports.detectPhantomRedirect = detectPhantomRedirect;
320
1297
  exports.getSolanaConnection = require_addWaasSolanaExtension.getSolanaConnection;
321
1298
  exports.isSolanaNetworkProvider = isSolanaNetworkProvider;
322
1299
  exports.isSolanaWalletAccount = isSolanaWalletAccount;