@dynamic-labs/solana 4.49.0 → 4.50.2

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/CHANGELOG.md CHANGED
@@ -1,4 +1,28 @@
1
1
 
2
+ ### [4.50.2](https://github.com/dynamic-labs/dynamic-auth/compare/v4.50.1...v4.50.2) (2025-12-12)
3
+
4
+
5
+ ### Bug Fixes
6
+
7
+ * **flutter:** flutter should not require redirectUrl ([#10094](https://github.com/dynamic-labs/dynamic-auth/issues/10094)) ([7a14d7f](https://github.com/dynamic-labs/dynamic-auth/commit/7a14d7f235eaa308b2dfa42954fc38d484f9d3d3))
8
+ * properly handle signAllTransactions when using Solana Wallet Standard wallets ([#10084](https://github.com/dynamic-labs/dynamic-auth/issues/10084)) ([e3b2872](https://github.com/dynamic-labs/dynamic-auth/commit/e3b287252460150661df4f09a4eb9ddf2d23cb64))
9
+ * set authMode in configWaasWalletConnector in useSetWalletConnectorFetchers ([#10087](https://github.com/dynamic-labs/dynamic-auth/issues/10087)) ([c5f95de](https://github.com/dynamic-labs/dynamic-auth/commit/c5f95dec76c14e5bdd41b6ba88a3d8c45e616fc6))
10
+
11
+ ## [4.50.0](https://github.com/dynamic-labs/dynamic-auth/compare/v4.49.0...v4.50.0) (2025-12-09)
12
+
13
+
14
+ ### Features
15
+
16
+ * add usePhantomRedirectEvents hook ([#10030](https://github.com/dynamic-labs/dynamic-auth/issues/10030)) ([fb8585e](https://github.com/dynamic-labs/dynamic-auth/commit/fb8585eec9847cd350be71d28291e9831a3f31b1))
17
+ * introduce embedded wallet reveal complete events ([#10068](https://github.com/dynamic-labs/dynamic-auth/issues/10068)) ([8ea79ce](https://github.com/dynamic-labs/dynamic-auth/commit/8ea79ceaba36745d46759c8e8c6681895b092096))
18
+ * **react-native:** add download file ([#9954](https://github.com/dynamic-labs/dynamic-auth/issues/9954)) ([408b00f](https://github.com/dynamic-labs/dynamic-auth/commit/408b00fabd2afbd87d12efb51e78ecbd89a323d3))
19
+ * **react-native:** add solana Phantom support with client module for listening to events ([#10033](https://github.com/dynamic-labs/dynamic-auth/issues/10033)) ([a5a2d11](https://github.com/dynamic-labs/dynamic-auth/commit/a5a2d11ccd3c102e22dece808a0ba4116155a582))
20
+
21
+
22
+ ### Bug Fixes
23
+
24
+ * remove overflow from token balance dropdown ([#10044](https://github.com/dynamic-labs/dynamic-auth/issues/10044)) ([b37c5bb](https://github.com/dynamic-labs/dynamic-auth/commit/b37c5bb8e8c916b8d685b495555a3a3481cd5f0d))
25
+
2
26
  ## [4.49.0](https://github.com/dynamic-labs/dynamic-auth/compare/v4.48.2...v4.49.0) (2025-12-05)
3
27
 
4
28
 
package/package.cjs CHANGED
@@ -3,6 +3,6 @@
3
3
 
4
4
  Object.defineProperty(exports, '__esModule', { value: true });
5
5
 
6
- var version = "4.49.0";
6
+ var version = "4.50.2";
7
7
 
8
8
  exports.version = version;
package/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  'use client'
2
- var version = "4.49.0";
2
+ var version = "4.50.2";
3
3
 
4
4
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynamic-labs/solana",
3
- "version": "4.49.0",
3
+ "version": "4.50.2",
4
4
  "description": "A React SDK for implementing wallet web3 authentication and authorization to your website.",
5
5
  "author": "Dynamic Labs, Inc.",
6
6
  "license": "MIT",
@@ -18,7 +18,7 @@
18
18
  },
19
19
  "homepage": "https://www.dynamic.xyz/",
20
20
  "dependencies": {
21
- "@dynamic-labs/wallet-connect": "4.49.0",
21
+ "@dynamic-labs/wallet-connect": "4.50.2",
22
22
  "@solana/web3.js": "1.98.1",
23
23
  "@wallet-standard/app": "1.0.1",
24
24
  "@wallet-standard/base": "1.0.1",
@@ -29,17 +29,17 @@
29
29
  "@walletconnect/sign-client": "2.21.5",
30
30
  "@walletconnect/utils": "2.21.5",
31
31
  "@walletconnect/types": "2.21.5",
32
- "@dynamic-labs/assert-package-version": "4.49.0",
33
- "@dynamic-labs/embedded-wallet-solana": "4.49.0",
34
- "@dynamic-labs/logger": "4.49.0",
35
- "@dynamic-labs/rpc-providers": "4.49.0",
32
+ "@dynamic-labs/assert-package-version": "4.50.2",
33
+ "@dynamic-labs/embedded-wallet-solana": "4.50.2",
34
+ "@dynamic-labs/logger": "4.50.2",
35
+ "@dynamic-labs/rpc-providers": "4.50.2",
36
36
  "@dynamic-labs/sdk-api-core": "0.0.831",
37
- "@dynamic-labs/solana-core": "4.49.0",
38
- "@dynamic-labs/types": "4.49.0",
39
- "@dynamic-labs/utils": "4.49.0",
40
- "@dynamic-labs/waas-svm": "4.49.0",
41
- "@dynamic-labs/wallet-book": "4.49.0",
42
- "@dynamic-labs/wallet-connector-core": "4.49.0",
37
+ "@dynamic-labs/solana-core": "4.50.2",
38
+ "@dynamic-labs/types": "4.50.2",
39
+ "@dynamic-labs/utils": "4.50.2",
40
+ "@dynamic-labs/waas-svm": "4.50.2",
41
+ "@dynamic-labs/wallet-book": "4.50.2",
42
+ "@dynamic-labs/wallet-connector-core": "4.50.2",
43
43
  "eventemitter3": "5.0.1"
44
44
  },
45
45
  "peerDependencies": {}
package/src/index.cjs CHANGED
@@ -22,6 +22,7 @@ var createSolanaSignerFromWalletStandard = require('./injected/walletStandard/cr
22
22
  var getWalletStandardWallets = require('./injected/walletStandard/getWalletStandardWallets/getWalletStandardWallets.cjs');
23
23
  var hasAllWalletStandardRequiredFeatures = require('./injected/walletStandard/hasAllWalletStandardRequiredFeatures/hasAllWalletStandardRequiredFeatures.cjs');
24
24
  require('./injected/FallbackSolanaConnector/FallbackSolanaConnector.cjs');
25
+ var PhantomRedirect = require('./phantomRedirect/PhantomRedirect/PhantomRedirect.cjs');
25
26
 
26
27
  /* eslint-disable @typescript-eslint/no-unused-vars */
27
28
  assertPackageVersion.assertPackageVersion('@dynamic-labs/solana', _package.version);
@@ -44,3 +45,4 @@ exports.isSignedMessage = isSignedMessage.isSignedMessage;
44
45
  exports.createSolanaSignerFromWalletStandard = createSolanaSignerFromWalletStandard.createSolanaSignerFromWalletStandard;
45
46
  exports.getWalletStandardWallets = getWalletStandardWallets.getWalletStandardWallets;
46
47
  exports.hasAllWalletStandardRequiredFeatures = hasAllWalletStandardRequiredFeatures.hasAllWalletStandardRequiredFeatures;
48
+ exports.PhantomRedirect = PhantomRedirect.PhantomRedirect;
package/src/index.d.ts CHANGED
@@ -8,3 +8,4 @@ export { SolanaWalletConnectorsWithConfig } from './SolanaWalletConnectorsWithCo
8
8
  export { isBackpackSolanaSigner } from './utils/isBackpackSolanaSigner';
9
9
  export { isSignedMessage } from './utils/isSignedMessage';
10
10
  export { getWalletStandardWallets, createSolanaSignerFromWalletStandard, hasAllWalletStandardRequiredFeatures, } from './injected';
11
+ export { PhantomRedirect } from './phantomRedirect/PhantomRedirect';
package/src/index.js CHANGED
@@ -18,6 +18,7 @@ export { createSolanaSignerFromWalletStandard } from './injected/walletStandard/
18
18
  export { getWalletStandardWallets } from './injected/walletStandard/getWalletStandardWallets/getWalletStandardWallets.js';
19
19
  export { hasAllWalletStandardRequiredFeatures } from './injected/walletStandard/hasAllWalletStandardRequiredFeatures/hasAllWalletStandardRequiredFeatures.js';
20
20
  import './injected/FallbackSolanaConnector/FallbackSolanaConnector.js';
21
+ export { PhantomRedirect } from './phantomRedirect/PhantomRedirect/PhantomRedirect.js';
21
22
 
22
23
  /* eslint-disable @typescript-eslint/no-unused-vars */
23
24
  assertPackageVersion('@dynamic-labs/solana', version);
@@ -79,20 +79,30 @@ const createSolanaSignerFromWalletStandard = ({ wallet, walletConnector, }) => {
79
79
  return `solana:${cluster}`;
80
80
  };
81
81
  const signTransaction = (transaction) => _tslib.__awaiter(void 0, void 0, void 0, function* () {
82
+ const signedTransactions = yield signAllTransactions([transaction]);
83
+ return signedTransactions[0];
84
+ });
85
+ const signAllTransactions = (transactions) => _tslib.__awaiter(void 0, void 0, void 0, function* () {
86
+ const signTransactionMethod = features['solana:signTransaction'].signTransaction;
82
87
  const account = yield getCurrentAccount();
83
- const signTransactionResult = yield features['solana:signTransaction'].signTransaction({
88
+ const chain = getChain();
89
+ const transactionsInput = transactions.map((transaction) => ({
84
90
  account,
85
- chain: getChain(),
91
+ chain,
86
92
  transaction: transaction.serialize({
87
93
  requireAllSignatures: false,
88
94
  }),
95
+ }));
96
+ const signTransactionResult = yield signTransactionMethod(...transactionsInput);
97
+ const signedTransactions = signTransactionResult.map(({ signedTransaction }, index) => {
98
+ const inputTransaction = transactions[index];
99
+ if (isVersionedTransaction(inputTransaction)) {
100
+ return web3_js.VersionedTransaction.deserialize(signedTransaction);
101
+ }
102
+ return web3_js.Transaction.from(signedTransaction);
89
103
  });
90
- if (isVersionedTransaction(transaction)) {
91
- return web3_js.VersionedTransaction.deserialize(signTransactionResult[0].signedTransaction);
92
- }
93
- return web3_js.Transaction.from(signTransactionResult[0].signedTransaction);
104
+ return signedTransactions;
94
105
  });
95
- const signAllTransactions = (transactions) => _tslib.__awaiter(void 0, void 0, void 0, function* () { return Promise.all(transactions.map(signTransaction)); });
96
106
  const signAndSendTransaction = (transaction) => _tslib.__awaiter(void 0, void 0, void 0, function* () {
97
107
  var _c;
98
108
  const signAndSendTransactionMethod = (_c = features['solana:signAndSendTransaction']) === null || _c === void 0 ? void 0 : _c.signAndSendTransaction;
@@ -71,20 +71,30 @@ const createSolanaSignerFromWalletStandard = ({ wallet, walletConnector, }) => {
71
71
  return `solana:${cluster}`;
72
72
  };
73
73
  const signTransaction = (transaction) => __awaiter(void 0, void 0, void 0, function* () {
74
+ const signedTransactions = yield signAllTransactions([transaction]);
75
+ return signedTransactions[0];
76
+ });
77
+ const signAllTransactions = (transactions) => __awaiter(void 0, void 0, void 0, function* () {
78
+ const signTransactionMethod = features['solana:signTransaction'].signTransaction;
74
79
  const account = yield getCurrentAccount();
75
- const signTransactionResult = yield features['solana:signTransaction'].signTransaction({
80
+ const chain = getChain();
81
+ const transactionsInput = transactions.map((transaction) => ({
76
82
  account,
77
- chain: getChain(),
83
+ chain,
78
84
  transaction: transaction.serialize({
79
85
  requireAllSignatures: false,
80
86
  }),
87
+ }));
88
+ const signTransactionResult = yield signTransactionMethod(...transactionsInput);
89
+ const signedTransactions = signTransactionResult.map(({ signedTransaction }, index) => {
90
+ const inputTransaction = transactions[index];
91
+ if (isVersionedTransaction(inputTransaction)) {
92
+ return VersionedTransaction.deserialize(signedTransaction);
93
+ }
94
+ return Transaction.from(signedTransaction);
81
95
  });
82
- if (isVersionedTransaction(transaction)) {
83
- return VersionedTransaction.deserialize(signTransactionResult[0].signedTransaction);
84
- }
85
- return Transaction.from(signTransactionResult[0].signedTransaction);
96
+ return signedTransactions;
86
97
  });
87
- const signAllTransactions = (transactions) => __awaiter(void 0, void 0, void 0, function* () { return Promise.all(transactions.map(signTransaction)); });
88
98
  const signAndSendTransaction = (transaction) => __awaiter(void 0, void 0, void 0, function* () {
89
99
  var _c;
90
100
  const signAndSendTransactionMethod = (_c = features['solana:signAndSendTransaction']) === null || _c === void 0 ? void 0 : _c.signAndSendTransaction;
@@ -14,6 +14,7 @@ var clearRedirectUrlForPhantom = require('../clearRedirectUrlForPhantom/clearRed
14
14
  var decryptPayload = require('../decryptPayload/decryptPayload.cjs');
15
15
  var encryptPayload = require('../encryptPayload/encryptPayload.cjs');
16
16
  var storage = require('../storage/storage.cjs');
17
+ var logger = require('../../utils/logger.cjs');
17
18
 
18
19
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
19
20
 
@@ -29,6 +30,59 @@ class PhantomRedirect extends solanaCore.SolanaWalletConnector {
29
30
  getMethod() {
30
31
  throw new Error('Method not implemented.');
31
32
  }
33
+ /**
34
+ * Sets up a Promise/listener pattern for native mobile redirects.
35
+ * Returns undefined if not on native mobile.
36
+ */
37
+ setupNativeMobileListener({ eventName, methodName, getResult, shouldIgnoreEvent, }) {
38
+ if (!utils.PlatformService.isNativeMobile) {
39
+ return undefined;
40
+ }
41
+ const requestId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
42
+ storage.storage.requestId.set(requestId);
43
+ logger.logger.logVerboseTroubleshootingMessage(`[PhantomRedirect] ${methodName} - setting up listener`, { requestId });
44
+ return new Promise((resolve, reject) => {
45
+ const listener = (event) => {
46
+ // Check requestId mismatch or custom validation
47
+ if (event.requestId !== requestId ||
48
+ (shouldIgnoreEvent === null || shouldIgnoreEvent === void 0 ? void 0 : shouldIgnoreEvent(event, requestId))) {
49
+ logger.logger.logVerboseTroubleshootingMessage(`[PhantomRedirect] ${methodName} - ignoring event (requestId mismatch)`, {
50
+ expectedRequestId: requestId,
51
+ receivedRequestId: event.requestId,
52
+ });
53
+ return;
54
+ }
55
+ logger.logger.logVerboseTroubleshootingMessage(`[PhantomRedirect] ${methodName} - listener received matching event`, { errorCode: event.errorCode, requestId });
56
+ // Clean up this listener
57
+ this.off(eventName, listener);
58
+ if (event.errorCode) {
59
+ reject(new Error(event.errorMessage || event.errorCode));
60
+ }
61
+ else {
62
+ resolve(getResult(event));
63
+ }
64
+ };
65
+ this.on(eventName, listener);
66
+ });
67
+ }
68
+ /**
69
+ * Encrypts payload, builds Phantom redirect URL, stores method, and opens URL.
70
+ */
71
+ openPhantomUrl({ payload, sharedSecret, encryptionPublicKey, phantomEndpoint, methodToStore, }) {
72
+ const [nonce, encryptedPayload] = encryptPayload.encryptPayload(payload, sharedSecret);
73
+ const params = new URLSearchParams({
74
+ dapp_encryption_public_key: bs58__default["default"].encode(encryptionPublicKey),
75
+ nonce: bs58__default["default"].encode(nonce),
76
+ payload: bs58__default["default"].encode(encryptedPayload),
77
+ redirect_link: clearRedirectUrlForPhantom.clearRedirectUrlForPhantom(utils.PlatformService.getUrl()),
78
+ });
79
+ const url = buildUrl.buildUrl(phantomEndpoint, params);
80
+ storage.storage.method.set(methodToStore);
81
+ logger.logger.debug(`[PhantomRedirect] ${methodToStore} - opening Phantom`, {
82
+ isNativeMobile: utils.PlatformService.isNativeMobile,
83
+ });
84
+ utils.PlatformService.openURL(url);
85
+ }
32
86
  getAddress() {
33
87
  return _tslib.__awaiter(this, void 0, void 0, function* () {
34
88
  const address = storage.storage.address.get();
@@ -92,36 +146,58 @@ class PhantomRedirect extends solanaCore.SolanaWalletConnector {
92
146
  }
93
147
  signMessage(messageToSign) {
94
148
  return _tslib.__awaiter(this, void 0, void 0, function* () {
149
+ logger.logger.debug('[PhantomRedirect] signMessage called', {
150
+ messageLength: messageToSign.length,
151
+ messagePreview: messageToSign.substring(0, 200),
152
+ });
95
153
  const { session, sharedSecret, encryptionPublicKey } = this.getInputsOrThrow('signMessage', [], ['session', 'sharedSecret', 'encryptionPublicKey']);
96
154
  storage.storage.message.set(messageToSign);
97
155
  const payload = {
98
156
  message: bs58__default["default"].encode(Buffer.from(messageToSign)),
99
157
  session,
100
158
  };
101
- const [nonce, encryptedPayload] = encryptPayload.encryptPayload(payload, sharedSecret);
102
- const params = new URLSearchParams({
103
- dapp_encryption_public_key: bs58__default["default"].encode(encryptionPublicKey),
104
- nonce: bs58__default["default"].encode(nonce),
105
- payload: bs58__default["default"].encode(encryptedPayload),
106
- redirect_link: clearRedirectUrlForPhantom.clearRedirectUrlForPhantom(utils.PlatformService.getUrl()),
159
+ this.openPhantomUrl({
160
+ encryptionPublicKey,
161
+ methodToStore: 'signMessage',
162
+ payload,
163
+ phantomEndpoint: 'signMessage',
164
+ sharedSecret,
107
165
  });
108
- const url = buildUrl.buildUrl('signMessage', params);
109
- storage.storage.method.set('signMessage');
110
- utils.PlatformService.openURL(url);
111
- // throwing this to prevent local storage from being cleared.
112
- // when verifying signature, the SDK calls endSession when no
113
- // signature is returned. in the case of phantom mobile, a signature
114
- // is not returned from signMessage, so an error will always be thrown.
115
- // this is a workaround to prevent the SDK from clearing local storage
116
- // ideally we would figure out how to:
117
- // 1. kick off the sign message on one tab
118
- // 2. resume the process on that tab after the user signs in phantom
166
+ const nativePromise = this.setupNativeMobileListener({
167
+ eventName: 'signMessage',
168
+ getResult: (event) => event.signature,
169
+ methodName: 'signMessage',
170
+ shouldIgnoreEvent: (event) => event.message !== messageToSign,
171
+ });
172
+ if (nativePromise) {
173
+ return nativePromise;
174
+ }
175
+ // On mobile web browsers, Phantom opens in a new tab and redirects back
176
+ // to a fresh page load, losing this Promise/listener context.
177
+ // The signature is handled via PhantomRedirectContext which reads URL
178
+ // params on page load and emits the 'signMessage' event, but there's
179
+ // no way to return it to the original caller. Throwing here prevents
180
+ // the SDK from clearing local storage when no signature is returned.
181
+ logger.logger.debug('[PhantomRedirect] signMessage - mobile web, throwing ignore error');
119
182
  throw new Error('ignore');
120
183
  });
121
184
  }
122
185
  extractSignature() {
186
+ var _a;
187
+ logger.logger.debug('[PhantomRedirect] extractSignature called');
123
188
  const { data, nonce, sharedSecret, message } = this.getInputsOrThrow('extractSignature', ['data', 'nonce'], ['sharedSecret', 'message']);
189
+ logger.logger.debug('[PhantomRedirect] extractSignature - retrieved from storage', {
190
+ dataPresent: Boolean(data),
191
+ message,
192
+ messageLength: message === null || message === void 0 ? void 0 : message.length,
193
+ noncePresent: Boolean(nonce),
194
+ sharedSecretPresent: Boolean(sharedSecret),
195
+ });
124
196
  const signMessageData = decryptPayload.decryptPayload(data, nonce, sharedSecret);
197
+ logger.logger.debug('[PhantomRedirect] extractSignature - decrypted payload', {
198
+ signature: signMessageData.signature,
199
+ signatureLength: (_a = signMessageData.signature) === null || _a === void 0 ? void 0 : _a.length,
200
+ });
125
201
  return {
126
202
  message,
127
203
  signature: signMessageData.signature,
@@ -136,14 +212,55 @@ class PhantomRedirect extends solanaCore.SolanaWalletConnector {
136
212
  extractTransaction() {
137
213
  const { data, nonce, sharedSecret } = this.getInputsOrThrow('extractTransaction', ['data', 'nonce'], ['sharedSecret']);
138
214
  const signTransactionData = decryptPayload.decryptPayload(data, nonce, sharedSecret);
139
- const decodedTransaction = web3_js.Transaction.from(bs58__default["default"].decode(signTransactionData.transaction));
140
- return decodedTransaction;
215
+ const transactionBytes = bs58__default["default"].decode(signTransactionData.transaction);
216
+ // Try to deserialize as VersionedTransaction first, fall back to legacy Transaction
217
+ // VersionedTransaction has a version prefix byte, legacy transactions don't
218
+ try {
219
+ return web3_js.VersionedTransaction.deserialize(transactionBytes);
220
+ }
221
+ catch (_a) {
222
+ // If VersionedTransaction fails, try legacy Transaction
223
+ return web3_js.Transaction.from(transactionBytes);
224
+ }
225
+ }
226
+ /**
227
+ * Extracts the signed transaction and sends it to the network.
228
+ * Used for signAndSendTransaction since Phantom redirect doesn't support it natively.
229
+ * @returns The transaction signature
230
+ */
231
+ extractAndSendTransaction() {
232
+ return _tslib.__awaiter(this, void 0, void 0, function* () {
233
+ logger.logger.debug('[PhantomRedirect] extractAndSendTransaction called');
234
+ const signedTransaction = this.extractTransaction();
235
+ // Get stored send options if any
236
+ const sendOptionsJson = storage.storage.sendOptions.get();
237
+ storage.storage.sendOptions.remove();
238
+ const sendOptions = sendOptionsJson
239
+ ? JSON.parse(sendOptionsJson)
240
+ : undefined;
241
+ logger.logger.debug('[PhantomRedirect] Sending transaction to network', {
242
+ hasSendOptions: Boolean(sendOptions),
243
+ isVersioned: signedTransaction instanceof web3_js.VersionedTransaction,
244
+ });
245
+ // Send the signed transaction to the network
246
+ const serialized = signedTransaction.serialize();
247
+ const signature = yield this.getWalletClient().sendRawTransaction(serialized, sendOptions);
248
+ logger.logger.debug('[PhantomRedirect] Transaction sent successfully', {
249
+ signature,
250
+ });
251
+ return signature;
252
+ });
141
253
  }
142
254
  consumeMethod() {
143
255
  const method = storage.storage.method.get();
144
256
  storage.storage.method.remove();
145
257
  return method;
146
258
  }
259
+ consumeRequestId() {
260
+ const requestId = storage.storage.requestId.get();
261
+ storage.storage.requestId.remove();
262
+ return requestId;
263
+ }
147
264
  getSigner() {
148
265
  return _tslib.__awaiter(this, void 0, void 0, function* () {
149
266
  const address = storage.storage.address.get();
@@ -206,55 +323,67 @@ class PhantomRedirect extends solanaCore.SolanaWalletConnector {
206
323
  session,
207
324
  transactions: serializedTransactions,
208
325
  };
209
- const [nonce, encryptedPayload] = encryptPayload.encryptPayload(payload, sharedSecret);
210
- const params = new URLSearchParams({
211
- dapp_encryption_public_key: bs58__default["default"].encode(encryptionPublicKey),
212
- nonce: bs58__default["default"].encode(nonce),
213
- payload: bs58__default["default"].encode(encryptedPayload),
214
- redirect_link: clearRedirectUrlForPhantom.clearRedirectUrlForPhantom(utils.PlatformService.getUrl()),
326
+ this.openPhantomUrl({
327
+ encryptionPublicKey,
328
+ methodToStore: 'signAllTransactions',
329
+ payload,
330
+ phantomEndpoint: 'signAllTransactions',
331
+ sharedSecret,
332
+ });
333
+ const nativePromise = this.setupNativeMobileListener({
334
+ eventName: 'signAllTransactions',
335
+ getResult: (event) => (event.transactions || []),
336
+ methodName: 'signAllTransactions',
215
337
  });
216
- const url = buildUrl.buildUrl('signAllTransactions', params);
217
- utils.PlatformService.openURL(url);
218
- // actual signatures will be retrieved upon redirect back to dapp
338
+ if (nativePromise) {
339
+ return nativePromise;
340
+ }
341
+ // On mobile web browsers, return empty array
342
+ // The signed transactions are handled via PhantomRedirectContext
219
343
  return [];
220
344
  }),
221
345
  signAndSendTransaction: (transaction, options) => _tslib.__awaiter(this, void 0, void 0, function* () {
346
+ // Phantom redirect doesn't support signAndSendTransaction natively,
347
+ // so we use signTransaction and then send it manually after redirect
348
+ const serializedTransaction = bs58__default["default"].encode(transaction.serialize({ requireAllSignatures: false }));
222
349
  const { session, sharedSecret, encryptionPublicKey } = this.getInputsOrThrow('signAndSendTransaction', [], ['session', 'sharedSecret', 'encryptionPublicKey']);
223
350
  const payload = {
224
- options,
225
351
  session,
226
- transaction: bs58__default["default"].encode(transaction.serialize({ requireAllSignatures: false })),
352
+ transaction: serializedTransaction,
227
353
  };
228
- const [nonce, encryptedPayload] = encryptPayload.encryptPayload(payload, sharedSecret);
229
- const params = new URLSearchParams({
230
- dapp_encryption_public_key: bs58__default["default"].encode(encryptionPublicKey),
231
- nonce: bs58__default["default"].encode(nonce),
232
- payload: bs58__default["default"].encode(encryptedPayload),
233
- redirect_link: clearRedirectUrlForPhantom.clearRedirectUrlForPhantom(utils.PlatformService.getUrl()),
354
+ // Store send options to use when sending the transaction
355
+ if (options) {
356
+ storage.storage.sendOptions.set(JSON.stringify(options));
357
+ }
358
+ // Use signTransaction endpoint since Phantom redirect doesn't support signAndSendTransaction
359
+ // but store method as signAndSendTransaction so we know to send after signing
360
+ this.openPhantomUrl({
361
+ encryptionPublicKey,
362
+ methodToStore: 'signAndSendTransaction',
363
+ payload,
364
+ phantomEndpoint: 'signTransaction',
365
+ sharedSecret,
234
366
  });
235
- const url = buildUrl.buildUrl('signAndSendTransaction', params);
236
- storage.storage.method.set('signAndSendTransaction');
237
- utils.PlatformService.openURL(url);
238
- // actual signature will be retrived upon redirect back to dapp
367
+ const nativePromise = this.setupNativeMobileListener({
368
+ eventName: 'signAndSendTransaction',
369
+ getResult: (event) => ({ signature: event.signature || '' }),
370
+ methodName: 'signAndSendTransaction',
371
+ });
372
+ if (nativePromise) {
373
+ return nativePromise;
374
+ }
375
+ // On mobile web browsers, return empty signature
376
+ // The actual signature is handled via PhantomRedirectContext
239
377
  return { signature: '' };
240
378
  }),
241
379
  signMessage: (message) => _tslib.__awaiter(this, void 0, void 0, function* () {
242
- const { session, sharedSecret, encryptionPublicKey } = this.getInputsOrThrow('signMessage', [], ['session', 'sharedSecret', 'encryptionPublicKey']);
243
- const payload = {
244
- message: bs58__default["default"].encode(Buffer.from(message)),
245
- session,
380
+ // Delegate to connector's signMessage which properly stores method and sets up listeners
381
+ const messageString = new TextDecoder().decode(message);
382
+ const signature = yield this.signMessage(messageString);
383
+ // Convert signature string to Uint8Array for ISolana interface
384
+ return {
385
+ signature: signature ? bs58__default["default"].decode(signature) : new Uint8Array(0),
246
386
  };
247
- const [nonce, encryptedPayload] = encryptPayload.encryptPayload(payload, sharedSecret);
248
- const params = new URLSearchParams({
249
- dapp_encryption_public_key: bs58__default["default"].encode(encryptionPublicKey),
250
- nonce: bs58__default["default"].encode(nonce),
251
- payload: bs58__default["default"].encode(encryptedPayload),
252
- redirect_link: clearRedirectUrlForPhantom.clearRedirectUrlForPhantom(utils.PlatformService.getUrl()),
253
- });
254
- const url = buildUrl.buildUrl('signMessage', params);
255
- utils.PlatformService.openURL(url);
256
- // actual signature will be retrived upon redirect back to dapp
257
- return { signature: Buffer.from('') };
258
387
  }),
259
388
  signTransaction: (transaction) => _tslib.__awaiter(this, void 0, void 0, function* () {
260
389
  const serializedTransaction = bs58__default["default"].encode(transaction.serialize({
@@ -265,15 +394,23 @@ class PhantomRedirect extends solanaCore.SolanaWalletConnector {
265
394
  session,
266
395
  transaction: serializedTransaction,
267
396
  };
268
- const [nonce, encryptedPayload] = encryptPayload.encryptPayload(payload, sharedSecret);
269
- const params = new URLSearchParams({
270
- dapp_encryption_public_key: bs58__default["default"].encode(encryptionPublicKey),
271
- nonce: bs58__default["default"].encode(nonce),
272
- payload: bs58__default["default"].encode(encryptedPayload),
273
- redirect_link: clearRedirectUrlForPhantom.clearRedirectUrlForPhantom(utils.PlatformService.getUrl()),
397
+ this.openPhantomUrl({
398
+ encryptionPublicKey,
399
+ methodToStore: 'signTransaction',
400
+ payload,
401
+ phantomEndpoint: 'signTransaction',
402
+ sharedSecret,
403
+ });
404
+ const nativePromise = this.setupNativeMobileListener({
405
+ eventName: 'signTransaction',
406
+ getResult: (event) => event.transaction,
407
+ methodName: 'signTransaction',
274
408
  });
275
- const url = buildUrl.buildUrl('signTransaction', params);
276
- utils.PlatformService.openURL(url);
409
+ if (nativePromise) {
410
+ return nativePromise;
411
+ }
412
+ // On mobile web browsers, return the original transaction
413
+ // The signed transaction is handled via PhantomRedirectContext
277
414
  return transaction;
278
415
  }),
279
416
  };
@@ -1,4 +1,4 @@
1
- import { Transaction } from '@solana/web3.js';
1
+ import { Transaction, VersionedTransaction } from '@solana/web3.js';
2
2
  import { SolanaWalletConnector, type ISolana } from '@dynamic-labs/solana-core';
3
3
  import { IPhantomRedirectConnector } from '@dynamic-labs/wallet-connector-core';
4
4
  import { Method } from '../types';
@@ -7,6 +7,15 @@ export declare class PhantomRedirect extends SolanaWalletConnector implements IP
7
7
  overrideKey: string;
8
8
  constructor(props: any);
9
9
  getMethod(): 'signMessage' | 'signAndSendTransaction' | undefined;
10
+ /**
11
+ * Sets up a Promise/listener pattern for native mobile redirects.
12
+ * Returns undefined if not on native mobile.
13
+ */
14
+ private setupNativeMobileListener;
15
+ /**
16
+ * Encrypts payload, builds Phantom redirect URL, stores method, and opens URL.
17
+ */
18
+ private openPhantomUrl;
10
19
  getAddress(): Promise<string | undefined>;
11
20
  connect(): Promise<void>;
12
21
  getSession(): Promise<string>;
@@ -16,8 +25,15 @@ export declare class PhantomRedirect extends SolanaWalletConnector implements IP
16
25
  message: string;
17
26
  };
18
27
  extractTransactions(): Transaction[];
19
- extractTransaction(): Transaction;
28
+ extractTransaction(): Transaction | VersionedTransaction;
29
+ /**
30
+ * Extracts the signed transaction and sends it to the network.
31
+ * Used for signAndSendTransaction since Phantom redirect doesn't support it natively.
32
+ * @returns The transaction signature
33
+ */
34
+ extractAndSendTransaction(): Promise<string>;
20
35
  consumeMethod(): Method | undefined;
36
+ consumeRequestId(): string | undefined;
21
37
  getSigner(): Promise<ISolana | undefined>;
22
38
  getConnectedAccounts(): Promise<string[]>;
23
39
  endSession(): Promise<void>;