diff --git a/client/PublicRequestTypes.ts b/client/PublicRequestTypes.ts index 6027026b..7ab83e43 100644 --- a/client/PublicRequestTypes.ts +++ b/client/PublicRequestTypes.ts @@ -92,17 +92,10 @@ export interface SignTransactionRequest extends BasicRequest { validityStartHeight: number; // FIXME To be made optional when hub has its own network } -export interface SignStakingRequest extends SignTransactionRequest { - type: number; - - // For createStaker and updateStaker transactions - delegation?: string; - - // For updateStaker transactions - reactivateAllStake?: boolean; - - // For inactivateStake transactions - newInactiveBalance?: number; +export interface SignStakingRequest extends BasicRequest { + senderLabel?: string; + recipientLabel?: string; + transaction: Uint8Array; } export interface NimiqCheckoutRequest extends BasicRequest { @@ -225,6 +218,7 @@ export interface MultiCurrencyCheckoutRequest extends BasicRequest { export type CheckoutRequest = NimiqCheckoutRequest | MultiCurrencyCheckoutRequest; export interface SignedTransaction { + transaction: Uint8Array; serializedTx: string; // HEX hash: string; // HEX diff --git a/client/dist/src/PublicRequestTypes.d.ts b/client/dist/src/PublicRequestTypes.d.ts index c03cc924..94673179 100644 --- a/client/dist/src/PublicRequestTypes.d.ts +++ b/client/dist/src/PublicRequestTypes.d.ts @@ -71,11 +71,10 @@ export interface SignTransactionRequest extends BasicRequest { flags?: number; validityStartHeight: number; } -export interface SignStakingRequest extends SignTransactionRequest { - type: number; - delegation?: string; - reactivateAllStake?: boolean; - newInactiveBalance?: number; +export interface SignStakingRequest extends BasicRequest { + senderLabel?: string; + recipientLabel?: string; + transaction: Uint8Array; } export interface NimiqCheckoutRequest extends BasicRequest { version?: 1; @@ -175,6 +174,7 @@ export interface MultiCurrencyCheckoutRequest extends BasicRequest { } export declare type CheckoutRequest = NimiqCheckoutRequest | MultiCurrencyCheckoutRequest; export interface SignedTransaction { + transaction: Uint8Array; serializedTx: string; hash: string; raw: { diff --git a/package.json b/package.json index 039045e1..28888623 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "@nimiq/electrum-client": "https://github.com/nimiq/electrum-client#build", "@nimiq/fastspot-api": "^1.7.0", "@nimiq/iqons": "^1.5.2", - "@nimiq/keyguard-client": "https://gitpkg.vercel.app/nimiq/keyguard?scripts.postinstall=cd%20client%20%26%26%20.%2Fbuild-gitpkg.sh&fb889616bb9221792a6cb2b0194a33cb0835da96", + "@nimiq/keyguard-client": "https://gitpkg.vercel.app/nimiq/keyguard?scripts.postinstall=cd%20client%20%26%26%20.%2Fbuild-gitpkg.sh&2bac12200d370a4c2a64684597ae9b726a3d83f9", "@nimiq/ledger-api": "^2.3.0", "@nimiq/network-client": "^0.6.2", "@nimiq/oasis-api": "^1.1.1", diff --git a/src/components/Network.vue b/src/components/Network.vue index f2383f75..a12bcad0 100644 --- a/src/components/Network.vue +++ b/src/components/Network.vue @@ -122,6 +122,7 @@ class Network extends Vue { : (() => { throw new Error('Unsupported transaction proof'); })(); const result: SignedTransaction = { + transaction: tx.serialize(), serializedTx: Nimiq.BufferUtils.toHex(tx.serialize()), hash: tx.hash().toHex(), diff --git a/src/i18n/en.po b/src/i18n/en.po index 3e42f54d..0ebb1676 100644 --- a/src/i18n/en.po +++ b/src/i18n/en.po @@ -113,7 +113,6 @@ msgid "Atomic swaps require two BTC transactions." msgstr "" #: src/views/CashlinkManage.vue:192 -#: src/views/CheckoutTransmission.vue:77 msgid "Awaiting receipt confirmation..." msgstr "" @@ -399,7 +398,7 @@ msgid "Connecting to Keyguard..." msgstr "" #: src/views/CashlinkManage.vue:133 -#: src/views/CheckoutTransmission.vue:36 +#: src/views/CheckoutTransmission.vue:42 #: src/views/LoginSuccess.vue:44 msgid "Connecting to network..." msgstr "" @@ -411,7 +410,7 @@ msgstr "" #: src/components/CheckoutCardNimiq.vue:283 #: src/components/CheckoutCardNimiq.vue:288 #: src/views/CashlinkManage.vue:186 -#: src/views/CheckoutTransmission.vue:71 +#: src/views/CheckoutTransmission.vue:120 msgid "Contacting seed nodes..." msgstr "" @@ -803,7 +802,7 @@ msgstr "" msgid "Payment successful" msgstr "" -#: src/views/CheckoutTransmission.vue:82 +#: src/views/CheckoutTransmission.vue:128 msgid "Payment successful." msgstr "" @@ -843,7 +842,7 @@ msgstr "" msgid "Preparing Swap" msgstr "" -#: src/views/CheckoutTransmission.vue:84 +#: src/views/CheckoutTransmission.vue:130 msgid "Processing your payment" msgstr "" @@ -926,7 +925,7 @@ msgid "Sending Transaction" msgstr "" #: src/views/CashlinkManage.vue:190 -#: src/views/CheckoutTransmission.vue:75 +#: src/views/CheckoutTransmission.vue:122 msgid "Sending transaction..." msgstr "" @@ -951,7 +950,7 @@ msgid "Single Accounts" msgstr "" #: src/views/CashlinkManage.vue:228 -#: src/views/CheckoutTransmission.vue:83 +#: src/views/CheckoutTransmission.vue:129 msgid "Something went wrong" msgstr "" @@ -967,7 +966,8 @@ msgstr "" msgid "Speed up your transaction" msgstr "" -#: src/views/SignStaking.vue:38 +#: src/views/SignStaking.vue:41 +#: src/views/SignStaking.vue:57 msgid "Staking Contract" msgstr "" @@ -1003,7 +1003,7 @@ msgstr "" #: src/components/CheckoutCardNimiq.vue:290 #: src/views/AddVestingContract.vue:11 #: src/views/CashlinkManage.vue:188 -#: src/views/CheckoutTransmission.vue:73 +#: src/views/CheckoutTransmission.vue:121 msgid "Syncing consensus..." msgstr "" @@ -1115,12 +1115,12 @@ msgid "Total fees" msgstr "" #: src/views/CashlinkManage.vue:211 -#: src/views/CheckoutTransmission.vue:61 +#: src/views/CheckoutTransmission.vue:111 msgid "Transaction could not be relayed" msgstr "" #: src/views/CashlinkManage.vue:209 -#: src/views/CheckoutTransmission.vue:59 +#: src/views/CheckoutTransmission.vue:109 msgid "Transaction is expired" msgstr "" diff --git a/src/lib/RequestParser.ts b/src/lib/RequestParser.ts index 0963c273..9033f457 100644 --- a/src/lib/RequestParser.ts +++ b/src/lib/RequestParser.ts @@ -61,7 +61,6 @@ export class RequestParser { switch (requestType) { case RequestType.SIGN_TRANSACTION: - case RequestType.SIGN_STAKING: const signTransactionRequest = request as SignTransactionRequest; if (!signTransactionRequest.value) throw new Error('value is required'); @@ -81,14 +80,25 @@ export class RequestParser { : signTransactionRequest.extraData || new Uint8Array(0), flags: signTransactionRequest.flags || Nimiq.Transaction.Flag.NONE, validityStartHeight: signTransactionRequest.validityStartHeight, - - ...(requestType === RequestType.SIGN_STAKING ? { - type: (signTransactionRequest as any as SignStakingRequest).type, - delegation: (signTransactionRequest as any as SignStakingRequest).delegation, - reactivateAllStake: (signTransactionRequest as any as SignStakingRequest).reactivateAllStake, - newInactiveBalance: (signTransactionRequest as any as SignStakingRequest).newInactiveBalance, - } : {}), } as ParsedSignTransactionRequest; + case RequestType.SIGN_STAKING: + const signStakingRequest = request as SignStakingRequest; + + // const Albatross = await window.loadAlbatross(); + // const transaction = Albatross.Transaction.fromAny( + // Nimiq.BufferUtils.toHex(signStakingRequest.transaction), + // ); + + if (!(signStakingRequest.transaction instanceof Uint8Array)) { + throw new Error('transaction must be a Uint8Array'); + } + + return { + senderLabel: signStakingRequest.senderLabel, + recipientLabel: signStakingRequest.recipientLabel, + // transaction: transaction.toPlain(), + transaction: signStakingRequest.transaction, + } as ParsedSignStakingRequest; case RequestType.CHECKOUT: const checkoutRequest = request as CheckoutRequest; @@ -707,7 +717,6 @@ export class RequestParser { : RpcRequest | null { switch (request.kind) { case RequestType.SIGN_TRANSACTION: - case RequestType.SIGN_STAKING: const signTransactionRequest = request as ParsedSignTransactionRequest; return { appName: signTransactionRequest.appName, @@ -723,16 +732,19 @@ export class RequestParser { extraData: signTransactionRequest.data, flags: signTransactionRequest.flags, validityStartHeight: signTransactionRequest.validityStartHeight, - - ...(request.kind === RequestType.SIGN_STAKING ? { - type: (signTransactionRequest as any as ParsedSignStakingRequest).type, - delegation: (signTransactionRequest as any as ParsedSignStakingRequest).delegation, - reactivateAllStake: (signTransactionRequest as any as ParsedSignStakingRequest) - .reactivateAllStake, - newInactiveBalance: (signTransactionRequest as any as ParsedSignStakingRequest) - .newInactiveBalance, - } : {}), } as SignTransactionRequest; + case RequestType.SIGN_STAKING: + const signStakingRequest = request as ParsedSignStakingRequest; + + // const Albatross = await window.loadAlbatross(); + // const transaction = Albatross.Transaction.fromPlain(signStakingRequest.transaction); + + return { + senderLabel: signStakingRequest.senderLabel, + recipientLabel: signStakingRequest.recipientLabel, + // transaction: transaction.serialize(), + transaction: signStakingRequest.transaction, + } as SignStakingRequest; case RequestType.CREATE_CASHLINK: const createCashlinkRequest = request as ParsedCreateCashlinkRequest; // Note that there is no need to export autoTruncateMessage as the message already got truncated diff --git a/src/lib/RequestTypes.ts b/src/lib/RequestTypes.ts index 2e34f7ad..876c7623 100644 --- a/src/lib/RequestTypes.ts +++ b/src/lib/RequestTypes.ts @@ -6,6 +6,7 @@ import type { ParsedEtherSpecifics, ParsedEtherDirectPaymentOptions } from './pa import type { ParsedBitcoinSpecifics, ParsedBitcoinDirectPaymentOptions } from './paymentOptions/BitcoinPaymentOptions'; import type { SwapAsset } from '@nimiq/fastspot-api'; import type { FiatApiSupportedFiatCurrency } from '@nimiq/utils'; +// import type { PlainTransaction as AlbatrossPlainTransaction } from '@nimiq/albatross-wasm'; export interface ParsedBasicRequest { kind: RequestType; @@ -51,11 +52,11 @@ export interface ParsedSignTransactionRequest extends ParsedBasicRequest { validityStartHeight: number; // FIXME To be made optional when hub has its own network } -export interface ParsedSignStakingRequest extends ParsedSignTransactionRequest { - type: number; - delegation?: string; - reactivateAllStake?: boolean; - newInactiveBalance?: number; +export interface ParsedSignStakingRequest extends ParsedBasicRequest { + senderLabel?: string; + recipientLabel?: string; + // transaction: AlbatrossPlainTransaction; + transaction: Uint8Array; } export type ParsedProtocolSpecificsForCurrency = diff --git a/src/lib/RpcApi.ts b/src/lib/RpcApi.ts index fa1c1206..48e450a6 100644 --- a/src/lib/RpcApi.ts +++ b/src/lib/RpcApi.ts @@ -318,17 +318,27 @@ export default class RpcApi { accountRequired = true; account = await WalletStore.Instance.get((request as ParsedSimpleRequest).walletId); errorMsg = 'AccountId not found'; - } else if (requestType === RequestType.SIGN_TRANSACTION || requestType === RequestType.SIGN_STAKING) { + } else if (requestType === RequestType.SIGN_TRANSACTION) { accountRequired = true; const parsedSignTransactionRequest = request as ParsedSignTransactionRequest; - const isUnstaking = requestType === RequestType.SIGN_STAKING - && (request as ParsedSignStakingRequest).type === StakingTransactionType.UNSTAKE; - const address = isUnstaking - ? parsedSignTransactionRequest.recipient - : parsedSignTransactionRequest.sender instanceof Nimiq.Address - ? parsedSignTransactionRequest.sender - : parsedSignTransactionRequest.sender.address; + const address = parsedSignTransactionRequest.sender instanceof Nimiq.Address + ? parsedSignTransactionRequest.sender + : parsedSignTransactionRequest.sender.address; account = this._store.getters.findWalletByAddress(address.toUserFriendlyAddress(), true); + } else if (requestType === RequestType.SIGN_STAKING) { + accountRequired = false; + + // TODO + // The RequestParser is not async, so we cannot load and parse the transation from its + // Uint8Array representation there, so the parsed request cannot contain the plain transaction, + // and we have to do the parsing and validation in SignStaking.vue instead. + + // const parsedSignStakingRequest = request as ParsedSignStakingRequest; + // // Only support signing staking transactions by the tx's sender or recipient + // const address = parsedSignStakingRequest.transaction.senderType === 'staking' + // ? parsedSignStakingRequest.transaction.recipient + // : parsedSignStakingRequest.transaction.sender; + // account = this._store.getters.findWalletByAddress(address, true); } else if (requestType === RequestType.SIGN_MESSAGE) { accountRequired = false; // Sign message allows user to select an account const address = (request as ParsedSignMessageRequest).signer; diff --git a/src/views/CheckoutTransmission.vue b/src/views/CheckoutTransmission.vue index e2e31f1b..fc65af83 100644 --- a/src/views/CheckoutTransmission.vue +++ b/src/views/CheckoutTransmission.vue @@ -78,6 +78,7 @@ export default class CheckoutTransmission extends Vue { const plain = tx.toPlain(); const result: SignedTransaction = { + transaction: this.keyguardResult.serializedTx, serializedTx: hex, hash: plain.transactionHash, raw: { diff --git a/src/views/SignStaking.vue b/src/views/SignStaking.vue index 5511390a..b9f98e4a 100644 --- a/src/views/SignStaking.vue +++ b/src/views/SignStaking.vue @@ -4,10 +4,9 @@ import { Component, Vue } from 'vue-property-decorator'; import { ParsedSignStakingRequest } from '../lib/RequestTypes'; import KeyguardClient from '@nimiq/keyguard-client'; -import staticStore, { Static } from '../lib/StaticStore'; +import { Static } from '../lib/StaticStore'; import { WalletInfo } from '../lib/WalletInfo'; import { Getter } from 'vuex-class'; -import { StakingTransactionType } from '../lib/Constants'; @Component export default class SignStaking extends Vue { @@ -15,62 +14,49 @@ export default class SignStaking extends Vue { @Getter private findWalletByAddress!: (address: string, includeContracts: boolean) => WalletInfo | undefined; public async created() { - // Forward user through Hub to Keyguard + // Determine signer and forward user to Keyguard - let senderAddress: Nimiq.Address; - let senderLabel: string | undefined; - let senderType: Nimiq.Account.Type | 3 /* Staking */; + let senderLabel = this.request.senderLabel; let keyId: string; let keyPath: string; let keyLabel: string | undefined; - let recipientAddress: Nimiq.Address; - let recipientLabel: string | undefined; - let recipientType: Nimiq.Account.Type | 3 /* Staking */; + let recipientLabel = this.request.recipientLabel; - const isUnstaking = this.request.type === StakingTransactionType.UNSTAKE; + const Albatross = await window.loadAlbatross(); + const transaction = Albatross.Transaction.fromAny(Nimiq.BufferUtils.toHex(this.request.transaction)).toPlain(); - if (isUnstaking) { - // existence checked in RpcApi - const wallet = this.findWalletByAddress(this.request.recipient.toUserFriendlyAddress(), false)!; - const signer = wallet.findSignerForAddress(this.request.recipient)!; + if (transaction.senderType === 'staking') { + const signerAddress = transaction.recipient; + const wallet = this.findWalletByAddress(signerAddress, false); + const signer = wallet?.findSignerForAddress(Nimiq.Address.fromUserFriendlyAddress(signerAddress)); + if (!wallet || !signer) throw new Error('Signer not found'); + + if (transaction.recipientType !== 'basic') { + throw new Error('Recipient must be a basic account when sender is staking contract'); + } - senderAddress = this.request.sender as Nimiq.Address; - senderLabel = this.$t('Staking Contract') as string; - senderType = 3; // Staking keyId = wallet.keyId; keyPath = signer.path; keyLabel = wallet.labelForKeyguard; - recipientAddress = signer.address; + senderLabel = this.request.senderLabel || this.$t('Staking Contract') as string; recipientLabel = signer.label; - recipientType = Nimiq.Account.Type.BASIC; - } else { - if (this.request.sender instanceof Nimiq.Address) { - // existence checked in RpcApi - const wallet = this.findWalletByAddress(this.request.sender.toUserFriendlyAddress(), false)!; - const signer = wallet.findSignerForAddress(this.request.sender)!; + } else if (transaction.recipientType === 'staking') { + const signerAddress = transaction.sender; + const wallet = this.findWalletByAddress(signerAddress, false); + const signer = wallet?.findSignerForAddress(Nimiq.Address.fromUserFriendlyAddress(signerAddress)); + if (!wallet || !signer) throw new Error('Signer not found'); - senderAddress = this.request.sender; - senderLabel = signer.label; - senderType = Nimiq.Account.Type.BASIC; - keyId = wallet.keyId; - keyPath = signer.path; - keyLabel = wallet.labelForKeyguard; - } else { - ({ - address: senderAddress, - label: senderLabel, - type: senderType, - signerKeyId: keyId, - signerKeyPath: keyPath, - walletLabel: keyLabel, - } = { - type: Nimiq.Account.Type.BASIC, - ...this.request.sender, - }); + if (transaction.senderType !== 'basic') { + throw new Error('Sender must be a basic account when recipient is staking contract'); } - recipientAddress = this.request.recipient; - recipientLabel = this.request.recipientLabel; - recipientType = this.request.recipientType; + + keyId = wallet.keyId; + keyPath = signer.path; + keyLabel = wallet.labelForKeyguard; + senderLabel = signer.label; + recipientLabel = this.request.recipientLabel || this.$t('Staking Contract') as string; + } else { + throw new Error('Sender or recipient must be the staking contract'); } const request: KeyguardClient.SignStakingRequest = { @@ -80,26 +66,12 @@ export default class SignStaking extends Vue { keyPath, keyLabel, - sender: senderAddress.serialize(), senderLabel, - senderType, - recipient: recipientAddress.serialize(), - recipientType, recipientLabel, - value: this.request.value, - fee: this.request.fee, - validityStartHeight: this.request.validityStartHeight, - data: this.request.data, - flags: this.request.flags, - type: this.request.type, - delegation: this.request.delegation, - reactivateAllStake: this.request.reactivateAllStake, - newInactiveBalance: this.request.newInactiveBalance, + transaction: Albatross.Transaction.fromPlain(transaction).serialize(), }; - staticStore.keyguardRequest = request; - const client = this.$rpc.createKeyguardClient(true); client.signStaking(request); } diff --git a/src/views/SignStakingSuccess.vue b/src/views/SignStakingSuccess.vue index 2047f6b4..7f3c57e5 100644 --- a/src/views/SignStakingSuccess.vue +++ b/src/views/SignStakingSuccess.vue @@ -10,13 +10,14 @@ export default class SignStakingSuccess extends Vue { @State private keyguardResult!: KeyguardClient.SignStakingResult; private async mounted() { - const { Transaction } = await window.loadAlbatross(); + const Albatross = await window.loadAlbatross(); - const hex = bytesToHex(this.keyguardResult.serializedTx); - const tx = Transaction.fromAny(hex); + const hex = bytesToHex(this.keyguardResult.transaction); + const tx = Albatross.Transaction.fromAny(hex); const plain = tx.toPlain(); const result: SignedTransaction = { + transaction: this.keyguardResult.transaction, serializedTx: hex, hash: plain.transactionHash, raw: { @@ -26,14 +27,10 @@ export default class SignStakingSuccess extends Vue { proof: tx.proof, signerPublicKey: 'publicKey' in plain.proof ? hexToBytes(plain.proof.publicKey) - : 'creatorPublicKey' in plain.proof - ? hexToBytes(plain.proof.creatorPublicKey) - : new Uint8Array(0), + : new Uint8Array(0), signature: 'signature' in plain.proof ? hexToBytes(plain.proof.signature) - : 'creatorSignature' in plain.proof - ? hexToBytes(plain.proof.creatorSignature) - : new Uint8Array(0), + : new Uint8Array(0), extraData: tx.data, networkId: tx.networkId, }, diff --git a/src/views/SignTransactionSuccess.vue b/src/views/SignTransactionSuccess.vue index 570f0833..69e0ecf4 100644 --- a/src/views/SignTransactionSuccess.vue +++ b/src/views/SignTransactionSuccess.vue @@ -17,6 +17,7 @@ export default class SignTransactionSuccess extends Vue { const plain = tx.toPlain(); const result: SignedTransaction = { + transaction: this.keyguardResult.serializedTx, serializedTx: hex, hash: plain.transactionHash, raw: { diff --git a/yarn.lock b/yarn.lock index faf98078..aaaaf708 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1508,9 +1508,9 @@ btoa "^1.1.2" node-lmdb "^0.9.6" -"@nimiq/keyguard-client@https://gitpkg.vercel.app/nimiq/keyguard?scripts.postinstall=cd%20client%20%26%26%20.%2Fbuild-gitpkg.sh&fb889616bb9221792a6cb2b0194a33cb0835da96": +"@nimiq/keyguard-client@https://gitpkg.vercel.app/nimiq/keyguard?scripts.postinstall=cd%20client%20%26%26%20.%2Fbuild-gitpkg.sh&2bac12200d370a4c2a64684597ae9b726a3d83f9": version "1.0.0" - resolved "https://gitpkg.vercel.app/nimiq/keyguard?scripts.postinstall=cd%20client%20%26%26%20.%2Fbuild-gitpkg.sh&fb889616bb9221792a6cb2b0194a33cb0835da96#a016613e0ec5248a1a10acfbaa0147e6a42486d8" + resolved "https://gitpkg.vercel.app/nimiq/keyguard?scripts.postinstall=cd%20client%20%26%26%20.%2Fbuild-gitpkg.sh&2bac12200d370a4c2a64684597ae9b726a3d83f9#36a2e50d81b6d92174ef3ff84bba57ab8e48b6ea" "@nimiq/ledger-api@^2.3.0": version "2.3.0"