Repository: Emurgo/yoroi-frontend
Pull request #4008: staking key signing fix
Author: login: zuzunker
Created: 2025-05-20T15:44:23Z | Merged: 2025-05-22T11:00:58Z

https://emurgo.atlassian.net/browse/YOEXT-1981


Diff:
diff --git a/packages/yoroi-extension/app/api/ada/index.js b/packages/yoroi-extension/app/api/ada/index.js
index 21a62fc39b2..ed7cf6f4955 100644
--- a/packages/yoroi-extension/app/api/ada/index.js
+++ b/packages/yoroi-extension/app/api/ada/index.js
@@ -42,12 +42,12 @@ import {
   asGetAllUtxos,
   asGetPublicKey,
   asGetSigningKey,
+  asGetStakingKey,
   asHasLevels,
   asHasUtxoChains,
-  asGetStakingKey,
 } from './lib/storage/models/PublicDeriver/traits';
 import { ConceptualWallet } from './lib/storage/models/ConceptualWallet/index';
-import { WalletTypeOption, type IHasLevels } from './lib/storage/models/ConceptualWallet/interfaces';
+import { type IHasLevels, WalletTypeOption } from './lib/storage/models/ConceptualWallet/interfaces';
 import type {
   Address,
   Addressing,
@@ -56,8 +56,6 @@ import type {
   IDisplayCutoff,
   IGetAllUtxoAddressesResponse,
   IGetAllUtxosResponse,
-  IGetSigningKey,
-  IGetStakingKey,
   IHasUtxoChains,
   IHasUtxoChainsRequest,
   IPublicDeriver,
@@ -77,7 +75,7 @@ import {
   newAdaUnsignedTx as shelleyNewAdaUnsignedTx,
   newAdaUnsignedTxForConnector as shelleyNewAdaUnsignedTxForConnector,
   sendAllUnsignedTx as shelleySendAllUnsignedTx,
-  signTransaction as shelleySignTransaction,
+  signTransactionFromWallet as shelleySignTransactionFromWallet,
 } from './transactions/shelley/transactions';
 import { generateAdaMnemonic, generateWalletRootKey, } from './lib/cardanoCrypto/cryptoWallet';
 import { buildCoseSign1FromSignature, cip8Sign, makeCip8Key, v4PublicToV2 } from './lib/cardanoCrypto/utils';
@@ -208,7 +206,7 @@ export type AdaGetTransactionsRequest = {|
 // signAndBroadcast
 
 export type SignAndBroadcastRequest = {|
-  publicDeriver: IPublicDeriver<ConceptualWallet & IHasLevels> & IGetSigningKey,
+  publicDeriver: PublicDeriver<>,
   signRequest: {
     senderUtxos: Array<CardanoAddressedUtxo>,
     +unsignedTx: RustModule.WalletV4.Transaction |
@@ -217,10 +215,6 @@ export type SignAndBroadcastRequest = {|
       Buffer |
       Uint8Array,
     metadata: void | RustModule.WalletV4.AuxiliaryData,
-    +neededStakingKeyHashes: {
-      wits: Set<string>, // Vkeywitness
-      ...
-    },
     ...
   },
   password: string,
@@ -350,7 +344,7 @@ export type CreateUnsignedTxForUtxosFunc = (
 // createDelegationTx
 
 export type CreateDelegationTxRequest = {|
- wallet: WalletState,
+  wallet: WalletState,
   absSlotNumber: BigNumber,
   registrationStatus: boolean,
   poolRequest?: string,
@@ -424,10 +418,6 @@ export type CreateWithdrawalTxRequest = {|
 |};
 export type CreateWithdrawalTxResponse = HaskellShelleyTxSignRequest;
 
-export type CreateWithdrawalTxFunc = (
-  request: CreateWithdrawalTxRequest
-) => Promise<CreateWithdrawalTxResponse>;
-
 // saveLastReceiveAddressIndex
 
 export type SaveLastReceiveAddressIndexRequest = {|
@@ -701,22 +691,14 @@ export default class AdaApi {
     Logger.debug(`${nameof(AdaApi)}::${nameof(this.signAndBroadcast)} called`);
     const { password } = request;
     try {
-      const signingKey = await request.publicDeriver.getSigningKey();
-      const normalizedKey = await request.publicDeriver.normalizeKey({
-        ...signingKey,
-        password,
-      });
-
-      const { txHash, encodedTx } = RustModule.WasmScope(Scope => {
-        const signedTx = shelleySignTransaction(
+      const { txHash, encodedTx } = await RustModule.WasmScope(async (Scope) => {
+        const signedTx = await shelleySignTransactionFromWallet(
           request.signRequest.senderUtxos,
           request.signRequest.unsignedTx,
-          request.publicDeriver.getParent().getPublicDeriverLevel(),
-          Scope.WalletV4.Bip32PrivateKey.from_hex(normalizedKey.prvKeyHex),
-          request.signRequest.neededStakingKeyHashes.wits,
+          request.publicDeriver,
+          password,
           request.signRequest.metadata,
         );
-
         return {
           txHash: Scope.WalletV4.FixedTransaction.from_hex(signedTx.to_hex()).transaction_hash().to_hex(),
           encodedTx: signedTx.to_bytes(),
@@ -737,7 +719,7 @@ export default class AdaApi {
       if (error instanceof WrongPassphraseError) {
         throw new IncorrectWalletPasswordError();
       }
-
+      console.error(`${nameof(AdaApi)}::${nameof(this.signAndBroadcast)}`, error);
       Logger.error(`${nameof(AdaApi)}::${nameof(this.signAndBroadcast)} error: ${fullErrStr(error)}` );
       if (error instanceof InvalidWitnessError) {
         throw new InvalidWitnessError();
@@ -969,10 +951,6 @@ export default class AdaApi {
           PoolDeposit: new BigNumber(protocolParameters.poolDeposit),
           NetworkId: request.network.NetworkId,
         },
-        neededStakingKeyHashes: {
-          neededHashes: new Set(),
-          wits: new Set(),
-        },
         receiver: signRequestReceiver,
       });
     } catch (error) {
@@ -1333,10 +1311,6 @@ export default class AdaApi {
         PoolDeposit: new BigNumber(protocolParameters.poolDeposit),
         NetworkId: network.NetworkId,
       },
-      neededStakingKeyHashes: {
-        neededHashes: new Set(),
-        wits: new Set(),
-      },
     });
   }
 
@@ -1430,9 +1404,6 @@ export default class AdaApi {
         .joinAddCopy(differenceAfterTx) // subtract any part of the fee that comes from UTXO
         .joinAddCopy(request.valueInAccount); // recall: rewards are compounding
 
-      const stakeCredentialHex = RustModule.WasmScope(Scope =>
-        Scope.WalletV4.Credential.from_keyhash(stakingKey.hash()).to_hex());
-
       const signTxRequest = new HaskellShelleyTxSignRequest({
         senderUtxos: unsignedTx.senderUtxos,
         unsignedTx: unsignedTx.txBuilder,
@@ -1444,10 +1415,6 @@ export default class AdaApi {
           PoolDeposit: new BigNumber(request.protocolParameters.poolDeposit),
           NetworkId: networkInfo.NetworkId,
         },
-        neededStakingKeyHashes: {
-          neededHashes: new Set([stakeCredentialHex]),
-          wits: new Set(),
-        },
       });
       return {
         signTxRequest,
@@ -1483,12 +1450,7 @@ export default class AdaApi {
       const addressedUtxo = asAddressedUtxo(utxos);
 
       const changeAddr = request.wallet.receiveAddress;
-
       const certificates = [];
-      const neededKeys = {
-        neededHashes: new Set(),
-        wits: new Set(),
-      };
 
       const requiredWits: Array<RustModule.WalletV4.Ed25519KeyHash> = [];
       for (const withdrawal of request.withdrawals) {
@@ -1506,7 +1468,6 @@ export default class AdaApi {
           certificates.push(RustModule.WalletV4.Certificate.new_stake_deregistration(
             RustModule.WalletV4.StakeDeregistration.new(paymentCred)
           ));
-          neededKeys.neededHashes.add(paymentCred.to_hex());
         }
       }
       const accountStates = await request.getAccountState({
@@ -1537,10 +1498,6 @@ export default class AdaApi {
           if (rewardAddress == null) {
             throw new Error(`${nameof(AdaApi)}::${nameof(this.createUnsignedTx)} withdrawal not a reward address`);
           }
-          {
-            const stakeCredential = rewardAddress.payment_cred();
-            neededKeys.neededHashes.add(stakeCredential.to_hex());
-          }
           list.push({
             address: rewardAddress,
             amount: RustModule.WalletV4.BigNum.from_str(rewardForAddress.remainingAmount)
@@ -1579,20 +1536,6 @@ export default class AdaApi {
         `${nameof(AdaApi)}::${nameof(this.createWithdrawalTx)} success: ` + stringifyData(unsignedTxResponse)
       );
 
-      {
-        const tx = unsignedTxResponse.txBuilder.build_tx();
-        for (const withdrawal of request.withdrawals) {
-          if (withdrawal.privateKey != null) {
-            const { privateKey } = withdrawal;
-            neededKeys.wits.add(
-              RustModule.WalletV4.make_vkey_witness(
-                RustModule.WalletV4.FixedTransaction.from_hex(tx.to_hex()).transaction_hash(),
-                privateKey
-              ).to_hex()
-            );
-          }
-        }
-      }
       const result = new HaskellShelleyTxSignRequest({
         senderUtxos: unsignedTxResponse.senderUtxos,
         unsignedTx: unsignedTxResponse.txBuilder,
@@ -1604,7 +1547,6 @@ export default class AdaApi {
           PoolDeposit: new BigNumber(request.protocolParameters.poolDeposit),
           NetworkId: request.wallet.networkId,
         },
-        neededStakingKeyHashes: neededKeys,
       });
       return result;
     } catch (error) {
@@ -1665,10 +1607,6 @@ export default class AdaApi {
           PoolDeposit: new BigNumber(request.protocolParameters.poolDeposit),
           NetworkId: request.publicDeriver.networkId,
         },
-        neededStakingKeyHashes: {
-          neededHashes: new Set(),
-          wits: new Set(),
-        },
       });
     } catch (error) {
       Logger.error(`${nameof(AdaApi)}::${nameof(this.createSimpleTx)} error: ` + stringifyError(error));
@@ -1749,10 +1687,6 @@ export default class AdaApi {
           PoolDeposit: new BigNumber(request.protocolParameters.poolDeposit),
           NetworkId: request.wallet.networkId,
         },
-        neededStakingKeyHashes: {
-          neededHashes: new Set(),
-          wits: new Set(),
-        },
         trezorTCatalystRegistrationTxSignData:
           request.trezorTWallet ? request.trezorTWallet : undefined,
         ledgerNanoCatalystRegistrationTxSignData:
@@ -2595,37 +2529,6 @@ function getDifferenceAfterTx(
   return sumOutForKey.joinSubtractCopy(sumInForKey);
 }
 
-export async function genOwnStakingKey(request: {|
-  publicDeriver: IPublicDeriver<ConceptualWallet & IHasLevels> & IGetSigningKey & IGetStakingKey,
-  password: string,
-|}): Promise<RustModule.WalletV4.PrivateKey> {
-  try {
-    const signingKeyFromStorage = await request.publicDeriver.getSigningKey();
-    const stakingAddr = await request.publicDeriver.getStakingKey();
-    const normalizedKey = await request.publicDeriver.normalizeKey({
-      ...signingKeyFromStorage,
-      password: request.password,
-    });
-    const normalizedSigningKey = RustModule.WalletV4.Bip32PrivateKey.from_hex(normalizedKey.prvKeyHex);
-    const normalizedStakingKey = derivePrivateByAddressing({
-      addressing: stakingAddr.addressing,
-      startingFrom: {
-        key: normalizedSigningKey,
-        level: request.publicDeriver.getParent().getPublicDeriverLevel(),
-      },
-    }).to_raw_key();
-
-    return normalizedStakingKey;
-  } catch (error) {
-    Logger.error(`${nameof(genOwnStakingKey)} error: ` + stringifyError(error));
-    if (error instanceof WrongPassphraseError) {
-      throw new IncorrectWalletPasswordError();
-    }
-    if (error instanceof LocalizableError) throw error;
-    throw new GenericApiError();
-  }
-}
-
 export { cip8Sign } from './lib/cardanoCrypto/utils';
 
 /**
diff --git a/packages/yoroi-extension/app/api/ada/lib/cardanoCrypto/utils.js b/packages/yoroi-extension/app/api/ada/lib/cardanoCrypto/utils.js
index 82de12d8263..636f14cd55e 100644
--- a/packages/yoroi-extension/app/api/ada/lib/cardanoCrypto/utils.js
+++ b/packages/yoroi-extension/app/api/ada/lib/cardanoCrypto/utils.js
@@ -242,3 +242,72 @@ export const makeCip8Key: (Uint8Array) => RustModule.MessageSigning.COSEKey = (p
 
   return key;
 }
+
+function getWithdrawalKeyHashesFromTransactionBody(
+  txBody: RustModule.WalletV4.TransactionBody,
+): Set<string> {
+  const result = new Set<string>();
+  const withdrawals = txBody.withdrawals?.();
+  if (withdrawals != null) {
+    for (const rewardAddress of iterateLenGet(withdrawals.keys())) {
+      maybe(rewardAddress.payment_cred().to_keyhash()?.to_hex(), x => result.add(x));
+    }
+  }
+  return result;
+}
+
+function resolveCredential(
+  cred: RustModule.WalletV4.Credential,
+): {| keyHash: ?string, scriptHash: ?string |} {
+  return {
+    keyHash: cred.to_keyhash()?.to_hex(),
+    scriptHash: cred.to_scripthash()?.to_hex(),
+  };
+}
+
+function getCertificateStakeCredential(
+  cert: RustModule.WalletV4.Certificate,
+): ?{| keyHash: ?string, scriptHash: ?string |} {
+  switch (cert.kind()) {
+    case RustModule.WalletV4.CertificateKind.StakeRegistration:
+      return resolveCredential(forceNonNull(cert.as_stake_registration()).stake_credential());
+    case RustModule.WalletV4.CertificateKind.StakeDeregistration:
+      return resolveCredential(forceNonNull(cert.as_stake_deregistration()).stake_credential());
+    case RustModule.WalletV4.CertificateKind.StakeDelegation:
+      return resolveCredential(forceNonNull(cert.as_stake_delegation()).stake_credential());
+    case RustModule.WalletV4.CertificateKind.StakeAndVoteDelegation:
+      return resolveCredential(forceNonNull(cert.as_stake_and_vote_delegation()).stake_credential());
+    case RustModule.WalletV4.CertificateKind.StakeRegistrationAndDelegation:
+      return resolveCredential(forceNonNull(cert.as_stake_registration_and_delegation()).stake_credential());
+    case RustModule.WalletV4.CertificateKind.StakeVoteRegistrationAndDelegation:
+      return resolveCredential(forceNonNull(cert.as_stake_vote_registration_and_delegation()).stake_credential());
+    case RustModule.WalletV4.CertificateKind.VoteDelegation:
+      return resolveCredential(forceNonNull(cert.as_vote_delegation()).stake_credential());
+    case RustModule.WalletV4.CertificateKind.VoteRegistrationAndDelegation:
+      return resolveCredential(forceNonNull(cert.as_vote_registration_and_delegation()).stake_credential());
+    default:
+      return null;
+  }
+}
+
+function getCertificateKeyHashesFromTransactionBody(
+  txBody: RustModule.WalletV4.TransactionBody,
+): Set<string> {
+  const result = new Set<string>();
+  const certificates = txBody.certs?.();
+  if (certificates != null) {
+    for (const cert of iterateLenGet(certificates)) {
+      maybe(getCertificateStakeCredential(cert)?.keyHash, x => result.add(x));
+    }
+  }
+  return result;
+}
+
+export function getStakingKeyHashesInTransactionBody(txBodyHex: string): Set<string> {
+  return RustModule.WasmScope(Module => {
+    const txBody = Module.WalletV4.TransactionBody.from_hex(txBodyHex);
+    const withdrawalKeys = getWithdrawalKeyHashesFromTransactionBody(txBody);
+    const certificateKeys = getCertificateKeyHashesFromTransactionBody(txBody);
+    return new Set([ ...withdrawalKeys, ...certificateKeys ]);
+  });
+}
diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/index.js b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/index.js
index 25575a3e867..b1323e39e67 100644
--- a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/index.js
+++ b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/index.js
@@ -43,6 +43,7 @@ import { UtxoService } from '@emurgo/yoroi-lib/dist/utxo';
 import { UtxoStorageApi, } from '../utils';
 import UtxoApi from '../../../state-fetch/utxoApi';
 import { networks } from '../../database/prepackaged/networks';
+import type { IHasLevels } from '../ConceptualWallet/interfaces';
 
 /** Snapshot of a PublicDeriver in the database */
 export class PublicDeriver<+Parent: ConceptualWallet = ConceptualWallet>
@@ -52,7 +53,7 @@ implements IPublicDeriver<Parent>, IRename, IGetLastSyncInfo {
    */
 
   publicDeriverId: number;
-  +parent: Parent;
+  +parent: Parent & IHasLevels;
   derivationId: number;
   pathToPublic: Array<number>;
   utxoService: UtxoService;
@@ -64,6 +65,7 @@ implements IPublicDeriver<Parent>, IRename, IGetLastSyncInfo {
    */
   constructor(data: IPublicDeriverConstructor<Parent>): PublicDeriver<Parent> {
     this.publicDeriverId = data.publicDeriverId;
+    // $FlowIgnore
     this.parent = data.parent;
     this.pathToPublic = data.pathToPublic;
     this.derivationId = data.derivationId;
@@ -87,7 +89,7 @@ implements IPublicDeriver<Parent>, IRename, IGetLastSyncInfo {
     return this.publicDeriverId;
   }
 
-  getParent(): Parent {
+  getParent(): Parent & IHasLevels {
     return this.parent;
   }
 
diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/interfaces.js b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/interfaces.js
index e7a50d53baf..da77f1a7b71 100644
--- a/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/interfaces.js
+++ b/packages/yoroi-extension/app/api/ada/lib/storage/models/PublicDeriver/interfaces.js
@@ -5,7 +5,7 @@ import type {
 } from 'lovefield';
 
 import { ConceptualWallet } from '../ConceptualWallet/index';
-import type { IConceptualWallet } from '../ConceptualWallet/interfaces';
+import type { IConceptualWallet, IHasLevels } from '../ConceptualWallet/interfaces';
 
 import {
   GetUtxoTxOutputsWithTx,
@@ -104,7 +104,7 @@ export interface IPublicDeriver<+Parent: ConceptualWallet = ConceptualWallet> {
   constructor(data: IPublicDeriverConstructor<Parent>): IPublicDeriver<Parent>;
   getDb(): lf$Database;
   getPublicDeriverId(): number;
-  getParent(): Parent;
+  getParent(): Parent & IHasLevels;
   getPathToPublic(): Array<number>;
   getDerivationId(): number;
   getFullPublicDeriverInfo(): Promise<$ReadOnly<PublicDeriverRow>>;
diff --git a/packages/yoroi-extension/app/api/ada/staking.js b/packages/yoroi-extension/app/api/ada/staking.js
new file mode 100644
index 00000000000..e72a5aab093
--- /dev/null
+++ b/packages/yoroi-extension/app/api/ada/staking.js
@@ -0,0 +1,40 @@
+import type { IGetSigningKey, IGetStakingKey, IPublicDeriver } from './lib/storage/models/PublicDeriver/interfaces';
+import { ConceptualWallet } from './lib/storage/models/ConceptualWallet';
+import type { IHasLevels } from './lib/storage/models/ConceptualWallet/interfaces';
+import { RustModule } from './lib/cardanoCrypto/rustLoader';
+import { derivePrivateByAddressing } from './lib/cardanoCrypto/deriveByAddressing';
+import { Logger, stringifyError } from '../../utils/logging';
+import { WrongPassphraseError } from './lib/cardanoCrypto/cryptoErrors';
+import { GenericApiError, IncorrectWalletPasswordError } from '../common/errors';
+import LocalizableError from '../../i18n/LocalizableError';
+
+export async function genOwnStakingKey(request: {|
+  publicDeriver: IPublicDeriver<ConceptualWallet & IHasLevels> & IGetSigningKey & IGetStakingKey,
+  password: string,
+|}): Promise<RustModule.WalletV4.PrivateKey> {
+  try {
+    const signingKeyFromStorage = await request.publicDeriver.getSigningKey();
+    const stakingAddr = await request.publicDeriver.getStakingKey();
+    const normalizedKey = await request.publicDeriver.normalizeKey({
+      ...signingKeyFromStorage,
+      password: request.password,
+    });
+    const normalizedSigningKey = RustModule.WalletV4.Bip32PrivateKey.from_hex(normalizedKey.prvKeyHex);
+    const normalizedStakingKey = derivePrivateByAddressing({
+      addressing: stakingAddr.addressing,
+      startingFrom: {
+        key: normalizedSigningKey,
+        level: request.publicDeriver.getParent().getPublicDeriverLevel(),
+      },
+    }).to_raw_key();
+
+    return normalizedStakingKey;
+  } catch (error) {
+    Logger.error(`${nameof(genOwnStakingKey)} error: ` + stringifyError(error));
+    if (error instanceof WrongPassphraseError) {
+      throw new IncorrectWalletPasswordError();
+    }
+    if (error instanceof LocalizableError) throw error;
+    throw new GenericApiError();
+  }
+}
\ No newline at end of file
diff --git a/packages/yoroi-extension/app/api/ada/transactions/byron/yoroiTransfer.js b/packages/yoroi-extension/app/api/ada/transactions/byron/yoroiTransfer.js
index 6f4e0474130..15df586a119 100644
--- a/packages/yoroi-extension/app/api/ada/transactions/byron/yoroiTransfer.js
+++ b/packages/yoroi-extension/app/api/ada/transactions/byron/yoroiTransfer.js
@@ -93,7 +93,7 @@ export async function buildYoroiTransferTx(payload: {|
       unsignedTxResponse.txBuilder,
       payload.keyLevel,
       payload.signingKey,
-      new Set(),
+      null,
       undefined,
     );
 
diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/HaskellShelleyTxSignRequest.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/HaskellShelleyTxSignRequest.js
index a472755a5b4..3511cbdad55 100644
--- a/packages/yoroi-extension/app/api/ada/transactions/shelley/HaskellShelleyTxSignRequest.js
+++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/HaskellShelleyTxSignRequest.js
@@ -52,11 +52,6 @@ implements ISignRequest<RustModule.WalletV4.TransactionBuilder> {
   changeAddr: Array<{| ...Address, ...Value, ...Addressing |}>;
   metadata: void | RustModule.WalletV4.AuxiliaryData;
   networkSettingSnapshot: NetworkSettingSnapshot;
-  // TODO: this should be provided by WASM in some SignedTxBuilder interface of some kind
-  neededStakingKeyHashes: {|
-    neededHashes: Set<string>, // StakeCredential
-    wits: Set<string>, // Vkeywitness
-  |};
   trezorTCatalystRegistrationTxSignData:
     void | TrezorTCatalystRegistrationTxSignData;
   ledgerNanoCatalystRegistrationTxSignData:
@@ -68,10 +63,6 @@ implements ISignRequest<RustModule.WalletV4.TransactionBuilder> {
     changeAddr: Array<{| ...Address, ...Value, ...Addressing |}>,
     metadata: void | RustModule.WalletV4.AuxiliaryData,
     networkSettingSnapshot: NetworkSettingSnapshot,
-    neededStakingKeyHashes: {|
-      neededHashes: Set<string>, // StakeCredential
-      wits: Set<string>, // Vkeywitness
-    |},
     trezorTCatalystRegistrationTxSignData?:
       void | TrezorTCatalystRegistrationTxSignData;
     ledgerNanoCatalystRegistrationTxSignData?:
@@ -83,7 +74,6 @@ implements ISignRequest<RustModule.WalletV4.TransactionBuilder> {
     this.changeAddr = data.changeAddr;
     this.metadata = data.metadata;
     this.networkSettingSnapshot = data.networkSettingSnapshot;
-    this.neededStakingKeyHashes = data.neededStakingKeyHashes;
     this.trezorTCatalystRegistrationTxSignData =
       data.trezorTCatalystRegistrationTxSignData;
     this.ledgerNanoCatalystRegistrationTxSignData =
diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.test.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.test.js
index e70c1d3765c..260789f7624 100644
--- a/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.test.js
+++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/ledgerTx.test.js
@@ -327,10 +327,6 @@ test('Create Ledger transaction', async () => {
       KeyDeposit: new BigNumber(baseConfig.KeyDeposit),
       NetworkId: network.NetworkId,
     },
-    neededStakingKeyHashes: {
-      neededHashes: new Set([stakeCredential.to_hex()]),
-      wits: new Set() // not needed for this test, but something should be here
-    },
   });
 
   const rewardAddressString = RustModule.WalletV4.RewardAddress.new(
diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/transactions.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/transactions.js
index 7918516bf42..e64937293a8 100644
--- a/packages/yoroi-extension/app/api/ada/transactions/shelley/transactions.js
+++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/transactions.js
@@ -51,6 +51,10 @@ import type { DerivationInfo } from '@emurgo/yoroi-eutxo-txs/dist/classes';
 import { DerivationLevel } from '@emurgo/yoroi-eutxo-txs/dist/classes/addresses/derivation-info.type';
 import type { RequiredInput, TxMint } from '@emurgo/yoroi-eutxo-txs/dist/tx-builder';
 import type { NetworkConfig } from '@emurgo/yoroi-eutxo-txs/dist/kernel';
+import { PublicDeriver } from '../../lib/storage/models/PublicDeriver';
+import { asGetAllAccounting, asGetSigningKey } from '../../lib/storage/models/PublicDeriver/traits';
+import { genOwnStakingKey } from '../../staking';
+import { getStakingKeyHashesInTransactionBody } from '../../lib/cardanoCrypto/utils';
 
 // <TODO:FETCH> unmagic this
 const COSTMODELS = 'a30098a61a000189b41901a401011903e818ad00011903e819ea350401192baf18201a000312591920a404193e801864193e801864193e801864193e801864193e801864193e80186418641864193e8018641a000170a718201a00020782182019f016041a0001194a18b2000119568718201a0001643519030104021a00014f581a00037c71187a0001011903e819a7a90402195fe419733a1826011a000db464196a8f0119ca3f19022e011999101903e819ecb2011a00022a4718201a000144ce1820193bc318201a0001291101193371041956540a197147184a01197147184a0119a9151902280119aecd19021d0119843c18201a00010a9618201a00011aaa1820191c4b1820191cdf1820192d1a18201a00014f581a00037c71187a0001011a0001614219020700011a000122c118201a00014f581a00037c71187a0001011a00014f581a00037c71187a0001011a0004213c19583c041a00163cad19fc3604194ff30104001a00022aa818201a000189b41901a401011a00013eff182019e86a1820194eae182019600c1820195108182019654d182019602f18201a032e93af1937fd0a0198af1a000189b41901a401011903e818ad00011903e819ea350401192baf18201a000312591920a404193e801864193e801864193e801864193e801864193e801864193e80186418641864193e8018641a000170a718201a00020782182019f016041a0001194a18b2000119568718201a0001643519030104021a00014f581a00037c71187a0001011903e819a7a90402195fe419733a1826011a000db464196a8f0119ca3f19022e011999101903e819ecb2011a00022a4718201a000144ce1820193bc318201a0001291101193371041956540a197147184a01197147184a0119a9151902280119aecd19021d0119843c18201a00010a9618201a00011aaa1820191c4b1820191cdf1820192d1a18201a00014f581a00037c71187a0001011a0001614219020700011a000122c118201a00014f581a00037c71187a0001011a00014f581a00037c71187a0001011a000e94721a0003414000021a0004213c19583c041a00163cad19fc3604194ff30104001a00022aa818201a000189b41901a401011a00013eff182019e86a1820194eae182019600c1820195108182019654d182019602f18201a0290f1e70a1a032e93af1937fd0a1a0298e40b1966c40a029901291a000189b41901a401011903e818ad00011903e819ea350401192baf18201a000312591920a404193e801864193e801864193e801864193e801864193e801864193e80186418641864193e8018641a000170a718201a00020782182019f016041a0001194a18b2000119568718201a0001643519030104021a00014f581a0001e143191c893903831906b419022518391a00014f580001011903e819a7a90402195fe419733a1826011a000db464196a8f0119ca3f19022e011999101903e819ecb2011a00022a4718201a000144ce1820193bc318201a0001291101193371041956540a197147184a01197147184a0119a9151902280119aecd19021d0119843c18201a00010a9618201a00011aaa1820191c4b1820191cdf1820192d1a18201a00014f581a0001e143191c893903831906b419022518391a00014f5800011a0001614219020700011a000122c118201a00014f581a0001e143191c893903831906b419022518391a00014f580001011a00014f581a0001e143191c893903831906b419022518391a00014f5800011a000e94721a0003414000021a0004213c19583c041a00163cad19fc3604194ff30104001a00022aa818201a000189b41901a401011a00013eff182019e86a1820194eae182019600c1820195108182019654d182019602f18201a0290f1e70a1a032e93af1937fd0a1a0298e40b1966c40a193e801864193e8018641a000eaf1f121a002a6e06061a0006be98011a0321aac7190eac121a00041699121a048e466e1922a4121a0327ec9a121a001e743c18241a0031410f0c1a000dbf9e011a09f2f6d31910d318241a0004578218241a096e44021967b518241a0473cee818241a13e62472011a0f23d40118481a00212c5618481a0022814619fc3b041a00032b00192076041a0013be0419702c183f00011a000f59d919aa6718fb00011a000187551902d61902cf00011a000187551902d61902cf00011a000187551902d61902cf00011a0001a5661902a800011a00017468011a00044a391949a000011a0002bfe2189f01011a00026b371922ee00011a00026e9219226d00011a0001a3e2190ce2011a00019e4919028f011a001df8bb195fc803';
@@ -1084,6 +1088,46 @@ async function newAdaUnsignedTxFromUtxoForConnector(
 
 type UtxoOrAddressing = CardanoAddressedUtxo | {| ...Address, ...Addressing |};
 
+export async function signTransactionFromWallet(
+  senderUtxos: Array<CardanoAddressedUtxo>,
+  unsignedTx: RustModule.WalletV4.Transaction |
+    RustModule.WalletV4.TransactionBuilder |
+    RustModule.WalletV4.TransactionBody |
+    Buffer |
+    Uint8Array,
+  wallet: PublicDeriver<>,
+  password: string,
+  metadata: ?RustModule.WalletV4.AuxiliaryData,
+  otherRequiredSigners: Array<{| ...Address, ...Addressing |}> = [],
+): Promise<RustModule.WalletV4.Transaction> {
+  const withSigning = asGetSigningKey(wallet);
+  if (withSigning == null) {
+    throw new Error('unexpected missing asGetSigningKey result');
+  }
+  const signingKey = await withSigning.getSigningKey();
+  const normalizedKey = await withSigning.normalizeKey({
+    ...signingKey,
+    password,
+  });
+  const withStakingKey = asGetAllAccounting(withSigning);
+  if (withStakingKey == null) {
+    throw new Error('unexpected missing asGetAllAcccounting result');
+  }
+  const stakingKey = await genOwnStakingKey({
+    publicDeriver: withStakingKey,
+    password,
+  });
+  return signTransaction(
+    senderUtxos,
+    unsignedTx,
+    wallet.getParent().getPublicDeriverLevel(),
+    RustModule.WalletV4.Bip32PrivateKey.from_hex(normalizedKey.prvKeyHex),
+    stakingKey,
+    metadata,
+    otherRequiredSigners,
+  )
+}
+
 export function signTransaction(
   senderUtxos: Array<CardanoAddressedUtxo>,
   unsignedTx: RustModule.WalletV4.Transaction |
@@ -1093,7 +1137,7 @@ export function signTransaction(
     Uint8Array,
   keyLevel: number,
   signingKey: RustModule.WalletV4.Bip32PrivateKey,
-  stakingKeyWits: Set<string>,
+  stakingKey: ?RustModule.WalletV4.PrivateKey,
   metadata: ?RustModule.WalletV4.AuxiliaryData,
   otherRequiredSigners: Array<{| ...Address, ...Addressing |}> = [],
 ): RustModule.WalletV4.Transaction {
@@ -1173,13 +1217,20 @@ export function signTransaction(
     bootstrapWits,
   );
 
-  const stakingKeySigSet = new Set<string>();
-  for (const witness of stakingKeyWits) {
-    if (stakingKeySigSet.has(witness)) {
-      continue;
+  const stakingKeyHashesInTx = getStakingKeyHashesInTransactionBody(txBody.to_hex());
+  if (stakingKeyHashesInTx.size > 0) {
+    if (stakingKey == null) {
+      console.warn('Transaction in signing contains staking credentials but no staking key is provided. Cannot fully sign.')
+    } else {
+      const ownStakingKeyHash = stakingKey.to_public().hash().to_hex();
+      if (stakingKeyHashesInTx.has(ownStakingKeyHash)) {
+        vkeyWits.add(RustModule.WalletV4.make_vkey_witness(txHash, stakingKey));
+      }
+      if (stakingKeyHashesInTx.size > 1) {
+        const foreignStakingKeyHashes = [...stakingKeyHashesInTx].filter(x => x !== ownStakingKeyHash);
+        console.warn(`Transaction in signing contains foreign staking credentials hashes '${JSON.stringify(foreignStakingKeyHashes)}' which do not match own staking key '${String(ownStakingKeyHash)}'. Cannot fully sign.`)
+      }
     }
-    stakingKeySigSet.add(witness);
-    vkeyWits.add(RustModule.WalletV4.Vkeywitness.from_hex(witness));
   }
 
   if (bootstrapWits.len() > 0) witnessSet.set_bootstraps(bootstrapWits);
diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/transactions.test.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/transactions.test.js
index f4708df65bb..03266fbb455 100644
--- a/packages/yoroi-extension/app/api/ada/transactions/shelley/transactions.test.js
+++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/transactions.test.js
@@ -816,7 +816,7 @@ describe('Create signed transactions', () => {
       unsignedTxResponse.txBuilder.build().to_bytes(),
       Bip44DerivationLevels.ACCOUNT.level,
       accountPrivateKey,
-      new Set(),
+      null,
       undefined,
     );
     const witnesses = signedTx.witness_set();
@@ -893,7 +893,7 @@ describe('Create signed transactions', () => {
       txBody,
       Bip44DerivationLevels.ACCOUNT.level,
       accountPrivateKey,
-      new Set(),
+      null,
       undefined,
     );
     const witnesses = signedTx.witness_set();
@@ -962,14 +962,7 @@ describe('Create signed transactions', () => {
       unsignedTxResponse.txBuilder.build().to_bytes(),
       Bip44DerivationLevels.ACCOUNT.level,
       accountPrivateKey,
-      new Set([
-        RustModule.WalletV4.make_vkey_witness(
-          RustModule.WalletV4.FixedTransaction.from_hex(
-            unsignedTxResponse.txBuilder.build_tx().to_hex(),
-          ).transaction_hash(),
-          stakingKey,
-        ).to_hex()
-      ]),
+      stakingKey,
       undefined,
     );
     const witnesses = signedTx.witness_set();
@@ -1037,14 +1030,7 @@ describe('Create signed transactions', () => {
       unsignedTxResponse.txBuilder.build().to_bytes(),
       Bip44DerivationLevels.ACCOUNT.level,
       accountPrivateKey,
-      new Set([
-        RustModule.WalletV4.make_vkey_witness(
-          RustModule.WalletV4.FixedTransaction.from_hex(
-            unsignedTxResponse.txBuilder.build_tx().to_hex()
-          ).transaction_hash(),
-          stakingKey,
-        ).to_hex(),
-      ]),
+      stakingKey,
       undefined,
     );
     const witnesses = signedTx.witness_set();
diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.test.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.test.js
index 3ce0911960b..1b326c2b26a 100644
--- a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.test.js
+++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.test.js
@@ -226,10 +226,6 @@ test('Create Trezor transaction', async () => {
         KeyDeposit: new BigNumber(baseConfig.KeyDeposit),
         NetworkId: network.NetworkId,
       },
-      neededStakingKeyHashes: {
-        neededHashes: new Set([stakeCredential.to_hex()]),
-        wits: new Set() // not needed for this test, but something should be here
-      },
     }),
     ByronNetworkId,
     Number.parseInt(ChainNetworkId, 10),
diff --git a/packages/yoroi-extension/app/api/thunk.js b/packages/yoroi-extension/app/api/thunk.js
index c836ff3f047..6f2905d245c 100644
--- a/packages/yoroi-extension/app/api/thunk.js
+++ b/packages/yoroi-extension/app/api/thunk.js
@@ -2,7 +2,6 @@
 
 import type { WalletState, ServerStatus } from '../../chrome/extension/background/types';
 import { HaskellShelleyTxSignRequest } from './ada/transactions/shelley/HaskellShelleyTxSignRequest';
-import { RustModule } from './ada/lib/cardanoCrypto/rustLoader';
 import WalletTransaction from '../domain/WalletTransaction';
 import type { WalletAuthEntry } from '../../chrome/extension/connector/types';
 import CardanoShelleyTransaction, {
@@ -218,21 +217,16 @@ export async function signAndBroadcastTransaction(
 ): Promise<{| txId: string |}> {
   const tx = request.signRequest.unsignedTx.build_tx();
   const txBody = tx.body();
-  const txHash = RustModule.WalletV4.FixedTransaction.from_hex(tx.to_hex()).transaction_hash();
 
   const serializableRequest: SignAndBroadcastTransactionRequestType = {
     senderUtxos: request.signRequest.senderUtxos,
     unsignedTx: tx.to_hex(),
     metadata: request.signRequest.metadata?.to_hex(),
-    neededHashes: [...request.signRequest.neededStakingKeyHashes.neededHashes],
-    wits: [...request.signRequest.neededStakingKeyHashes.wits],
     password: request.password,
     publicDeriverId: request.publicDeriverId,
-    txHash: txHash.to_hex(),
   };
-  tx.free();
   txBody.free();
-  txHash.free();
+  tx.free();
   const result = await callBackground({
     type: SignAndBroadcastTransaction.typeTag,
     request: serializableRequest,
diff --git a/packages/yoroi-extension/app/containers/wallet/staking/WithdrawRewardsDialog.js b/packages/yoroi-extension/app/containers/wallet/staking/WithdrawRewardsDialog.js
index 5e2fd0c21c8..0a877e9afd6 100644
--- a/packages/yoroi-extension/app/containers/wallet/staking/WithdrawRewardsDialog.js
+++ b/packages/yoroi-extension/app/containers/wallet/staking/WithdrawRewardsDialog.js
@@ -154,11 +154,8 @@ export default class WithdrawRewardsDialog extends Component<{| ...StoresProps,
 
     const network = getNetworkById(publicDeriver.networkId);
     const meta = this.props.stores.delegation.getLocalPoolInfo(publicDeriver.networkId, String(currentPool));
-    if (meta == null) {
-      // server hasn't returned information about the stake pool yet
-      return null;
-    }
-    const name = meta.info?.name ?? intl.formatMessage(globalMessages.unknownPoolLabel);
+
+    const name = meta?.info?.name ?? intl.formatMessage(globalMessages.unknownPoolLabel);
     const avatarSource = toSvg(currentPool, 36, { padding: 0 });
     const avatarGenerated = `data:image/svg+xml;utf8,${encodeURIComponent(avatarSource)}`;
 
diff --git a/packages/yoroi-extension/app/stores/stateless/addressStores.js b/packages/yoroi-extension/app/stores/stateless/addressStores.js
index 630565d71de..a2642a424ae 100644
--- a/packages/yoroi-extension/app/stores/stateless/addressStores.js
+++ b/packages/yoroi-extension/app/stores/stateless/addressStores.js
@@ -352,6 +352,7 @@ export async function getReceiveAddress(
     const nextInternal = await withChains.nextInternal();
     return nextInternal.addressInfo;
   }
+  // <TODO:PENDING_REMOVAL> we don't have wallets without chain support
   const withUtxos = asGetAllUtxos(publicDeriver);
   if (withUtxos) {
 
diff --git a/packages/yoroi-extension/chrome/extension/background/handlers/yoroi/transaction.js b/packages/yoroi-extension/chrome/extension/background/handlers/yoroi/transaction.js
index c3bcd2eade8..0b61b0e467c 100644
--- a/packages/yoroi-extension/chrome/extension/background/handlers/yoroi/transaction.js
+++ b/packages/yoroi-extension/chrome/extension/background/handlers/yoroi/transaction.js
@@ -9,11 +9,7 @@ import {
   transactionHexToHash,
   transactionHexAddSignaturesFromWitnessSetHex,
 } from '../../../../../app/api/ada/lib/cardanoCrypto/utils';
-import {
-  asGetSigningKey,
-  asGetAllAccounting,
-} from '../../../../../app/api/ada/lib/storage/models/PublicDeriver/traits';
-import AdaApi, { genOwnStakingKey } from '../../../../../app/api/ada';
+import AdaApi from '../../../../../app/api/ada';
 import { RustModule } from '../../../../../app/api/ada/lib/cardanoCrypto/rustLoader';
 import { emitUpdateToSubscriptions } from '../../subscriptionManager';
 import LocalStorageApi from '../../../../../app/api/localStorage/index';
@@ -61,10 +57,7 @@ export type SignAndBroadcastTransactionRequestType = {|
   senderUtxos: Array<CardanoAddressedUtxo>,
   unsignedTx: string,
   metadata: ?string,
-  neededHashes: Array<string>,
-  wits: Array<string>,
   password: string,
-  txHash: string,
 |};
 export const SignAndBroadcastTransaction: HandlerType<
   SignAndBroadcastTransactionRequestType,
@@ -75,57 +68,21 @@ export const SignAndBroadcastTransaction: HandlerType<
   handle: async (request) => {
     return RustModule.WasmScope(async (Scope) => {
       const publicDeriver = await getPublicDeriverById(request.publicDeriverId);
-      const { senderUtxos, unsignedTx, metadata, wits, neededHashes, txHash } = request;
+      const { senderUtxos, unsignedTx, metadata, password, } = request;
 
       try {
-        const withSigning = asGetSigningKey(publicDeriver);
-        if (withSigning == null) {
-          throw new Error('unexpected missing asGetSigningKey result');
-        }
-
-        if (neededHashes.length - wits.length >= 2) {
-          throw new Error('Too many missing witnesses');
-        }
-        if (neededHashes.length !== wits.length) {
-          const withStakingKey = asGetAllAccounting(withSigning);
-          if (withStakingKey == null) {
-            throw new Error('unexpected missing asGetAllAcccounting result');
-          }
-          const stakingKey = await genOwnStakingKey({
-            publicDeriver: withStakingKey,
-            password: request.password,
-          });
-          if (neededHashes.includes(
-              Scope.WalletV4.Credential.from_keyhash(
-                stakingKey.to_public().hash()
-              ).to_hex()
-          )) {
-            wits.push(
-              Scope.WalletV4.make_vkey_witness(
-                Scope.WalletV4.TransactionHash.from_hex(txHash),
-                stakingKey
-              ).to_hex()
-            );
-          } else {
-            throw new Error('missing witness but it was not ours');
-          }
-        }
-
         const unsignedTxWasm = RustModule.WalletV4.Transaction.from_hex(unsignedTx);
 
         const signRequest = {
           senderUtxos,
           unsignedTx: unsignedTxWasm,
           metadata: metadata ? Scope.WalletV4.AuxiliaryData.from_hex(metadata) : undefined,
-          neededStakingKeyHashes: {
-            wits: new Set(wits),
-          },
         };
         const stateFetcher = await getCardanoStateFetcher(new LocalStorageApi());
         const adaApi = new AdaApi();
         const { txId, signedTxHex, } = await adaApi.signAndBroadcast({
-          publicDeriver: withSigning,
-          password: request.password,
+          publicDeriver,
+          password,
           signRequest,
           sendTx: stateFetcher.sendTx,
         });
diff --git a/packages/yoroi-extension/chrome/extension/background/handlers/yoroi/wallet.js b/packages/yoroi-extension/chrome/extension/background/handlers/yoroi/wallet.js
index fb8323febd8..42a119748eb 100644
--- a/packages/yoroi-extension/chrome/extension/background/handlers/yoroi/wallet.js
+++ b/packages/yoroi-extension/chrome/extension/background/handlers/yoroi/wallet.js
@@ -2,7 +2,7 @@
 import type { HandlerType } from './type';
 import { RustModule } from '../../../../../app/api/ada/lib/cardanoCrypto/rustLoader';
 import { getNetworkById } from '../../../../../app/api/ada/lib/storage/database/prepackaged/networks';
-import AdaApi, { genOwnStakingKey } from '../../../../../app/api/ada';
+import AdaApi from '../../../../../app/api/ada';
 import { getDb, syncWallet } from '../../state';
 import { emitUpdateToSubscriptions } from '../../subscriptionManager';
 import type { WalletState } from '../../types';
@@ -32,6 +32,7 @@ import type { AdaGetTransactionsRequest } from '../../../../../app/api/ada';
 import { updateProtocolParametersCacheFromNetwork } from './protocolParameters';
 import { isAnyTrezorWallet } from '../../../../../app/api/ada/lib/storage/models/ConceptualWallet';
 import type { PublicDeriver, } from '../../../../../app/api/ada/lib/storage/models/PublicDeriver';
+import { genOwnStakingKey } from '../../../../../app/api/ada/staking';
 
 type CreateWalletRequest = {|
   networkId: number,
diff --git a/packages/yoroi-extension/chrome/extension/connector/api.js b/packages/yoroi-extension/chrome/extension/connector/api.js
index e145ab42ff8..9a7cb41c8ac 100644
--- a/packages/yoroi-extension/chrome/extension/connector/api.js
+++ b/packages/yoroi-extension/chrome/extension/connector/api.js
@@ -9,11 +9,9 @@ import {
   asGetAllUtxos,
   asGetBalance,
   asGetPublicKey,
-  asGetSigningKey,
   asHasLevels,
   asHasUtxoChains,
 } from '../../../app/api/ada/lib/storage/models/PublicDeriver/traits';
-import { ConceptualWallet } from '../../../app/api/ada/lib/storage/models/ConceptualWallet/index';
 import BigNumber from 'bignumber.js';
 
 import { CannotSendBelowMinimumValueError, NotEnoughMoneyToSendError, } from '../../../app/api/common/errors';
@@ -39,7 +37,7 @@ import type {
   RemoteUnspentOutput,
 } from '../../../app/api/ada/lib/state-fetch/types';
 import {
-  signTransaction as shelleySignTransaction,
+  signTransactionFromWallet as shelleySignTransactionFromWallet,
   toLibUTxO,
 } from '../../../app/api/ada/transactions/shelley/transactions';
 import {
@@ -755,21 +753,6 @@ async function __connectorSignCardanoTx(
     submittedTxs
   );
 
-  const withLevels = asHasLevels<ConceptualWallet>(publicDeriver);
-  if (!withLevels) {
-    throw new Error(`can't get level`);
-  }
-
-  const withSigningKey = asGetSigningKey(publicDeriver);
-  if (!withSigningKey) {
-    throw new Error('expect to be able to get signing key');
-  }
-  const signingKey = await withSigningKey.getSigningKey();
-  const normalizedKey = await withSigningKey.normalizeKey({
-    ...signingKey,
-    password,
-  });
-
   const utxoIdSet: Set<string> =
     iterateLenGet(txBody.inputs())
       .join(iterateLenGet(txBody.collateral()))
@@ -779,12 +762,11 @@ async function __connectorSignCardanoTx(
   const usedUtxos = addressedUtxos
     .filter(utxo => utxoIdSet.has(utxo.utxo_id));
 
-  const signedTx = shelleySignTransaction(
+  const signedTx = await shelleySignTransactionFromWallet(
     usedUtxos,
     rawTxBody,
-    withLevels.getParent().getPublicDeriverLevel(),
-    RustModule.WalletV4.Bip32PrivateKey.from_hex(normalizedKey.prvKeyHex),
-    new Set(), // stakingKeyWits
+    publicDeriver,
+    password,
     auxiliaryData, // metadata
     otherRequiredSigners,
   );
@@ -833,32 +815,14 @@ export async function connectorCreateCardanoTx(
     },
     foreignUtxoFetcher,
   );
-
   if (password == null) {
     return signRequest.unsignedTx.build_tx().to_hex();
   }
-
-  const withSigningKey = asGetSigningKey(publicDeriver);
-  if (!withSigningKey) {
-    throw new Error('expect to be able to get signing key');
-  }
-  const signingKey = await withSigningKey.getSigningKey();
-  const normalizedKey = await withSigningKey.normalizeKey({
-    ...signingKey,
-    password,
-  });
-
-  const withLevels = asHasLevels<ConceptualWallet>(publicDeriver);
-  if (!withLevels) {
-    throw new Error(`can't get level`);
-  }
-
-  const signedTx = shelleySignTransaction(
+  const signedTx = await shelleySignTransactionFromWallet(
     signRequest.senderUtxos,
     signRequest.unsignedTx,
-    withLevels.getParent().getPublicDeriverLevel(),
-    RustModule.WalletV4.Bip32PrivateKey.from_hex(normalizedKey.prvKeyHex),
-    signRequest.neededStakingKeyHashes.wits,
+    publicDeriver,
+    password,
     signRequest.metadata,
   );
   return signedTx.to_hex();


Reviews:
user: login: yushih
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-22T03:47:06Z
user: login: yushih
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-22T03:47:18Z
user: login: zuzunker
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T08:06:47Z

Review comments:
diff_hunk: @@ -1084,6 +1088,46 @@ async function newAdaUnsignedTxFromUtxoForConnector(
 
 type UtxoOrAddressing = CardanoAddressedUtxo | {| ...Address, ...Addressing |};
 
+export async function signTransactionFromWallet(
+  senderUtxos: Array<CardanoAddressedUtxo>,
+  unsignedTx: RustModule.WalletV4.Transaction |
+    RustModule.WalletV4.TransactionBuilder |
+    RustModule.WalletV4.TransactionBody |
+    Buffer |
+    Uint8Array,
+  wallet: PublicDeriver<>,
+  password: string,
+  metadata: ?RustModule.WalletV4.AuxiliaryData,
+  otherRequiredSigners: Array<{| ...Address, ...Addressing |}> = [],
+): Promise<RustModule.WalletV4.Transaction> {
+  const withSigning = asGetSigningKey(wallet);
+  if (withSigning == null) {
+    throw new Error('unexpected missing asGetSigningKey result');
+  }
+  const signingKey = await withSigning.getSigningKey();
+  const normalizedKey = await withSigning.normalizeKey({
+    ...signingKey,
+    password,
+  });
+  const withStakingKey = asGetAllAccounting(withSigning);
+  if (withStakingKey == null) {
+    throw new Error('unexpected missing asGetAllAcccounting result');
+  }
+  const stakingKey = await genOwnStakingKey({
+    publicDeriver: withStakingKey,
+    password,
+  });
+  return signTransaction(
+    senderUtxos,
+    unsignedTx,
+    wallet.getParent().getPublicDeriverLevel(),
+    RustModule.WalletV4.Bip32PrivateKey.from_hex(normalizedKey.prvKeyHex),
+    stakingKey,
+    metadata,
+    otherRequiredSigners,
+  )
+}
+
 export function signTransaction(
path: packages/yoroi-extension/app/api/ada/transactions/shelley/transactions.js
user: login: yushih
body: This function is no longer used by other modules now that there is `signTransactionFromWallet`  so maybe remove the `export` ?
created_at: 2025-05-22T03:47:06Z
author_association: COLLABORATOR
diff_hunk: @@ -1084,6 +1088,46 @@ async function newAdaUnsignedTxFromUtxoForConnector(
 
 type UtxoOrAddressing = CardanoAddressedUtxo | {| ...Address, ...Addressing |};
 
+export async function signTransactionFromWallet(
+  senderUtxos: Array<CardanoAddressedUtxo>,
+  unsignedTx: RustModule.WalletV4.Transaction |
+    RustModule.WalletV4.TransactionBuilder |
+    RustModule.WalletV4.TransactionBody |
+    Buffer |
+    Uint8Array,
+  wallet: PublicDeriver<>,
+  password: string,
+  metadata: ?RustModule.WalletV4.AuxiliaryData,
+  otherRequiredSigners: Array<{| ...Address, ...Addressing |}> = [],
+): Promise<RustModule.WalletV4.Transaction> {
+  const withSigning = asGetSigningKey(wallet);
+  if (withSigning == null) {
+    throw new Error('unexpected missing asGetSigningKey result');
+  }
+  const signingKey = await withSigning.getSigningKey();
+  const normalizedKey = await withSigning.normalizeKey({
+    ...signingKey,
+    password,
+  });
+  const withStakingKey = asGetAllAccounting(withSigning);
+  if (withStakingKey == null) {
+    throw new Error('unexpected missing asGetAllAcccounting result');
+  }
+  const stakingKey = await genOwnStakingKey({
+    publicDeriver: withStakingKey,
+    password,
+  });
+  return signTransaction(
+    senderUtxos,
+    unsignedTx,
+    wallet.getParent().getPublicDeriverLevel(),
+    RustModule.WalletV4.Bip32PrivateKey.from_hex(normalizedKey.prvKeyHex),
+    stakingKey,
+    metadata,
+    otherRequiredSigners,
+  )
+}
+
 export function signTransaction(
path: packages/yoroi-extension/app/api/ada/transactions/shelley/transactions.js
user: login: zuzunker
body: @yushih , it's actually used directly in one place for signing transfer from Byron addresses txs, so the same kind of the wallet is not available for those inputs and calling `signFromWallet` would not work there. The generic signing is compatible and it's just passing null for staking key so that part is ignored.
created_at: 2025-05-22T08:06:46Z
author_association: MEMBER

PR comments:
user: login: sonarqube-yoroi[bot]
created_at: 2025-05-21T16:39:44Z
body: ## [![Quality Gate passed](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarqube.emurgornd.com/dashboard?id=Emurgo_yoroi-frontend_AYXkHIRHWigj-SXq5ncX&pullRequest=4008) **Quality Gate passed**  
Issues  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://sonarqube.emurgornd.com/project/issues?id=Emurgo_yoroi-frontend_AYXkHIRHWigj-SXq5ncX&pullRequest=4008&resolved=false) [2 New issues](https://sonarqube.emurgornd.com/project/issues?id=Emurgo_yoroi-frontend_AYXkHIRHWigj-SXq5ncX&pullRequest=4008&resolved=false)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/accepted-16px.png '')](https://sonarqube.emurgornd.com/project/issues?id=Emurgo_yoroi-frontend_AYXkHIRHWigj-SXq5ncX&pullRequest=4008&issueStatuses=ACCEPTED) [0 Accepted issues](https://sonarqube.emurgornd.com/project/issues?id=Emurgo_yoroi-frontend_AYXkHIRHWigj-SXq5ncX&pullRequest=4008&issueStatuses=ACCEPTED)

Measures  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://sonarqube.emurgornd.com/security_hotspots?id=Emurgo_yoroi-frontend_AYXkHIRHWigj-SXq5ncX&pullRequest=4008) [0 Security Hotspots](https://sonarqube.emurgornd.com/security_hotspots?id=Emurgo_yoroi-frontend_AYXkHIRHWigj-SXq5ncX&pullRequest=4008)  
![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/no-data-16px.png '') No data about Coverage  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://sonarqube.emurgornd.com/component_measures?id=Emurgo_yoroi-frontend_AYXkHIRHWigj-SXq5ncX&pullRequest=4008&metric=new_duplicated_lines_density&view=list) [0.0% Duplication on New Code](https://sonarqube.emurgornd.com/component_measures?id=Emurgo_yoroi-frontend_AYXkHIRHWigj-SXq5ncX&pullRequest=4008&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube](https://sonarqube.emurgornd.com/dashboard?id=Emurgo_yoroi-frontend_AYXkHIRHWigj-SXq5ncX&pullRequest=4008)


author_association: NONE