From 227e4a44a22f18c5d49ba455253da2e5e188e343 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 21 May 2024 12:31:15 +0400 Subject: [PATCH 1/5] fix: show correct prepared transaction info --- src/composables/maxAmount.ts | 100 +++++++++++++----- src/composables/multisigTransactions.ts | 19 +++- src/constants/stubs.ts | 1 + .../TransferSend/TransferReviewBase.vue | 3 +- src/popup/locales/en.json | 6 ++ .../aeternity/components/TransferReview.vue | 87 ++++++++++++--- .../aeternity/components/TransferSendForm.vue | 15 ++- src/types/index.ts | 2 + 8 files changed, 183 insertions(+), 50 deletions(-) diff --git a/src/composables/maxAmount.ts b/src/composables/maxAmount.ts index 7ded9fa2f..54ad4c944 100644 --- a/src/composables/maxAmount.ts +++ b/src/composables/maxAmount.ts @@ -18,32 +18,40 @@ import { unpackTx, } from '@aeternity/aepp-sdk'; -import type { IFormModel } from '@/types'; +import type { IFormModel, IMultisigAccount } from '@/types'; import { executeAndSetInterval, isUrlValid } from '@/utils'; import { PROTOCOLS } from '@/constants'; -import { STUB_CALL_DATA, STUB_CONTRACT_ADDRESS } from '@/constants/stubs'; +import { STUB_TIP_NOTE } from '@/constants/stubs'; import { AE_COIN_PRECISION, AE_CONTRACT_ID } from '@/protocols/aeternity/config'; import FungibleTokenFullInterfaceACI from '@/protocols/aeternity/aci/FungibleTokenFullInterfaceACI.json'; +import { aettosToAe } from '@/protocols/aeternity/helpers'; import { useAeSdk } from './aeSdk'; import { useBalances } from './balances'; import { useAccounts } from './accounts'; +import { useMultisigTransactions } from './multisigTransactions'; +import { useTippingContracts } from './tippingContracts'; export interface MaxAmountOptions { formModel: Ref; + multisigVault?: IMultisigAccount; } /** * Composable that allows to use real max amount of selected token * considering the fee that needs to be paid. */ -export function useMaxAmount({ formModel }: MaxAmountOptions) { +export function useMaxAmount({ formModel, multisigVault }: MaxAmountOptions) { const { getAeSdk } = useAeSdk(); const { balance } = useBalances(); const { getLastActiveProtocolAccount } = useAccounts(); + const { getTippingContracts } = useTippingContracts(); let updateTokenBalanceInterval: NodeJS.Timer; const fee = ref(new BigNumber(0)); + const total = ref(new BigNumber(0)); + const gasUsed = ref(0); + const gasPrice = ref(0); const selectedTokenBalance = ref(new BigNumber(0)); let tokenInstance: Contract | null; const selectedAssetDecimals = ref(0); @@ -86,33 +94,63 @@ export function useMaxAmount({ formModel }: MaxAmountOptions) { const numericAmount = (val.amount && +val.amount > 0) ? val.amount : 0; const amount = new BigNumber(numericAmount).shiftedBy(AE_COIN_PRECISION); - if ( - !isAssetAe - || ( - val.address && !isNameValid(val.address) && isUrlValid(val.address) - ) - ) { - let callData: Encoded.ContractBytearray = STUB_CALL_DATA; - if (tokenInstance) { - callData = tokenInstance._calldata.encode( - tokenInstance._name, - 'transfer', - [account.address, amount.toFixed()], + let callResult: any; + + if (!isAssetAe && tokenInstance) { + try { + callResult = await tokenInstance.transfer( + val.address ?? account.address, + amount.toFixed(), + { callStatic: true }, ); + } catch (e) { + return; } - fee.value = BigNumber(unpackTx( - await aeSdk.buildTx({ - tag: Tag.ContractCallTx, - callerId: account.address as Encoded.AccountAddress, - contractId: (isAssetAe) - ? STUB_CONTRACT_ADDRESS - : val.selectedAsset.contractId as Encoded.ContractAddress, - amount: 0, - callData, - ttl: await aeSdk.getHeight({ cached: true }) + 3, - }), - Tag.ContractCallTx, // https://github.com/aeternity/aepp-sdk-js/issues/1852 - ).fee).shiftedBy(-AE_COIN_PRECISION); + } + + if (multisigVault) { + const { proposeTx } = useMultisigTransactions(); + const builtTransactionHash = await aeSdk.buildTx({ + tag: Tag.SpendTx, + senderId: multisigVault.gaAccountId as Encoded.AccountAddress, + recipientId: account.address as Encoded.AccountAddress, + amount, + payload: encode(new TextEncoder().encode(val.payload), Encoding.Bytearray), + }); + callResult = (await proposeTx( + builtTransactionHash, + multisigVault.contractId, + { callStatic: true }, + ))?.callResult; + } + + if (val.address && !isNameValid(val.address) && isUrlValid(val.address)) { + const { tippingV1, tippingV2 } = await getTippingContracts(); + const tippingContract = tippingV2 || tippingV1; + try { + callResult = await tippingContract.tip( + val.address ?? account.address, + STUB_TIP_NOTE, + { + amount, + waitMined: false, + callStatic: true, + }, + ); + } catch (e) { + return; + } + } + + if (callResult?.result) { + const aettosFee = (callResult.tx as any).fee; + gasUsed.value = +callResult.result.gasUsed.toString() ?? 0; + gasPrice.value = +aettosToAe(callResult.result.gasPrice.toString()); + total.value = new BigNumber(callResult.result.gasUsed ?? 0) + .multipliedBy(callResult.result.gasPrice.toString() ?? 0) + .plus(aettosFee) + .shiftedBy(-AE_COIN_PRECISION); + fee.value = new BigNumber(aettosFee).shiftedBy(-AE_COIN_PRECISION); return; } @@ -132,6 +170,7 @@ export function useMaxAmount({ formModel }: MaxAmountOptions) { Tag.SpendTx, // https://github.com/aeternity/aepp-sdk-js/issues/1852 ).fee).shiftedBy(-AE_COIN_PRECISION); if (!minFee.isEqualTo(fee.value)) fee.value = minFee; + total.value = new BigNumber(amount).shiftedBy(-AE_COIN_PRECISION).plus(fee.value); }, { deep: true, @@ -152,7 +191,10 @@ export function useMaxAmount({ formModel }: MaxAmountOptions) { onBeforeUnmount(() => clearInterval(updateTokenBalanceInterval)); return { - max, fee, + gasPrice, + gasUsed, + max, + total, }; } diff --git a/src/composables/multisigTransactions.ts b/src/composables/multisigTransactions.ts index 4b1e1049a..2aefde7ed 100644 --- a/src/composables/multisigTransactions.ts +++ b/src/composables/multisigTransactions.ts @@ -104,7 +104,11 @@ export function useMultisigTransactions() { return postJson(`${aeActiveNetworkPredefinedSettings.value.multisigBackendUrl}/tx`, { body: { hash: txHash, tx } }); } - async function proposeTx(spendTx: Encoded.Transaction, contractId: Encoded.ContractAddress) { + async function proposeTx( + spendTx: Encoded.Transaction, + contractId: Encoded.ContractAddress, + options?: any, + ) { const [aeSdk, topBlockHeight] = await Promise.all([getAeSdk(), fetchCurrentTopBlockHeight()]); const expirationHeight = topBlockHeight + MULTISIG_TRANSACTION_EXPIRATION_HEIGHT; @@ -115,11 +119,16 @@ export function useMultisigTransactions() { address: contractId, }); - await gaContractRpc.propose(spendTxHash, { - FixedTTL: [expirationHeight], - }); + const callResult = await gaContractRpc.propose( + spendTxHash, + { FixedTTL: [expirationHeight] }, + options || {}, + ); - return Buffer.from(spendTxHash).toString('hex'); + return { + proposeTxHash: Buffer.from(spendTxHash).toString('hex'), + callResult, + }; } /** diff --git a/src/constants/stubs.ts b/src/constants/stubs.ts index c900387d0..501e32e01 100644 --- a/src/constants/stubs.ts +++ b/src/constants/stubs.ts @@ -37,6 +37,7 @@ export const STUB_NONCE = 10000; export const STUB_TOKEN_CONTRACT_ADDRESS = 'ct_T6MWNrowGVC9dyTDksCBrCCSaeK3hzBMMY5hhMKwvwr8wJvM8'; export const STUB_TIPPING_CONTRACT_ID_V1 = 'ct_2Cvbf3NYZ5DLoaNYAU71t67DdXLHeSXhodkSNifhgd7Xsw28Xd'; export const STUB_TIPPING_CONTRACT_ID_V2 = 'ct_2ZEoCKcqXkbz2uahRrsWeaPooZs9SdCv6pmC4kc55rD4MhqYSu'; +export const STUB_TIP_NOTE = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam vel interdum ligula, non consequat libero. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nullam congue, nibh non malesuada ornare, ante metus tempor dui, a ultrices ante ut.'; export const STUB_ACCOUNT = { mnemonic: 'media view gym mystery all fault truck target envelope kit drop fade', diff --git a/src/popup/components/TransferSend/TransferReviewBase.vue b/src/popup/components/TransferSend/TransferReviewBase.vue index 5c9879d21..33f5e8e06 100644 --- a/src/popup/components/TransferSend/TransferReviewBase.vue +++ b/src/popup/components/TransferSend/TransferReviewBase.vue @@ -65,7 +65,7 @@