diff --git a/packages/yoroi-extension/app/api/ada/index.js b/packages/yoroi-extension/app/api/ada/index.js index b16413d348..61f4192f37 100644 --- a/packages/yoroi-extension/app/api/ada/index.js +++ b/packages/yoroi-extension/app/api/ada/index.js @@ -374,12 +374,24 @@ export type CreateDelegationTxRequest = {| valueInAccount: MultiToken, |}; -export type CreateVotingRegTxRequest = {| +type CreateVotingRegTxRequestCommon = {| publicDeriver: IPublicDeriver & IGetAllUtxos & IHasUtxoChains, absSlotNumber: BigNumber, - metadata: RustModule.WalletV4.GeneralTransactionMetadata, |}; +export type CreateVotingRegTxRequest = {| + ...CreateVotingRegTxRequestCommon, + normalWallet: {| + metadata: RustModule.WalletV4.GeneralTransactionMetadata, + |} +|} | {| + ...CreateVotingRegTxRequestCommon, + trezorTWallet: {| + votingPublicKey: string, + |} +|}; + + export type CreateDelegationTxResponse = {| signTxRequest: HaskellShelleyTxSignRequest, totalAmountToDelegate: MultiToken, @@ -849,7 +861,6 @@ export default class AdaApi { Number.parseInt(config.ChainNetworkId, 10), ); Logger.debug(`${nameof(AdaApi)}::${nameof(this.createTrezorSignTxData)} success: ` + stringifyData(trezorSignTxPayload)); - return { trezorSignTxPayload, }; @@ -1400,7 +1411,16 @@ export default class AdaApi { if (changeAddr == null) { throw new Error(`${nameof(this.createVotingRegTx)} no internal addresses left. Should never happen`); } - const trxMetadata = RustModule.WalletV4.TransactionMetadata.new(request.metadata); + let trxMetadata; + if (request.trezorTWallet) { + trxMetadata = undefined; + } else { + // Mnemonic wallet + trxMetadata = RustModule.WalletV4.TransactionMetadata.new( + request.normalWallet.metadata + ); + } + const unsignedTx = shelleyNewAdaUnsignedTx( [], { @@ -1431,6 +1451,12 @@ export default class AdaApi { neededHashes: new Set(), wits: new Set(), }, + trezorTCatalystRegistrationTxSignData: + request.trezorTWallet && + { + publicVotingKey: request.trezorTWallet.publicVotingKey, + nonce: request.absSlotNumber, + }, }); } catch (error) { Logger.error(`${nameof(AdaApi)}::${nameof(this.createVotingRegTx)} error: ` + stringifyError(error)); 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 119286e58f..7d645919f4 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/HaskellShelleyTxSignRequest.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/HaskellShelleyTxSignRequest.js @@ -45,6 +45,9 @@ implements ISignRequest { neededHashes: Set, // StakeCredential wits: Set, // Vkeywitness |}; + trezorTCatalystRegistrationTxSignData: void | {| + votingPublicKey: string, + |}; constructor(data: {| senderUtxos: Array, @@ -56,6 +59,9 @@ implements ISignRequest { neededHashes: Set, // StakeCredential wits: Set, // Vkeywitness |}, + trezorTCatalystRegistrationTxSignData: void | {| + votingPublicKey: string, + |}; |}) { this.senderUtxos = data.senderUtxos; this.unsignedTx = data.unsignedTx; @@ -63,6 +69,8 @@ implements ISignRequest { this.metadata = data.metadata; this.networkSettingSnapshot = data.networkSettingSnapshot; this.neededStakingKeyHashes = data.neededStakingKeyHashes; + this.trezorTCatalystRegistrationTxSignData = + data.trezorTCatalystRegistrationTxSignData; } txId(): string { diff --git a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js index 6bcedcbc3f..972d2c1eea 100644 --- a/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js +++ b/packages/yoroi-extension/app/api/ada/transactions/shelley/trezorTx.js @@ -111,6 +111,20 @@ export async function createTrezorSignTxPayload( metadata: Buffer.from(metadata.to_bytes()).toString('hex') }; + if (signRequest.trezorTCatalystRegistrationTxSignData) { + request.auxiliaryData = { + catalystRegistrationParameters: { + votingPublicKey: signRequest.trezorTCatalystRegistrationTxSignData.votingPublicKey, + stakingPath: getStakingKeyPath(), + rewardAddressParameters: { + addressType: ADDRESS_TYPE.Reward, + stakingPath: getStakingKeyPath(), + }, + nonce: signRequest.trezorTCatalystRegistrationTxSignData.nonce, + }, + }; + } + return request; } diff --git a/packages/yoroi-extension/app/containers/wallet/voting/VotingPage.js b/packages/yoroi-extension/app/containers/wallet/voting/VotingPage.js index c0d8b39e29..105d473f2d 100644 --- a/packages/yoroi-extension/app/containers/wallet/voting/VotingPage.js +++ b/packages/yoroi-extension/app/containers/wallet/voting/VotingPage.js @@ -10,8 +10,6 @@ import Voting from '../../../components/wallet/voting/Voting'; import VotingRegistrationDialogContainer from '../dialogs/voting/VotingRegistrationDialogContainer'; import type { GeneratedData as VotingRegistrationDialogContainerData } from '../dialogs/voting/VotingRegistrationDialogContainer'; import { handleExternalLinkClick } from '../../../utils/routing'; -import { WalletTypeOption, } from '../../../api/ada/lib/storage/models/ConceptualWallet/interfaces'; -import UnsupportedWallet from '../UnsupportedWallet'; import { PublicDeriver } from '../../../api/ada/lib/storage/models/PublicDeriver/index'; import LoadingSpinner from '../../../components/widgets/LoadingSpinner'; import VerticallyCenteredLayout from '../../../components/layout/VerticallyCenteredLayout'; @@ -85,13 +83,6 @@ export default class VotingPage extends Component { } = this.generated.stores; let activeDialog = null; - if(selected == null){ - throw new Error(`${nameof(VotingPage)} no wallet selected`); - } - if (selected.getParent().getWalletType() === WalletTypeOption.HARDWARE_WALLET) { - return ; - } - const balance = this.generated.balance; if (balance == null) { return ( diff --git a/packages/yoroi-extension/app/stores/ada/VotingStore.js b/packages/yoroi-extension/app/stores/ada/VotingStore.js index a333b9c5fc..3d0d64b1e0 100644 --- a/packages/yoroi-extension/app/stores/ada/VotingStore.js +++ b/packages/yoroi-extension/app/stores/ada/VotingStore.js @@ -24,6 +24,7 @@ import { isLedgerNanoWallet, isTrezorTWallet, } from '../../api/ada/lib/storage/models/ConceptualWallet/index'; +import { WalletTypeOption } from '../../api/ada/lib/storage/models/ConceptualWallet/interfaces'; import { genOwnStakingKey } from '../../api/ada/index'; import { RustModule } from '../../api/ada/lib/cardanoCrypto/rustLoader'; import type { StepStateEnum } from '../../components/widgets/ProgressSteps'; @@ -128,9 +129,24 @@ export default class VotingStore extends Store { this.progressInfo.stepState = StepState.LOAD; }; - @action _submitConfirm: void => void = () => { - this.progressInfo.currentStep = ProgressStep.REGISTER; - this.progressInfo.stepState = StepState.LOAD; + @action _submitConfirm: void => void = async () => { + const selected = this.stores.wallets.selected; + if (!selected) { + throw new Error(`${nameof(this._submitConfirm)} no public deriver. Should never happen`); + } + let nextStep; + if ( + selected.getParent().getWalletType() === WalletTypeOption.HARDWARE_WALLET + ) { + await this.actions.ada.voting.createTransaction.trigger(); + nextStep = ProgressStep.TRANSACTION; + } else { + nextStep = ProgressStep.REGISTER; + } + runInAction(() => { + this.progressInfo.currentStep = nextStep; + this.progressInfo.stepState = StepState.LOAD; + }) }; @action _submitConfirmError: void => void = () => { @@ -167,61 +183,83 @@ export default class VotingStore extends Store { this.progressInfo.stepState = StepState.ERROR; }; - // we need password for transaction building to sign the voting key with stake key - // as part of metadata + // For mnemonic wallet, we need password for transaction building to sign + // the voting key with stake key as part of metadata. @action - _createTransaction: string => Promise = async spendingPassword => { + _createTransaction: ?string => Promise = async spendingPassword => { this.progressInfo.stepState = StepState.PROCESS; const publicDeriver = this.stores.wallets.selected; if (!publicDeriver) { return; } - const withSigning = asGetSigningKey(publicDeriver); - if (withSigning == null) { + const withUtxos = asGetAllUtxos(publicDeriver); + if (withUtxos == null) { + throw new Error(`${nameof(this._createTransaction)} missing utxo functionality`); + } + + const network = withUtxos.getParent().getNetworkInfo(); + if (!isCardanoHaskell(network)) { throw new Error( - `${nameof(this._createTransaction)} public deriver missing signing functionality.` + `${nameof(VotingStore)}::${nameof(this._createTransaction)} network not supported` ); } - const withStakingKey = asGetAllAccounting(withSigning); - if (withStakingKey == null) { - throw new Error(`${nameof(this._createTransaction)} missing staking key functionality`); + + const withHasUtxoChains = asHasUtxoChains(withUtxos); + if (withHasUtxoChains == null) { + throw new Error(`${nameof(this._createTransaction)} missing chains functionality`); } - const stakingKey = await genOwnStakingKey({ - publicDeriver: withStakingKey, - password: spendingPassword, - }); + const fullConfig = getCardanoHaskellBaseConfig( publicDeriver.getParent().getNetworkInfo() ); - const config = fullConfig.reduce((acc, next) => Object.assign(acc, next), {}); - const rewardAddress = RustModule.WalletV4.RewardAddress.new( - Number.parseInt(config.ChainNetworkId, 10), - RustModule.WalletV4.StakeCredential.from_keyhash(stakingKey.to_public().hash()), + + const timeToSlot = await genTimeToSlot(fullConfig); + const absSlotNumber = new BigNumber( + timeToSlot({ + // use server time for TTL if connected to server + time: this.stores.serverConnectionStore.serverTime ?? new Date(), + }).slot ); - const withUtxos = asGetAllUtxos(publicDeriver); - if (withUtxos == null) { - throw new Error(`${nameof(this._createTransaction)} missing utxo functionality`); + if(this.catalystPrivateKey === undefined){ + throw new Error(`${nameof(this._createTransaction)} should never happen`); } - const network = withUtxos.getParent().getNetworkInfo(); - if (isCardanoHaskell(network)) { - const withHasUtxoChains = asHasUtxoChains(withUtxos); - if (withHasUtxoChains == null) { - throw new Error(`${nameof(this._createTransaction)} missing chains functionality`); - } - const timeToSlot = await genTimeToSlot(fullConfig); - const absSlotNumber = new BigNumber( - timeToSlot({ - // use server time for TTL if connected to server - time: this.stores.serverConnectionStore.serverTime ?? new Date(), - }).slot - ); + let votingRegTxPromise; + + if (isTrezorTWallet(publicDeriver.getParent())) { + const votingPublicKey = `0x${Buffer.from(this.catalystPrivateKey.to_public().as_bytes()).toString('hex')}`; - if(this.catalystPrivateKey === undefined){ - throw new Error(`${nameof(this._createTransaction)} should never happen`); + votingRegTxPromise = this.createVotingRegTx.execute({ + publicDeriver: withHasUtxoChains, + absSlotNumber, + trezorTWallet: { + votingPublicKey + }, + }).promise; + } else if ( + publicDeriver.getParent().getWalletType() === WalletTypeOption.WEB_WALLET + ) { + const withSigning = asGetSigningKey(publicDeriver); + if (withSigning == null) { + throw new Error( + `${nameof(this._createTransaction)} public deriver missing signing functionality.` + ); } + const withStakingKey = asGetAllAccounting(withSigning); + if (withStakingKey == null) { + throw new Error(`${nameof(this._createTransaction)} missing staking key functionality`); + } + const stakingKey = await genOwnStakingKey({ + publicDeriver: withStakingKey, + password: spendingPassword, + }); + const config = fullConfig.reduce((acc, next) => Object.assign(acc, next), {}); + const rewardAddress = RustModule.WalletV4.RewardAddress.new( + Number.parseInt(config.ChainNetworkId, 10), + RustModule.WalletV4.StakeCredential.from_keyhash(stakingKey.to_public().hash()), + ); const trxMeta = generateRegistration({ stakePrivateKey: stakingKey, @@ -233,21 +271,20 @@ export default class VotingStore extends Store { }).slot, }); - const votingRegTxPromise = this.createVotingRegTx.execute({ + votingRegTxPromise = this.createVotingRegTx.execute({ publicDeriver: withHasUtxoChains, absSlotNumber, - metadata: trxMeta, + normalWallet: { metadata: trxMeta }, }).promise; - if (votingRegTxPromise == null) { - throw new Error(`${nameof(this._createTransaction)} should never happen`); - } - await votingRegTxPromise; - this.markStale(false); } else { - throw new Error( - `${nameof(VotingStore)}::${nameof(this._createTransaction)} network not supported` - ); + throw new Error(`${nameof(this._createTransaction)} unexpected wallet type`); + } + + if (votingRegTxPromise == null) { + throw new Error(`${nameof(this._createTransaction)} should never happen`); } + await votingRegTxPromise; + this.markStale(false); }; @action