@dynamic-labs-wallet/svm 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
@@ -423,4 +423,173 @@ class DynamicSvmWalletClient extends browser.DynamicWalletClient {
423
423
  }
424
424
  }
425
425
 
426
+ const ADVANCE_NONCE_ACCOUNT_INSTRUCTION = 4;
427
+ const isHexBytes = (value)=>/^(?:[0-9a-fA-F]{2})+$/.test(value);
428
+ const assertNetworkChainId = (chainId)=>{
429
+ if (!(chainId == null ? void 0 : chainId.trim()) || chainId.trim() !== chainId) {
430
+ throw new Error('SVM signing intent requires a network chainId');
431
+ }
432
+ return chainId;
433
+ };
434
+ const parseUnsignedMessage = (transaction)=>{
435
+ if (!isHexBytes(transaction)) {
436
+ throw new Error('SVM signing intent requires a hex serialized message');
437
+ }
438
+ const message = web3_js.VersionedMessage.deserialize(Uint8Array.from(Buffer.from(transaction, 'hex')));
439
+ // Compare as hex: the browser Buffer polyfill's equals() rejects a plain Uint8Array.
440
+ if (Buffer.from(message.serialize()).toString('hex') !== transaction.toLowerCase()) {
441
+ throw new Error('SVM signing intent requires one complete unsigned message');
442
+ }
443
+ return message;
444
+ };
445
+ const assertSupportedMessageShape = (message)=>{
446
+ if (message.version !== 'legacy' && message.version !== 0) {
447
+ throw new Error('Unsupported SVM message version');
448
+ }
449
+ if (message.addressTableLookups.length !== 0) {
450
+ throw new Error('SVM signing intent does not support address-table lookups');
451
+ }
452
+ };
453
+ const resolveFeePayer = (message, accountAddress)=>{
454
+ var _message_staticAccountKeys_;
455
+ const feePayer = (_message_staticAccountKeys_ = message.staticAccountKeys[0]) == null ? void 0 : _message_staticAccountKeys_.toBase58();
456
+ if (!feePayer || feePayer !== accountAddress || message.header.numRequiredSignatures < 1) {
457
+ throw new Error('SVM fee payer must be the signing account');
458
+ }
459
+ return feePayer;
460
+ };
461
+ const isAdvanceNonceInstruction = (message, instruction)=>{
462
+ const program = message.staticAccountKeys[instruction.programIdIndex];
463
+ const data = Buffer.from(instruction.data);
464
+ return Boolean(program == null ? void 0 : program.equals(web3_js.SystemProgram.programId)) && data.length >= 4 && data.readUInt32LE(0) === ADVANCE_NONCE_ACCOUNT_INSTRUCTION;
465
+ };
466
+ const assertNoDurableNonce = (message)=>{
467
+ if (message.compiledInstructions.some((instruction)=>isAdvanceNonceInstruction(message, instruction))) {
468
+ throw new Error('SVM signing intent does not support durable nonce transactions');
469
+ }
470
+ };
471
+ const messageVersionOf = (message)=>{
472
+ if (message.version === 'legacy') return 'legacy';
473
+ return 'v0';
474
+ };
475
+ const toTransactionOperation = (message, chainId, feePayer)=>({
476
+ kind: 'transaction',
477
+ chainName: 'SVM',
478
+ chainId,
479
+ messageVersion: messageVersionOf(message),
480
+ feePayer,
481
+ header: {
482
+ numRequiredSignatures: message.header.numRequiredSignatures,
483
+ numReadonlySignedAccounts: message.header.numReadonlySignedAccounts,
484
+ numReadonlyUnsignedAccounts: message.header.numReadonlyUnsignedAccounts
485
+ },
486
+ staticAccountKeys: message.staticAccountKeys.map((key)=>key.toBase58()),
487
+ instructions: message.compiledInstructions.map((instruction)=>({
488
+ programIdIndex: instruction.programIdIndex,
489
+ accountKeyIndexes: [
490
+ ...instruction.accountKeyIndexes
491
+ ],
492
+ data: `0x${Buffer.from(instruction.data).toString('hex')}`
493
+ }))
494
+ });
495
+ const prepareSvmTransactionSigningOperation = ({ transaction, accountAddress, chainId })=>{
496
+ const networkChainId = assertNetworkChainId(chainId);
497
+ const message = parseUnsignedMessage(transaction);
498
+ assertSupportedMessageShape(message);
499
+ const feePayer = resolveFeePayer(message, accountAddress);
500
+ assertNoDurableNonce(message);
501
+ return {
502
+ operation: toTransactionOperation(message, networkChainId, feePayer),
503
+ lifetime: {
504
+ refreshable: 'blockhash'
505
+ }
506
+ };
507
+ };
508
+ const BLOCKHASH_BYTES = 32;
509
+ const signedSvmOperation = (intent)=>{
510
+ var _intent_lifetime;
511
+ const { operation } = intent;
512
+ if (operation.kind !== 'transaction' || operation.chainName !== 'SVM' || ((_intent_lifetime = intent.lifetime) == null ? void 0 : _intent_lifetime.refreshable) !== 'blockhash') {
513
+ throw new Error('Stored signing intent is not an SVM transaction');
514
+ }
515
+ return operation;
516
+ };
517
+ const assertBlockhash = (blockhash)=>{
518
+ if (!blockhash || new web3_js.PublicKey(blockhash).toBytes().length !== BLOCKHASH_BYTES) {
519
+ throw new Error('SVM transaction intent requires a blockhash');
520
+ }
521
+ return blockhash;
522
+ };
523
+ const instructionData = (data)=>Uint8Array.from(Buffer.from(data.slice(2), 'hex'));
524
+ const buildLegacyMessage = (operation, recentBlockhash)=>new web3_js.Message({
525
+ header: operation.header,
526
+ accountKeys: operation.staticAccountKeys.map((key)=>new web3_js.PublicKey(key)),
527
+ recentBlockhash,
528
+ instructions: operation.instructions.map((instruction)=>({
529
+ programIdIndex: instruction.programIdIndex,
530
+ accounts: [
531
+ ...instruction.accountKeyIndexes
532
+ ],
533
+ data: bs58.encode(instructionData(instruction.data))
534
+ }))
535
+ });
536
+ const buildV0Message = (operation, recentBlockhash)=>new web3_js.MessageV0({
537
+ header: operation.header,
538
+ staticAccountKeys: operation.staticAccountKeys.map((key)=>new web3_js.PublicKey(key)),
539
+ recentBlockhash,
540
+ compiledInstructions: operation.instructions.map((instruction)=>({
541
+ programIdIndex: instruction.programIdIndex,
542
+ accountKeyIndexes: [
543
+ ...instruction.accountKeyIndexes
544
+ ],
545
+ data: instructionData(instruction.data)
546
+ })),
547
+ addressTableLookups: []
548
+ });
549
+ const buildMessage = (operation, recentBlockhash)=>{
550
+ if (operation.messageVersion === 'legacy') return buildLegacyMessage(operation, recentBlockhash);
551
+ return buildV0Message(operation, recentBlockhash);
552
+ };
553
+ /** Orders the signed operation's fields as `prepareSvmTransactionSigningOperation` does, for comparison. */ const canonicalSvmOperation = (operation)=>({
554
+ kind: operation.kind,
555
+ chainName: operation.chainName,
556
+ chainId: operation.chainId,
557
+ messageVersion: operation.messageVersion,
558
+ feePayer: operation.feePayer,
559
+ header: {
560
+ numRequiredSignatures: operation.header.numRequiredSignatures,
561
+ numReadonlySignedAccounts: operation.header.numReadonlySignedAccounts,
562
+ numReadonlyUnsignedAccounts: operation.header.numReadonlyUnsignedAccounts
563
+ },
564
+ staticAccountKeys: operation.staticAccountKeys,
565
+ instructions: operation.instructions.map((instruction)=>({
566
+ programIdIndex: instruction.programIdIndex,
567
+ accountKeyIndexes: instruction.accountKeyIndexes,
568
+ data: instruction.data.toLowerCase()
569
+ }))
570
+ });
571
+ const assertRebuiltMatchesOperation = (transaction, accountAddress, operation)=>{
572
+ const rebuilt = prepareSvmTransactionSigningOperation({
573
+ transaction,
574
+ accountAddress,
575
+ chainId: operation.chainId
576
+ }).operation;
577
+ if (JSON.stringify(rebuilt) !== JSON.stringify(canonicalSvmOperation(operation))) {
578
+ throw new Error('Stored SVM transaction intent is malformed');
579
+ }
580
+ };
581
+ /**
582
+ * Reconstructs the unsigned serialized message (hex) from an approved intent.
583
+ * The signed intent carries no blockhash, so the caller supplies one. Passing the
584
+ * blockhash of the original message is fine while it is still valid.
585
+ */ const resumeSvmTransactionSigningIntent = ({ intent, lifetimeValue })=>{
586
+ const operation = signedSvmOperation(intent);
587
+ const recentBlockhash = assertBlockhash(lifetimeValue);
588
+ const transaction = Buffer.from(buildMessage(operation, recentBlockhash).serialize()).toString('hex');
589
+ assertRebuiltMatchesOperation(transaction, intent.accountAddress, operation);
590
+ return transaction;
591
+ };
592
+
426
593
  exports.DynamicSvmWalletClient = DynamicSvmWalletClient;
594
+ exports.prepareSvmTransactionSigningOperation = prepareSvmTransactionSigningOperation;
595
+ exports.resumeSvmTransactionSigningIntent = resumeSvmTransactionSigningIntent;
package/index.esm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DynamicWalletClient, getMPCChainConfig, ERROR_KEYGEN_FAILED, ERROR_PASSWORD_MISMATCH, ERROR_MULTIPLE_WALLETS_PER_CHAIN, ERROR_CREATE_WALLET_ACCOUNT, ERROR_ACCOUNT_ADDRESS_REQUIRED, ERROR_SIGN_MESSAGE, getEd25519PublicKeyFromRawScalar, ERROR_IMPORT_PRIVATE_KEY } from '@dynamic-labs-wallet/browser';
2
- import { VersionedMessage, VersionedTransaction, Keypair } from '@solana/web3.js';
2
+ import { VersionedMessage, VersionedTransaction, Keypair, PublicKey, SystemProgram, Message, MessageV0 } from '@solana/web3.js';
3
3
  import bs58 from 'bs58';
4
4
 
5
5
  function _extends() {
@@ -421,4 +421,171 @@ class DynamicSvmWalletClient extends DynamicWalletClient {
421
421
  }
422
422
  }
423
423
 
424
- export { DynamicSvmWalletClient };
424
+ const ADVANCE_NONCE_ACCOUNT_INSTRUCTION = 4;
425
+ const isHexBytes = (value)=>/^(?:[0-9a-fA-F]{2})+$/.test(value);
426
+ const assertNetworkChainId = (chainId)=>{
427
+ if (!(chainId == null ? void 0 : chainId.trim()) || chainId.trim() !== chainId) {
428
+ throw new Error('SVM signing intent requires a network chainId');
429
+ }
430
+ return chainId;
431
+ };
432
+ const parseUnsignedMessage = (transaction)=>{
433
+ if (!isHexBytes(transaction)) {
434
+ throw new Error('SVM signing intent requires a hex serialized message');
435
+ }
436
+ const message = VersionedMessage.deserialize(Uint8Array.from(Buffer.from(transaction, 'hex')));
437
+ // Compare as hex: the browser Buffer polyfill's equals() rejects a plain Uint8Array.
438
+ if (Buffer.from(message.serialize()).toString('hex') !== transaction.toLowerCase()) {
439
+ throw new Error('SVM signing intent requires one complete unsigned message');
440
+ }
441
+ return message;
442
+ };
443
+ const assertSupportedMessageShape = (message)=>{
444
+ if (message.version !== 'legacy' && message.version !== 0) {
445
+ throw new Error('Unsupported SVM message version');
446
+ }
447
+ if (message.addressTableLookups.length !== 0) {
448
+ throw new Error('SVM signing intent does not support address-table lookups');
449
+ }
450
+ };
451
+ const resolveFeePayer = (message, accountAddress)=>{
452
+ var _message_staticAccountKeys_;
453
+ const feePayer = (_message_staticAccountKeys_ = message.staticAccountKeys[0]) == null ? void 0 : _message_staticAccountKeys_.toBase58();
454
+ if (!feePayer || feePayer !== accountAddress || message.header.numRequiredSignatures < 1) {
455
+ throw new Error('SVM fee payer must be the signing account');
456
+ }
457
+ return feePayer;
458
+ };
459
+ const isAdvanceNonceInstruction = (message, instruction)=>{
460
+ const program = message.staticAccountKeys[instruction.programIdIndex];
461
+ const data = Buffer.from(instruction.data);
462
+ return Boolean(program == null ? void 0 : program.equals(SystemProgram.programId)) && data.length >= 4 && data.readUInt32LE(0) === ADVANCE_NONCE_ACCOUNT_INSTRUCTION;
463
+ };
464
+ const assertNoDurableNonce = (message)=>{
465
+ if (message.compiledInstructions.some((instruction)=>isAdvanceNonceInstruction(message, instruction))) {
466
+ throw new Error('SVM signing intent does not support durable nonce transactions');
467
+ }
468
+ };
469
+ const messageVersionOf = (message)=>{
470
+ if (message.version === 'legacy') return 'legacy';
471
+ return 'v0';
472
+ };
473
+ const toTransactionOperation = (message, chainId, feePayer)=>({
474
+ kind: 'transaction',
475
+ chainName: 'SVM',
476
+ chainId,
477
+ messageVersion: messageVersionOf(message),
478
+ feePayer,
479
+ header: {
480
+ numRequiredSignatures: message.header.numRequiredSignatures,
481
+ numReadonlySignedAccounts: message.header.numReadonlySignedAccounts,
482
+ numReadonlyUnsignedAccounts: message.header.numReadonlyUnsignedAccounts
483
+ },
484
+ staticAccountKeys: message.staticAccountKeys.map((key)=>key.toBase58()),
485
+ instructions: message.compiledInstructions.map((instruction)=>({
486
+ programIdIndex: instruction.programIdIndex,
487
+ accountKeyIndexes: [
488
+ ...instruction.accountKeyIndexes
489
+ ],
490
+ data: `0x${Buffer.from(instruction.data).toString('hex')}`
491
+ }))
492
+ });
493
+ const prepareSvmTransactionSigningOperation = ({ transaction, accountAddress, chainId })=>{
494
+ const networkChainId = assertNetworkChainId(chainId);
495
+ const message = parseUnsignedMessage(transaction);
496
+ assertSupportedMessageShape(message);
497
+ const feePayer = resolveFeePayer(message, accountAddress);
498
+ assertNoDurableNonce(message);
499
+ return {
500
+ operation: toTransactionOperation(message, networkChainId, feePayer),
501
+ lifetime: {
502
+ refreshable: 'blockhash'
503
+ }
504
+ };
505
+ };
506
+ const BLOCKHASH_BYTES = 32;
507
+ const signedSvmOperation = (intent)=>{
508
+ var _intent_lifetime;
509
+ const { operation } = intent;
510
+ if (operation.kind !== 'transaction' || operation.chainName !== 'SVM' || ((_intent_lifetime = intent.lifetime) == null ? void 0 : _intent_lifetime.refreshable) !== 'blockhash') {
511
+ throw new Error('Stored signing intent is not an SVM transaction');
512
+ }
513
+ return operation;
514
+ };
515
+ const assertBlockhash = (blockhash)=>{
516
+ if (!blockhash || new PublicKey(blockhash).toBytes().length !== BLOCKHASH_BYTES) {
517
+ throw new Error('SVM transaction intent requires a blockhash');
518
+ }
519
+ return blockhash;
520
+ };
521
+ const instructionData = (data)=>Uint8Array.from(Buffer.from(data.slice(2), 'hex'));
522
+ const buildLegacyMessage = (operation, recentBlockhash)=>new Message({
523
+ header: operation.header,
524
+ accountKeys: operation.staticAccountKeys.map((key)=>new PublicKey(key)),
525
+ recentBlockhash,
526
+ instructions: operation.instructions.map((instruction)=>({
527
+ programIdIndex: instruction.programIdIndex,
528
+ accounts: [
529
+ ...instruction.accountKeyIndexes
530
+ ],
531
+ data: bs58.encode(instructionData(instruction.data))
532
+ }))
533
+ });
534
+ const buildV0Message = (operation, recentBlockhash)=>new MessageV0({
535
+ header: operation.header,
536
+ staticAccountKeys: operation.staticAccountKeys.map((key)=>new PublicKey(key)),
537
+ recentBlockhash,
538
+ compiledInstructions: operation.instructions.map((instruction)=>({
539
+ programIdIndex: instruction.programIdIndex,
540
+ accountKeyIndexes: [
541
+ ...instruction.accountKeyIndexes
542
+ ],
543
+ data: instructionData(instruction.data)
544
+ })),
545
+ addressTableLookups: []
546
+ });
547
+ const buildMessage = (operation, recentBlockhash)=>{
548
+ if (operation.messageVersion === 'legacy') return buildLegacyMessage(operation, recentBlockhash);
549
+ return buildV0Message(operation, recentBlockhash);
550
+ };
551
+ /** Orders the signed operation's fields as `prepareSvmTransactionSigningOperation` does, for comparison. */ const canonicalSvmOperation = (operation)=>({
552
+ kind: operation.kind,
553
+ chainName: operation.chainName,
554
+ chainId: operation.chainId,
555
+ messageVersion: operation.messageVersion,
556
+ feePayer: operation.feePayer,
557
+ header: {
558
+ numRequiredSignatures: operation.header.numRequiredSignatures,
559
+ numReadonlySignedAccounts: operation.header.numReadonlySignedAccounts,
560
+ numReadonlyUnsignedAccounts: operation.header.numReadonlyUnsignedAccounts
561
+ },
562
+ staticAccountKeys: operation.staticAccountKeys,
563
+ instructions: operation.instructions.map((instruction)=>({
564
+ programIdIndex: instruction.programIdIndex,
565
+ accountKeyIndexes: instruction.accountKeyIndexes,
566
+ data: instruction.data.toLowerCase()
567
+ }))
568
+ });
569
+ const assertRebuiltMatchesOperation = (transaction, accountAddress, operation)=>{
570
+ const rebuilt = prepareSvmTransactionSigningOperation({
571
+ transaction,
572
+ accountAddress,
573
+ chainId: operation.chainId
574
+ }).operation;
575
+ if (JSON.stringify(rebuilt) !== JSON.stringify(canonicalSvmOperation(operation))) {
576
+ throw new Error('Stored SVM transaction intent is malformed');
577
+ }
578
+ };
579
+ /**
580
+ * Reconstructs the unsigned serialized message (hex) from an approved intent.
581
+ * The signed intent carries no blockhash, so the caller supplies one. Passing the
582
+ * blockhash of the original message is fine while it is still valid.
583
+ */ const resumeSvmTransactionSigningIntent = ({ intent, lifetimeValue })=>{
584
+ const operation = signedSvmOperation(intent);
585
+ const recentBlockhash = assertBlockhash(lifetimeValue);
586
+ const transaction = Buffer.from(buildMessage(operation, recentBlockhash).serialize()).toString('hex');
587
+ assertRebuiltMatchesOperation(transaction, intent.accountAddress, operation);
588
+ return transaction;
589
+ };
590
+
591
+ export { DynamicSvmWalletClient, prepareSvmTransactionSigningOperation, resumeSvmTransactionSigningIntent };
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@dynamic-labs-wallet/svm",
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/browser": "1.1.40",
7
+ "@dynamic-labs-wallet/browser": "1.1.41",
8
+ "@dynamic-labs-wallet/core": "1.1.41",
8
9
  "@solana/web3.js": "^1.98.2",
9
10
  "bs58": "^6.0.0",
10
11
  "@dynamic-labs/sdk-api-core": "0.50.0"
@@ -1,2 +1,3 @@
1
1
  export * from './client.js';
2
+ export { prepareSvmTransactionSigningOperation, resumeSvmTransactionSigningIntent } from './signingIntent.js';
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svm/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svm/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,qCAAqC,EAAE,iCAAiC,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { PreparedSvmTransactionSigningIntent, SigningIntent } from '@dynamic-labs-wallet/core';
2
+ export declare const prepareSvmTransactionSigningOperation: ({ transaction, accountAddress, chainId, }: {
3
+ transaction: string;
4
+ accountAddress: string;
5
+ chainId: string | undefined;
6
+ }) => PreparedSvmTransactionSigningIntent;
7
+ /**
8
+ * Reconstructs the unsigned serialized message (hex) from an approved intent.
9
+ * The signed intent carries no blockhash, so the caller supplies one. Passing the
10
+ * blockhash of the original message is fine while it is still valid.
11
+ */
12
+ export declare const resumeSvmTransactionSigningIntent: ({ intent, lifetimeValue, }: {
13
+ intent: SigningIntent;
14
+ lifetimeValue: string | undefined;
15
+ }) => string;
16
+ //# sourceMappingURL=signingIntent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signingIntent.d.ts","sourceRoot":"","sources":["../../src/svm/signingIntent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mCAAmC,EACnC,aAAa,EAEd,MAAM,2BAA2B,CAAC;AA2FnC,eAAO,MAAM,qCAAqC,8CAI/C;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B,KAAG,mCAWH,CAAC;AA0FF;;;;GAIG;AACH,eAAO,MAAM,iCAAiC,+BAG3C;IACD,MAAM,EAAE,aAAa,CAAC;IACtB,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC,KAAG,MAMH,CAAC"}