@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.
- package/dist/{addSolanaWalletStandardExtension-DikNvaRF.esm.js → addSolanaWalletStandardExtension-B7GpGFx3.esm.js} +2 -2
- package/dist/{addSolanaWalletStandardExtension-DikNvaRF.esm.js.map → addSolanaWalletStandardExtension-B7GpGFx3.esm.js.map} +1 -1
- package/dist/{addSolanaWalletStandardExtension-D5uAzwwi.cjs.js → addSolanaWalletStandardExtension-Bv39eDZc.cjs.js} +2 -2
- package/dist/{addSolanaWalletStandardExtension-D5uAzwwi.cjs.js.map → addSolanaWalletStandardExtension-Bv39eDZc.cjs.js.map} +1 -1
- package/dist/{addWaasSolanaExtension-DBfiocjd.cjs.js → addWaasSolanaExtension-BaxzWnYL.cjs.js} +3 -3
- package/dist/{addWaasSolanaExtension-DBfiocjd.cjs.js.map → addWaasSolanaExtension-BaxzWnYL.cjs.js.map} +1 -1
- package/dist/{addWaasSolanaExtension-FUlrXwXm.esm.js → addWaasSolanaExtension-ofYQDYyc.esm.js} +2 -2
- package/dist/{addWaasSolanaExtension-FUlrXwXm.esm.js.map → addWaasSolanaExtension-ofYQDYyc.esm.js.map} +1 -1
- package/dist/exports/index.d.ts +6 -2
- package/dist/exports/index.d.ts.map +1 -1
- package/dist/index.cjs.js +980 -3
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +979 -7
- package/dist/index.esm.js.map +1 -1
- package/dist/{isVersionedTransaction-s73AwAKu.esm.js → isVersionedTransaction-CecentWi.esm.js} +2 -2
- package/dist/{isVersionedTransaction-s73AwAKu.esm.js.map → isVersionedTransaction-CecentWi.esm.js.map} +1 -1
- package/dist/{isVersionedTransaction-CSkcEh-G.cjs.js → isVersionedTransaction-DW-V1tUS.cjs.js} +3 -3
- package/dist/{isVersionedTransaction-CSkcEh-G.cjs.js.map → isVersionedTransaction-DW-V1tUS.cjs.js.map} +1 -1
- package/dist/phantomRedirect/PhantomRedirectWalletProvider.types.d.ts +112 -0
- package/dist/phantomRedirect/PhantomRedirectWalletProvider.types.d.ts.map +1 -0
- package/dist/phantomRedirect/addPhantomRedirectSolanaExtension/addPhantomRedirectSolanaExtension.d.ts +115 -0
- package/dist/phantomRedirect/addPhantomRedirectSolanaExtension/addPhantomRedirectSolanaExtension.d.ts.map +1 -0
- package/dist/phantomRedirect/addPhantomRedirectSolanaExtension/index.d.ts +3 -0
- package/dist/phantomRedirect/addPhantomRedirectSolanaExtension/index.d.ts.map +1 -0
- package/dist/phantomRedirect/completePhantomRedirect/completePhantomRedirect.d.ts +32 -0
- package/dist/phantomRedirect/completePhantomRedirect/completePhantomRedirect.d.ts.map +1 -0
- package/dist/phantomRedirect/completePhantomRedirect/index.d.ts +3 -0
- package/dist/phantomRedirect/completePhantomRedirect/index.d.ts.map +1 -0
- package/dist/phantomRedirect/createPhantomRedirectWalletProvider/createPhantomRedirectWalletProvider.d.ts +17 -0
- package/dist/phantomRedirect/createPhantomRedirectWalletProvider/createPhantomRedirectWalletProvider.d.ts.map +1 -0
- package/dist/phantomRedirect/createPhantomRedirectWalletProvider/index.d.ts +3 -0
- package/dist/phantomRedirect/createPhantomRedirectWalletProvider/index.d.ts.map +1 -0
- package/dist/phantomRedirect/detectPhantomRedirect/detectPhantomRedirect.d.ts +17 -0
- package/dist/phantomRedirect/detectPhantomRedirect/detectPhantomRedirect.d.ts.map +1 -0
- package/dist/phantomRedirect/detectPhantomRedirect/index.d.ts +3 -0
- package/dist/phantomRedirect/detectPhantomRedirect/index.d.ts.map +1 -0
- package/dist/phantomRedirect/errors/NoPendingPhantomRequestError.d.ts +5 -0
- package/dist/phantomRedirect/errors/NoPendingPhantomRequestError.d.ts.map +1 -0
- package/dist/phantomRedirect/errors/NoPhantomSessionError.d.ts +5 -0
- package/dist/phantomRedirect/errors/NoPhantomSessionError.d.ts.map +1 -0
- package/dist/phantomRedirect/errors/PhantomMissingEncryptionParamsError.d.ts +5 -0
- package/dist/phantomRedirect/errors/PhantomMissingEncryptionParamsError.d.ts.map +1 -0
- package/dist/phantomRedirect/errors/PhantomRedirectDecryptionError.d.ts +5 -0
- package/dist/phantomRedirect/errors/PhantomRedirectDecryptionError.d.ts.map +1 -0
- package/dist/phantomRedirect/errors/PhantomRedirectRejectedError.d.ts +9 -0
- package/dist/phantomRedirect/errors/PhantomRedirectRejectedError.d.ts.map +1 -0
- package/dist/phantomRedirect/errors/PhantomRedirectTimeoutError.d.ts +5 -0
- package/dist/phantomRedirect/errors/PhantomRedirectTimeoutError.d.ts.map +1 -0
- package/dist/phantomRedirect/phantomRedirect.broadcast.types.d.ts +6 -0
- package/dist/phantomRedirect/phantomRedirect.broadcast.types.d.ts.map +1 -0
- package/dist/phantomRedirect/phantomRedirect.constants.d.ts +5 -0
- package/dist/phantomRedirect/phantomRedirect.constants.d.ts.map +1 -0
- package/dist/phantomRedirect/phantomRedirect.events.d.ts +152 -0
- package/dist/phantomRedirect/phantomRedirect.events.d.ts.map +1 -0
- package/dist/phantomRedirect/storage/pendingRequestStorageSchema/index.d.ts +2 -0
- package/dist/phantomRedirect/storage/pendingRequestStorageSchema/index.d.ts.map +1 -0
- package/dist/phantomRedirect/storage/pendingRequestStorageSchema/pendingRequestStorageSchema.d.ts +15 -0
- package/dist/phantomRedirect/storage/pendingRequestStorageSchema/pendingRequestStorageSchema.d.ts.map +1 -0
- package/dist/phantomRedirect/storage/phantomSessionStorageSchema/index.d.ts +2 -0
- package/dist/phantomRedirect/storage/phantomSessionStorageSchema/index.d.ts.map +1 -0
- package/dist/phantomRedirect/storage/phantomSessionStorageSchema/phantomSessionStorageSchema.d.ts +14 -0
- package/dist/phantomRedirect/storage/phantomSessionStorageSchema/phantomSessionStorageSchema.d.ts.map +1 -0
- package/dist/phantomRedirect/utils/broadcastPhantomEvent/broadcastPhantomEvent.d.ts +16 -0
- package/dist/phantomRedirect/utils/broadcastPhantomEvent/broadcastPhantomEvent.d.ts.map +1 -0
- package/dist/phantomRedirect/utils/broadcastPhantomEvent/index.d.ts +2 -0
- package/dist/phantomRedirect/utils/broadcastPhantomEvent/index.d.ts.map +1 -0
- package/dist/phantomRedirect/utils/buildPhantomDeepLink/buildPhantomDeepLink.d.ts +23 -0
- package/dist/phantomRedirect/utils/buildPhantomDeepLink/buildPhantomDeepLink.d.ts.map +1 -0
- package/dist/phantomRedirect/utils/buildPhantomDeepLink/index.d.ts +3 -0
- package/dist/phantomRedirect/utils/buildPhantomDeepLink/index.d.ts.map +1 -0
- package/dist/phantomRedirect/utils/cleanPhantomParamsFromUrl/cleanPhantomParamsFromUrl.d.ts +15 -0
- package/dist/phantomRedirect/utils/cleanPhantomParamsFromUrl/cleanPhantomParamsFromUrl.d.ts.map +1 -0
- package/dist/phantomRedirect/utils/cleanPhantomParamsFromUrl/index.d.ts +3 -0
- package/dist/phantomRedirect/utils/cleanPhantomParamsFromUrl/index.d.ts.map +1 -0
- package/dist/phantomRedirect/utils/crypto/createNaClSharedSecret/createNaClSharedSecret.d.ts +13 -0
- package/dist/phantomRedirect/utils/crypto/createNaClSharedSecret/createNaClSharedSecret.d.ts.map +1 -0
- package/dist/phantomRedirect/utils/crypto/createNaClSharedSecret/index.d.ts +3 -0
- package/dist/phantomRedirect/utils/crypto/createNaClSharedSecret/index.d.ts.map +1 -0
- package/dist/phantomRedirect/utils/crypto/decryptPayload/decryptPayload.d.ts +16 -0
- package/dist/phantomRedirect/utils/crypto/decryptPayload/decryptPayload.d.ts.map +1 -0
- package/dist/phantomRedirect/utils/crypto/decryptPayload/index.d.ts +3 -0
- package/dist/phantomRedirect/utils/crypto/decryptPayload/index.d.ts.map +1 -0
- package/dist/phantomRedirect/utils/crypto/encryptPayload/encryptPayload.d.ts +17 -0
- package/dist/phantomRedirect/utils/crypto/encryptPayload/encryptPayload.d.ts.map +1 -0
- package/dist/phantomRedirect/utils/crypto/encryptPayload/index.d.ts +3 -0
- package/dist/phantomRedirect/utils/crypto/encryptPayload/index.d.ts.map +1 -0
- package/dist/phantomRedirect/utils/crypto/generateNaClKeyPair/generateNaClKeyPair.d.ts +11 -0
- package/dist/phantomRedirect/utils/crypto/generateNaClKeyPair/generateNaClKeyPair.d.ts.map +1 -0
- package/dist/phantomRedirect/utils/crypto/generateNaClKeyPair/index.d.ts +3 -0
- package/dist/phantomRedirect/utils/crypto/generateNaClKeyPair/index.d.ts.map +1 -0
- package/dist/phantomRedirect/utils/getPhantomCluster/getPhantomCluster.d.ts +23 -0
- package/dist/phantomRedirect/utils/getPhantomCluster/getPhantomCluster.d.ts.map +1 -0
- package/dist/phantomRedirect/utils/getPhantomOriginTab/getPhantomOriginTab.d.ts +19 -0
- package/dist/phantomRedirect/utils/getPhantomOriginTab/getPhantomOriginTab.d.ts.map +1 -0
- package/dist/phantomRedirect/utils/getPhantomOriginTab/index.d.ts +3 -0
- package/dist/phantomRedirect/utils/getPhantomOriginTab/index.d.ts.map +1 -0
- package/dist/phantomRedirect/utils/listenForPhantomBroadcast/index.d.ts +2 -0
- package/dist/phantomRedirect/utils/listenForPhantomBroadcast/index.d.ts.map +1 -0
- package/dist/phantomRedirect/utils/listenForPhantomBroadcast/listenForPhantomBroadcast.d.ts +15 -0
- package/dist/phantomRedirect/utils/listenForPhantomBroadcast/listenForPhantomBroadcast.d.ts.map +1 -0
- package/dist/phantomRedirect/utils/parsePhantomRedirectParams/index.d.ts +3 -0
- package/dist/phantomRedirect/utils/parsePhantomRedirectParams/index.d.ts.map +1 -0
- package/dist/phantomRedirect/utils/parsePhantomRedirectParams/parsePhantomRedirectParams.d.ts +22 -0
- package/dist/phantomRedirect/utils/parsePhantomRedirectParams/parsePhantomRedirectParams.d.ts.map +1 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/waas.cjs.js +3 -3
- package/dist/waas.esm.js +2 -2
- package/dist/walletStandard.cjs.js +2 -2
- package/dist/walletStandard.esm.js +2 -2
- package/package.json +7 -5
package/dist/index.cjs.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
const require_addSolanaWalletStandardExtension = require('./addSolanaWalletStandardExtension-
|
|
2
|
-
const require_isVersionedTransaction = require('./isVersionedTransaction-
|
|
3
|
-
const require_addWaasSolanaExtension = require('./addWaasSolanaExtension-
|
|
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;
|