@vue-solana/core 0.4.1 → 0.4.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.
@@ -29,6 +29,7 @@ function resolveTweetNaCl() {
29
29
  }
30
30
  const DEFAULT_IOS_WALLET_CHAINS = ["solana:mainnet", "solana:devnet"];
31
31
  const PENDING_REQUEST_KEY = "vue-solana:ios-wallet:pending";
32
+ const PENDING_REQUEST_TTL_MS = 10 * 60 * 1e3;
32
33
  const SESSION_PREFIX = "vue-solana:ios-wallet:session:";
33
34
  const CALLBACK_PARAMS = /* @__PURE__ */ new Set([
34
35
  "data",
@@ -160,6 +161,11 @@ function adaptSolanaIosWallet(walletInfo, options = {}) {
160
161
  transactions,
161
162
  options
162
163
  );
164
+ if (signedTransactions.length !== transactions.length) {
165
+ throw new Error(
166
+ `iOS wallet returned ${signedTransactions.length} signed transactions for ${transactions.length} requested transactions`
167
+ );
168
+ }
163
169
  return signedTransactions.map(
164
170
  (transaction, index) => deserializeTransaction(transactions[index], transaction)
165
171
  );
@@ -187,36 +193,50 @@ function handleSolanaIosWalletCallback(options = {}) {
187
193
  cleanCallbackUrl(url, options.clearUrl);
188
194
  throw new Error("Received an iOS wallet callback without a pending request");
189
195
  }
196
+ if (isPendingRequestExpired(pending)) {
197
+ clearPendingRequest();
198
+ cleanCallbackUrl(url, options.clearUrl);
199
+ throw new Error("Received an expired iOS wallet callback");
200
+ }
190
201
  const definition = getIosWalletDefinitionById(pending.walletId);
191
202
  const walletEncryptionPublicKey = url.searchParams.get(definition.encryptionPublicKeyParam);
192
203
  const nonce = url.searchParams.get("nonce");
193
204
  const data = url.searchParams.get("data");
194
205
  if (!nonce || !data) {
206
+ clearPendingRequest();
207
+ cleanCallbackUrl(url, options.clearUrl);
195
208
  throw new Error("Received an incomplete iOS wallet callback");
196
209
  }
197
- const sharedSecret = getSharedSecret(
198
- walletEncryptionPublicKey ?? getStoredSession(pending.walletId)?.walletEncryptionPublicKey,
199
- pending.dappEncryptionSecretKey
200
- );
201
- const payload = decryptPayload(data, nonce, sharedSecret);
202
- const result = createCallbackResult(pending, payload);
203
- if (pending.method === "connect") {
204
- if (!walletEncryptionPublicKey) {
205
- throw new Error("iOS wallet connect callback did not include an encryption public key");
210
+ try {
211
+ const sharedSecret = getSharedSecret(
212
+ walletEncryptionPublicKey ?? getStoredSession(pending.walletId)?.walletEncryptionPublicKey,
213
+ pending.dappEncryptionSecretKey
214
+ );
215
+ const payload = decryptPayload(data, nonce, sharedSecret);
216
+ const result = createCallbackResult(pending, payload);
217
+ if (pending.method === "connect") {
218
+ if (!walletEncryptionPublicKey) {
219
+ throw new Error("iOS wallet connect callback did not include an encryption public key");
220
+ }
221
+ const publicKey = getPublicKeyField(payload, "public_key");
222
+ storeSession({
223
+ walletId: pending.walletId,
224
+ publicKey,
225
+ session: getStringField(payload, "session"),
226
+ dappEncryptionPublicKey: pending.dappEncryptionPublicKey,
227
+ dappEncryptionSecretKey: pending.dappEncryptionSecretKey,
228
+ walletEncryptionPublicKey,
229
+ sharedSecret: bs58__default.encode(sharedSecret)
230
+ });
206
231
  }
207
- storeSession({
208
- walletId: pending.walletId,
209
- publicKey: getStringField(payload, "public_key"),
210
- session: getStringField(payload, "session"),
211
- dappEncryptionPublicKey: pending.dappEncryptionPublicKey,
212
- dappEncryptionSecretKey: pending.dappEncryptionSecretKey,
213
- walletEncryptionPublicKey,
214
- sharedSecret: bs58__default.encode(sharedSecret)
215
- });
216
- }
217
- clearPendingRequest();
218
- cleanCallbackUrl(url, options.clearUrl);
219
- return result;
232
+ clearPendingRequest();
233
+ cleanCallbackUrl(url, options.clearUrl);
234
+ return result;
235
+ } catch (cause) {
236
+ clearPendingRequest();
237
+ cleanCallbackUrl(url, options.clearUrl);
238
+ throw cause;
239
+ }
220
240
  }
221
241
  function isSolanaIosWalletInfo(walletInfo) {
222
242
  return walletInfo.source === "deep-link" && isIosWalletDefinition(walletInfo.wallet);
@@ -316,7 +336,8 @@ async function launchEncryptedWalletRequest(definition, method, options, payload
316
336
  publicKey: bs58__default.decode(session.dappEncryptionPublicKey),
317
337
  secretKey: bs58__default.decode(session.dappEncryptionSecretKey)
318
338
  },
319
- options.redirectUrl
339
+ options.redirectUrl,
340
+ method === "signAllTransactions" && Array.isArray(payload.transactions) ? payload.transactions.length : void 0
320
341
  );
321
342
  const nonce = nacl.randomBytes(nacl.box.nonceLength);
322
343
  const encryptedPayload = encryptPayload(
@@ -333,7 +354,7 @@ async function launchEncryptedWalletRequest(definition, method, options, payload
333
354
  openIosWalletUrl(url.toString());
334
355
  return waitForRedirect();
335
356
  }
336
- function createPendingRequest(walletId, method, keyPair, redirectUrl) {
357
+ function createPendingRequest(walletId, method, keyPair, redirectUrl, requestedTransactionCount) {
337
358
  return {
338
359
  id: createRequestId(),
339
360
  walletId,
@@ -341,7 +362,8 @@ function createPendingRequest(walletId, method, keyPair, redirectUrl) {
341
362
  dappEncryptionPublicKey: bs58__default.encode(keyPair.publicKey),
342
363
  dappEncryptionSecretKey: bs58__default.encode(keyPair.secretKey),
343
364
  redirectUrl: redirectUrl ?? getDefaultIosWalletRedirectUrl(),
344
- createdAt: Date.now()
365
+ createdAt: Date.now(),
366
+ requestedTransactionCount
345
367
  };
346
368
  }
347
369
  function createCallbackResult(pending, payload) {
@@ -349,7 +371,7 @@ function createCallbackResult(pending, payload) {
349
371
  return {
350
372
  walletId: pending.walletId,
351
373
  method: pending.method,
352
- publicKey: getStringField(payload, "public_key")
374
+ publicKey: getPublicKeyField(payload, "public_key")
353
375
  };
354
376
  }
355
377
  if (pending.method === "signTransaction") {
@@ -364,6 +386,11 @@ function createCallbackResult(pending, payload) {
364
386
  if (!Array.isArray(transactions) || transactions.some((transaction) => typeof transaction !== "string")) {
365
387
  throw new Error("iOS wallet returned invalid signed transactions");
366
388
  }
389
+ if (typeof pending.requestedTransactionCount === "number" && transactions.length !== pending.requestedTransactionCount) {
390
+ throw new Error(
391
+ `iOS wallet returned ${transactions.length} signed transactions for ${pending.requestedTransactionCount} requested transactions`
392
+ );
393
+ }
367
394
  return {
368
395
  walletId: pending.walletId,
369
396
  method: pending.method,
@@ -433,7 +460,9 @@ function getStoredSession(walletId) {
433
460
  return null;
434
461
  }
435
462
  try {
436
- return JSON.parse(value);
463
+ const session = JSON.parse(value);
464
+ new web3Compat.PublicKey(session.publicKey);
465
+ return session;
437
466
  } catch {
438
467
  removeStoredSession(walletId);
439
468
  return null;
@@ -510,6 +539,18 @@ function getStringField(payload, field) {
510
539
  }
511
540
  return value;
512
541
  }
542
+ function getPublicKeyField(payload, field) {
543
+ const value = getStringField(payload, field);
544
+ try {
545
+ new web3Compat.PublicKey(value);
546
+ } catch {
547
+ throw new Error(`iOS wallet callback returned an invalid ${field}`);
548
+ }
549
+ return value;
550
+ }
551
+ function isPendingRequestExpired(request) {
552
+ return Date.now() - request.createdAt > PENDING_REQUEST_TTL_MS;
553
+ }
513
554
  function getIosWalletDefinition(walletInfo) {
514
555
  if (!isIosWalletDefinition(walletInfo.wallet)) {
515
556
  throw new Error("Solana wallet info is not an iOS wallet");
@@ -10,6 +10,7 @@ function resolveTweetNaCl() {
10
10
  }
11
11
  const DEFAULT_IOS_WALLET_CHAINS = ["solana:mainnet", "solana:devnet"];
12
12
  const PENDING_REQUEST_KEY = "vue-solana:ios-wallet:pending";
13
+ const PENDING_REQUEST_TTL_MS = 10 * 60 * 1e3;
13
14
  const SESSION_PREFIX = "vue-solana:ios-wallet:session:";
14
15
  const CALLBACK_PARAMS = /* @__PURE__ */ new Set([
15
16
  "data",
@@ -141,6 +142,11 @@ function adaptSolanaIosWallet(walletInfo, options = {}) {
141
142
  transactions,
142
143
  options
143
144
  );
145
+ if (signedTransactions.length !== transactions.length) {
146
+ throw new Error(
147
+ `iOS wallet returned ${signedTransactions.length} signed transactions for ${transactions.length} requested transactions`
148
+ );
149
+ }
144
150
  return signedTransactions.map(
145
151
  (transaction, index) => deserializeTransaction(transactions[index], transaction)
146
152
  );
@@ -168,36 +174,50 @@ function handleSolanaIosWalletCallback(options = {}) {
168
174
  cleanCallbackUrl(url, options.clearUrl);
169
175
  throw new Error("Received an iOS wallet callback without a pending request");
170
176
  }
177
+ if (isPendingRequestExpired(pending)) {
178
+ clearPendingRequest();
179
+ cleanCallbackUrl(url, options.clearUrl);
180
+ throw new Error("Received an expired iOS wallet callback");
181
+ }
171
182
  const definition = getIosWalletDefinitionById(pending.walletId);
172
183
  const walletEncryptionPublicKey = url.searchParams.get(definition.encryptionPublicKeyParam);
173
184
  const nonce = url.searchParams.get("nonce");
174
185
  const data = url.searchParams.get("data");
175
186
  if (!nonce || !data) {
187
+ clearPendingRequest();
188
+ cleanCallbackUrl(url, options.clearUrl);
176
189
  throw new Error("Received an incomplete iOS wallet callback");
177
190
  }
178
- const sharedSecret = getSharedSecret(
179
- walletEncryptionPublicKey ?? getStoredSession(pending.walletId)?.walletEncryptionPublicKey,
180
- pending.dappEncryptionSecretKey
181
- );
182
- const payload = decryptPayload(data, nonce, sharedSecret);
183
- const result = createCallbackResult(pending, payload);
184
- if (pending.method === "connect") {
185
- if (!walletEncryptionPublicKey) {
186
- throw new Error("iOS wallet connect callback did not include an encryption public key");
191
+ try {
192
+ const sharedSecret = getSharedSecret(
193
+ walletEncryptionPublicKey ?? getStoredSession(pending.walletId)?.walletEncryptionPublicKey,
194
+ pending.dappEncryptionSecretKey
195
+ );
196
+ const payload = decryptPayload(data, nonce, sharedSecret);
197
+ const result = createCallbackResult(pending, payload);
198
+ if (pending.method === "connect") {
199
+ if (!walletEncryptionPublicKey) {
200
+ throw new Error("iOS wallet connect callback did not include an encryption public key");
201
+ }
202
+ const publicKey = getPublicKeyField(payload, "public_key");
203
+ storeSession({
204
+ walletId: pending.walletId,
205
+ publicKey,
206
+ session: getStringField(payload, "session"),
207
+ dappEncryptionPublicKey: pending.dappEncryptionPublicKey,
208
+ dappEncryptionSecretKey: pending.dappEncryptionSecretKey,
209
+ walletEncryptionPublicKey,
210
+ sharedSecret: bs58.encode(sharedSecret)
211
+ });
187
212
  }
188
- storeSession({
189
- walletId: pending.walletId,
190
- publicKey: getStringField(payload, "public_key"),
191
- session: getStringField(payload, "session"),
192
- dappEncryptionPublicKey: pending.dappEncryptionPublicKey,
193
- dappEncryptionSecretKey: pending.dappEncryptionSecretKey,
194
- walletEncryptionPublicKey,
195
- sharedSecret: bs58.encode(sharedSecret)
196
- });
197
- }
198
- clearPendingRequest();
199
- cleanCallbackUrl(url, options.clearUrl);
200
- return result;
213
+ clearPendingRequest();
214
+ cleanCallbackUrl(url, options.clearUrl);
215
+ return result;
216
+ } catch (cause) {
217
+ clearPendingRequest();
218
+ cleanCallbackUrl(url, options.clearUrl);
219
+ throw cause;
220
+ }
201
221
  }
202
222
  function isSolanaIosWalletInfo(walletInfo) {
203
223
  return walletInfo.source === "deep-link" && isIosWalletDefinition(walletInfo.wallet);
@@ -297,7 +317,8 @@ async function launchEncryptedWalletRequest(definition, method, options, payload
297
317
  publicKey: bs58.decode(session.dappEncryptionPublicKey),
298
318
  secretKey: bs58.decode(session.dappEncryptionSecretKey)
299
319
  },
300
- options.redirectUrl
320
+ options.redirectUrl,
321
+ method === "signAllTransactions" && Array.isArray(payload.transactions) ? payload.transactions.length : void 0
301
322
  );
302
323
  const nonce = nacl.randomBytes(nacl.box.nonceLength);
303
324
  const encryptedPayload = encryptPayload(
@@ -314,7 +335,7 @@ async function launchEncryptedWalletRequest(definition, method, options, payload
314
335
  openIosWalletUrl(url.toString());
315
336
  return waitForRedirect();
316
337
  }
317
- function createPendingRequest(walletId, method, keyPair, redirectUrl) {
338
+ function createPendingRequest(walletId, method, keyPair, redirectUrl, requestedTransactionCount) {
318
339
  return {
319
340
  id: createRequestId(),
320
341
  walletId,
@@ -322,7 +343,8 @@ function createPendingRequest(walletId, method, keyPair, redirectUrl) {
322
343
  dappEncryptionPublicKey: bs58.encode(keyPair.publicKey),
323
344
  dappEncryptionSecretKey: bs58.encode(keyPair.secretKey),
324
345
  redirectUrl: redirectUrl ?? getDefaultIosWalletRedirectUrl(),
325
- createdAt: Date.now()
346
+ createdAt: Date.now(),
347
+ requestedTransactionCount
326
348
  };
327
349
  }
328
350
  function createCallbackResult(pending, payload) {
@@ -330,7 +352,7 @@ function createCallbackResult(pending, payload) {
330
352
  return {
331
353
  walletId: pending.walletId,
332
354
  method: pending.method,
333
- publicKey: getStringField(payload, "public_key")
355
+ publicKey: getPublicKeyField(payload, "public_key")
334
356
  };
335
357
  }
336
358
  if (pending.method === "signTransaction") {
@@ -345,6 +367,11 @@ function createCallbackResult(pending, payload) {
345
367
  if (!Array.isArray(transactions) || transactions.some((transaction) => typeof transaction !== "string")) {
346
368
  throw new Error("iOS wallet returned invalid signed transactions");
347
369
  }
370
+ if (typeof pending.requestedTransactionCount === "number" && transactions.length !== pending.requestedTransactionCount) {
371
+ throw new Error(
372
+ `iOS wallet returned ${transactions.length} signed transactions for ${pending.requestedTransactionCount} requested transactions`
373
+ );
374
+ }
348
375
  return {
349
376
  walletId: pending.walletId,
350
377
  method: pending.method,
@@ -414,7 +441,9 @@ function getStoredSession(walletId) {
414
441
  return null;
415
442
  }
416
443
  try {
417
- return JSON.parse(value);
444
+ const session = JSON.parse(value);
445
+ new PublicKey(session.publicKey);
446
+ return session;
418
447
  } catch {
419
448
  removeStoredSession(walletId);
420
449
  return null;
@@ -491,6 +520,18 @@ function getStringField(payload, field) {
491
520
  }
492
521
  return value;
493
522
  }
523
+ function getPublicKeyField(payload, field) {
524
+ const value = getStringField(payload, field);
525
+ try {
526
+ new PublicKey(value);
527
+ } catch {
528
+ throw new Error(`iOS wallet callback returned an invalid ${field}`);
529
+ }
530
+ return value;
531
+ }
532
+ function isPendingRequestExpired(request) {
533
+ return Date.now() - request.createdAt > PENDING_REQUEST_TTL_MS;
534
+ }
494
535
  function getIosWalletDefinition(walletInfo) {
495
536
  if (!isIosWalletDefinition(walletInfo.wallet)) {
496
537
  throw new Error("Solana wallet info is not an iOS wallet");
@@ -136,9 +136,17 @@ function adaptSolanaStandardWallet(walletInfo, options = {}) {
136
136
  chain: options.chain
137
137
  }))
138
138
  );
139
- return results.map(
140
- (result, index) => deserializeTransaction(transactions[index], result.signedTransaction)
141
- );
139
+ if (results.length !== transactions.length) {
140
+ throw new Error(
141
+ `Solana wallet returned ${results.length} signed transactions for ${transactions.length} requested transactions`
142
+ );
143
+ }
144
+ return results.map((result, index) => {
145
+ if (!result) {
146
+ throw new Error("Solana wallet did not return a signed transaction");
147
+ }
148
+ return deserializeTransaction(transactions[index], result.signedTransaction);
149
+ });
142
150
  } : void 0,
143
151
  signAndSendTransaction: hasSignAndSendTransaction(wallet) ? async (transaction, sendOptions) => {
144
152
  const activeAccount = getActiveAccount(account);
@@ -130,9 +130,17 @@ function adaptSolanaStandardWallet(walletInfo, options = {}) {
130
130
  chain: options.chain
131
131
  }))
132
132
  );
133
- return results.map(
134
- (result, index) => deserializeTransaction(transactions[index], result.signedTransaction)
135
- );
133
+ if (results.length !== transactions.length) {
134
+ throw new Error(
135
+ `Solana wallet returned ${results.length} signed transactions for ${transactions.length} requested transactions`
136
+ );
137
+ }
138
+ return results.map((result, index) => {
139
+ if (!result) {
140
+ throw new Error("Solana wallet did not return a signed transaction");
141
+ }
142
+ return deserializeTransaction(transactions[index], result.signedTransaction);
143
+ });
136
144
  } : void 0,
137
145
  signAndSendTransaction: hasSignAndSendTransaction(wallet) ? async (transaction, sendOptions) => {
138
146
  const activeAccount = getActiveAccount(account);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vue-solana/core",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Framework-agnostic primitives for Vue Solana libraries.",
5
5
  "license": "MIT",
6
6
  "keywords": [