@dynamic-labs-wallet/browser-wallet-client 1.1.40 → 1.1.41

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/index.cjs CHANGED
@@ -2339,31 +2339,70 @@ const limitKeys = [
2339
2339
  'maxPriorityFeePerGas',
2340
2340
  'maxTotalFee'
2341
2341
  ];
2342
+ const isHex = (value)=>typeof value === 'string' && /^0x(?:[0-9a-f]{2})*$/.test(value);
2343
+ const isSvmTransactionIntent = (intent)=>intent.operation.kind === 'transaction' && intent.operation.chainName === 'SVM';
2344
+ const isEvmTransactionIntent = (intent)=>intent.operation.kind === 'transaction' && intent.operation.chainName === 'EVM';
2342
2345
  const assertMatches = (matches, message)=>{
2343
2346
  if (!matches) throw new Error(message);
2344
2347
  };
2345
- const assertPreparedSigningIntentMatchesRequest = ({ intent, request, preparedPayload })=>{
2348
+ const hasLifetime = (lifetime, refreshable)=>{
2349
+ if (refreshable === undefined) return lifetime === undefined;
2350
+ const keys = lifetime && typeof lifetime === 'object' ? Object.keys(lifetime) : [];
2351
+ return keys.length === 1 && lifetime.refreshable === refreshable;
2352
+ };
2353
+ const hasMatchingMetadata = (intent, request)=>{
2346
2354
  const sameAddress = request.chainName === 'EVM' ? intent.accountAddress.toLowerCase() === request.accountAddress.toLowerCase() : intent.accountAddress === request.accountAddress;
2347
- assertMatches(Object.values(core.WaasChainEnum).includes(request.chainName) && intent.actionType === 'waasSigningQuorumApprove' && Boolean(intent.intentId) && Number.isFinite(Date.parse(intent.createdAt)) && sameAddress && intent.businessAccountId === request.businessAccountId && intent.initiatorUserId === request.initiatorUserId && intent.completeBy === request.completeBy && intent.operation.chainName === request.chainName && intent.operation.kind === request.kind, 'Prepared signing intent does not match request');
2355
+ return Object.values(core.WaasChainEnum).includes(request.chainName) && intent.actionType === 'waasSigningQuorumApprove' && Boolean(intent.intentId) && Number.isFinite(Date.parse(intent.createdAt)) && sameAddress && intent.businessAccountId === request.businessAccountId && intent.initiatorUserId === request.initiatorUserId && intent.completeBy === request.completeBy && intent.operation.chainName === request.chainName && intent.operation.kind === request.kind;
2356
+ };
2357
+ const hasValidSvmHeader = (intent)=>{
2358
+ const { header, staticAccountKeys } = intent.operation;
2359
+ return Number.isInteger(header.numRequiredSignatures) && header.numRequiredSignatures >= 1 && header.numRequiredSignatures <= staticAccountKeys.length && Number.isInteger(header.numReadonlySignedAccounts) && header.numReadonlySignedAccounts >= 0 && header.numReadonlySignedAccounts < header.numRequiredSignatures && Number.isInteger(header.numReadonlyUnsignedAccounts) && header.numReadonlyUnsignedAccounts >= 0 && header.numReadonlyUnsignedAccounts <= staticAccountKeys.length - header.numRequiredSignatures;
2360
+ };
2361
+ const hasValidSvmInstructions = (intent)=>{
2362
+ const { instructions, staticAccountKeys } = intent.operation;
2363
+ const isIndex = (value)=>Number.isInteger(value) && value >= 0 && value < staticAccountKeys.length;
2364
+ return instructions.every((instruction)=>isIndex(instruction.programIdIndex) && instruction.accountKeyIndexes.every(isIndex) && isHex(instruction.data));
2365
+ };
2366
+ const assertSvmTransactionMatchesRequest = (intent, request)=>{
2367
+ var _request_chainId;
2368
+ const { operation } = intent;
2369
+ const isBase58 = (value)=>/^[1-9A-HJ-NP-Za-km-z]+$/.test(value);
2370
+ assertMatches(request.chainName === 'SVM' && Boolean(request.chainId) && ((_request_chainId = request.chainId) == null ? void 0 : _request_chainId.trim()) === request.chainId && operation.chainId === request.chainId && request.refreshLimits === undefined && !request.refreshableNonce && intent.limits === undefined && hasLifetime(intent.lifetime, 'blockhash') && (operation.messageVersion === 'legacy' || operation.messageVersion === 'v0') && operation.staticAccountKeys.length > 0 && operation.staticAccountKeys.every(isBase58) && operation.feePayer === request.accountAddress && operation.feePayer === operation.staticAccountKeys[0] && hasValidSvmHeader(intent) && hasValidSvmInstructions(intent), 'Prepared SVM transaction signing intent is malformed');
2371
+ };
2372
+ const assertEvmTransactionMatchesRequest = (intent, request)=>{
2373
+ if (request.chainName !== 'EVM') throw new Error('Unsupported transaction signing intent');
2374
+ const { operation, limits } = intent;
2375
+ if (!limits) throw new Error('Prepared transaction signing intent is malformed');
2376
+ assertMatches(hasLifetime(intent.lifetime, request.refreshableNonce ? 'nonce' : undefined), 'Transaction lifetime does not match request');
2377
+ assertMatches(operation.from === intent.accountAddress.toLowerCase(), 'Transaction signer does not match request');
2378
+ assertMatches(!request.chainId || operation.chainId === request.chainId, 'Transaction chain does not match request');
2379
+ assertMatches(!request.refreshLimits || limitKeys.every((key)=>{
2380
+ var _request_refreshLimits;
2381
+ return limits[key] === ((_request_refreshLimits = request.refreshLimits) == null ? void 0 : _request_refreshLimits[key]);
2382
+ }), 'Transaction signing limits do not match request');
2383
+ // Wallet-service parses the transaction it signs and rejects any mismatch with
2384
+ // the approved operation, so the transaction fields are not re-derived here.
2385
+ };
2386
+ const assertPreparedSigningIntentMatchesRequest = ({ intent, request, preparedPayload })=>{
2387
+ assertMatches(hasMatchingMetadata(intent, request), 'Prepared signing intent does not match request');
2348
2388
  if (intent.operation.kind === 'transaction') {
2349
- if (request.kind !== 'transaction' || request.chainName !== 'EVM' || intent.operation.chainName !== 'EVM') {
2389
+ if (request.kind !== 'transaction') {
2390
+ throw new Error('Unsupported transaction signing intent');
2391
+ }
2392
+ if (isSvmTransactionIntent(intent)) {
2393
+ assertSvmTransactionMatchesRequest(intent, request);
2394
+ return;
2395
+ }
2396
+ if (!isEvmTransactionIntent(intent)) {
2350
2397
  throw new Error('Unsupported transaction signing intent');
2351
2398
  }
2352
- const { operation, limits } = intent;
2353
- if (!limits) throw new Error('Prepared transaction signing intent is malformed');
2354
- assertMatches(operation.from === intent.accountAddress.toLowerCase(), 'Transaction signer does not match request');
2355
- assertMatches(!request.chainId || operation.chainId === request.chainId, 'Transaction chain does not match request');
2356
- assertMatches(!request.refreshLimits || limitKeys.every((key)=>{
2357
- var _request_refreshLimits;
2358
- return limits[key] === ((_request_refreshLimits = request.refreshLimits) == null ? void 0 : _request_refreshLimits[key]);
2359
- }), 'Transaction signing limits do not match request');
2360
- // Wallet-service parses the transaction it signs and rejects any mismatch with
2361
- // the approved operation, so the transaction fields are not re-derived here.
2399
+ assertEvmTransactionMatchesRequest(intent, request);
2362
2400
  return;
2363
2401
  }
2364
2402
  const { operation } = intent;
2365
2403
  assertMatches(preparedPayload !== undefined && operation.payload === preparedPayload, 'Prepared signing intent payload does not match request');
2366
2404
  assertMatches(intent.limits === undefined, 'Exact signing intent cannot include refresh limits');
2405
+ assertMatches(hasLifetime(intent.lifetime, undefined), 'Exact signing intent cannot include a lifetime');
2367
2406
  if (operation.kind === 'message') {
2368
2407
  assertMatches(request.kind === 'message' && request.chainName !== 'TRON' && operation.message === request.message, 'Prepared signing intent message does not match request');
2369
2408
  return;
package/index.esm.js CHANGED
@@ -2338,31 +2338,70 @@ const limitKeys = [
2338
2338
  'maxPriorityFeePerGas',
2339
2339
  'maxTotalFee'
2340
2340
  ];
2341
+ const isHex = (value)=>typeof value === 'string' && /^0x(?:[0-9a-f]{2})*$/.test(value);
2342
+ const isSvmTransactionIntent = (intent)=>intent.operation.kind === 'transaction' && intent.operation.chainName === 'SVM';
2343
+ const isEvmTransactionIntent = (intent)=>intent.operation.kind === 'transaction' && intent.operation.chainName === 'EVM';
2341
2344
  const assertMatches = (matches, message)=>{
2342
2345
  if (!matches) throw new Error(message);
2343
2346
  };
2344
- const assertPreparedSigningIntentMatchesRequest = ({ intent, request, preparedPayload })=>{
2347
+ const hasLifetime = (lifetime, refreshable)=>{
2348
+ if (refreshable === undefined) return lifetime === undefined;
2349
+ const keys = lifetime && typeof lifetime === 'object' ? Object.keys(lifetime) : [];
2350
+ return keys.length === 1 && lifetime.refreshable === refreshable;
2351
+ };
2352
+ const hasMatchingMetadata = (intent, request)=>{
2345
2353
  const sameAddress = request.chainName === 'EVM' ? intent.accountAddress.toLowerCase() === request.accountAddress.toLowerCase() : intent.accountAddress === request.accountAddress;
2346
- assertMatches(Object.values(WaasChainEnum).includes(request.chainName) && intent.actionType === 'waasSigningQuorumApprove' && Boolean(intent.intentId) && Number.isFinite(Date.parse(intent.createdAt)) && sameAddress && intent.businessAccountId === request.businessAccountId && intent.initiatorUserId === request.initiatorUserId && intent.completeBy === request.completeBy && intent.operation.chainName === request.chainName && intent.operation.kind === request.kind, 'Prepared signing intent does not match request');
2354
+ return Object.values(WaasChainEnum).includes(request.chainName) && intent.actionType === 'waasSigningQuorumApprove' && Boolean(intent.intentId) && Number.isFinite(Date.parse(intent.createdAt)) && sameAddress && intent.businessAccountId === request.businessAccountId && intent.initiatorUserId === request.initiatorUserId && intent.completeBy === request.completeBy && intent.operation.chainName === request.chainName && intent.operation.kind === request.kind;
2355
+ };
2356
+ const hasValidSvmHeader = (intent)=>{
2357
+ const { header, staticAccountKeys } = intent.operation;
2358
+ return Number.isInteger(header.numRequiredSignatures) && header.numRequiredSignatures >= 1 && header.numRequiredSignatures <= staticAccountKeys.length && Number.isInteger(header.numReadonlySignedAccounts) && header.numReadonlySignedAccounts >= 0 && header.numReadonlySignedAccounts < header.numRequiredSignatures && Number.isInteger(header.numReadonlyUnsignedAccounts) && header.numReadonlyUnsignedAccounts >= 0 && header.numReadonlyUnsignedAccounts <= staticAccountKeys.length - header.numRequiredSignatures;
2359
+ };
2360
+ const hasValidSvmInstructions = (intent)=>{
2361
+ const { instructions, staticAccountKeys } = intent.operation;
2362
+ const isIndex = (value)=>Number.isInteger(value) && value >= 0 && value < staticAccountKeys.length;
2363
+ return instructions.every((instruction)=>isIndex(instruction.programIdIndex) && instruction.accountKeyIndexes.every(isIndex) && isHex(instruction.data));
2364
+ };
2365
+ const assertSvmTransactionMatchesRequest = (intent, request)=>{
2366
+ var _request_chainId;
2367
+ const { operation } = intent;
2368
+ const isBase58 = (value)=>/^[1-9A-HJ-NP-Za-km-z]+$/.test(value);
2369
+ assertMatches(request.chainName === 'SVM' && Boolean(request.chainId) && ((_request_chainId = request.chainId) == null ? void 0 : _request_chainId.trim()) === request.chainId && operation.chainId === request.chainId && request.refreshLimits === undefined && !request.refreshableNonce && intent.limits === undefined && hasLifetime(intent.lifetime, 'blockhash') && (operation.messageVersion === 'legacy' || operation.messageVersion === 'v0') && operation.staticAccountKeys.length > 0 && operation.staticAccountKeys.every(isBase58) && operation.feePayer === request.accountAddress && operation.feePayer === operation.staticAccountKeys[0] && hasValidSvmHeader(intent) && hasValidSvmInstructions(intent), 'Prepared SVM transaction signing intent is malformed');
2370
+ };
2371
+ const assertEvmTransactionMatchesRequest = (intent, request)=>{
2372
+ if (request.chainName !== 'EVM') throw new Error('Unsupported transaction signing intent');
2373
+ const { operation, limits } = intent;
2374
+ if (!limits) throw new Error('Prepared transaction signing intent is malformed');
2375
+ assertMatches(hasLifetime(intent.lifetime, request.refreshableNonce ? 'nonce' : undefined), 'Transaction lifetime does not match request');
2376
+ assertMatches(operation.from === intent.accountAddress.toLowerCase(), 'Transaction signer does not match request');
2377
+ assertMatches(!request.chainId || operation.chainId === request.chainId, 'Transaction chain does not match request');
2378
+ assertMatches(!request.refreshLimits || limitKeys.every((key)=>{
2379
+ var _request_refreshLimits;
2380
+ return limits[key] === ((_request_refreshLimits = request.refreshLimits) == null ? void 0 : _request_refreshLimits[key]);
2381
+ }), 'Transaction signing limits do not match request');
2382
+ // Wallet-service parses the transaction it signs and rejects any mismatch with
2383
+ // the approved operation, so the transaction fields are not re-derived here.
2384
+ };
2385
+ const assertPreparedSigningIntentMatchesRequest = ({ intent, request, preparedPayload })=>{
2386
+ assertMatches(hasMatchingMetadata(intent, request), 'Prepared signing intent does not match request');
2347
2387
  if (intent.operation.kind === 'transaction') {
2348
- if (request.kind !== 'transaction' || request.chainName !== 'EVM' || intent.operation.chainName !== 'EVM') {
2388
+ if (request.kind !== 'transaction') {
2389
+ throw new Error('Unsupported transaction signing intent');
2390
+ }
2391
+ if (isSvmTransactionIntent(intent)) {
2392
+ assertSvmTransactionMatchesRequest(intent, request);
2393
+ return;
2394
+ }
2395
+ if (!isEvmTransactionIntent(intent)) {
2349
2396
  throw new Error('Unsupported transaction signing intent');
2350
2397
  }
2351
- const { operation, limits } = intent;
2352
- if (!limits) throw new Error('Prepared transaction signing intent is malformed');
2353
- assertMatches(operation.from === intent.accountAddress.toLowerCase(), 'Transaction signer does not match request');
2354
- assertMatches(!request.chainId || operation.chainId === request.chainId, 'Transaction chain does not match request');
2355
- assertMatches(!request.refreshLimits || limitKeys.every((key)=>{
2356
- var _request_refreshLimits;
2357
- return limits[key] === ((_request_refreshLimits = request.refreshLimits) == null ? void 0 : _request_refreshLimits[key]);
2358
- }), 'Transaction signing limits do not match request');
2359
- // Wallet-service parses the transaction it signs and rejects any mismatch with
2360
- // the approved operation, so the transaction fields are not re-derived here.
2398
+ assertEvmTransactionMatchesRequest(intent, request);
2361
2399
  return;
2362
2400
  }
2363
2401
  const { operation } = intent;
2364
2402
  assertMatches(preparedPayload !== undefined && operation.payload === preparedPayload, 'Prepared signing intent payload does not match request');
2365
2403
  assertMatches(intent.limits === undefined, 'Exact signing intent cannot include refresh limits');
2404
+ assertMatches(hasLifetime(intent.lifetime, undefined), 'Exact signing intent cannot include a lifetime');
2366
2405
  if (operation.kind === 'message') {
2367
2406
  assertMatches(request.kind === 'message' && request.chainName !== 'TRON' && operation.message === request.message, 'Prepared signing intent message does not match request');
2368
2407
  return;
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@dynamic-labs-wallet/browser-wallet-client",
3
- "version": "1.1.40",
3
+ "version": "1.1.41",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "dependencies": {
7
- "@dynamic-labs-wallet/core": "1.1.40",
7
+ "@dynamic-labs-wallet/core": "1.1.41",
8
8
  "@dynamic-labs/logger": "^5.9.4",
9
9
  "@dynamic-labs/message-transport": "^5.9.1"
10
10
  },
@@ -1 +1 @@
1
- {"version":3,"file":"assertPreparedSigningIntentMatchesRequest.d.ts","sourceRoot":"","sources":["../../packages/src/assertPreparedSigningIntentMatchesRequest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,2BAA2B,EAAE,KAAK,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAQhH,eAAO,MAAM,yCAAyC,0CAInD;IACD,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,2BAA2B,CAAC;IACrC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,KAAG,IAiFH,CAAC"}
1
+ {"version":3,"file":"assertPreparedSigningIntentMatchesRequest.d.ts","sourceRoot":"","sources":["../../packages/src/assertPreparedSigningIntentMatchesRequest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,2BAA2B,EAAE,KAAK,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAiHhH,eAAO,MAAM,yCAAyC,0CAInD;IACD,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,2BAA2B,CAAC;IACrC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,KAAG,IA6DH,CAAC"}