diff --git a/locale/cnr/translation.toml b/locale/cnr/translation.toml index 90d2c3514..508e1f6d5 100644 --- a/locale/cnr/translation.toml +++ b/locale/cnr/translation.toml @@ -221,6 +221,7 @@ saveWalletFile = "" # Save Wallet File proposalOverBudget = "" # Over Budget badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) creatingShieldTransaction = "" # Creating SHIELD transaction... +autoShieldTransaction = "" # Auto shield transaction [ALERTS] INTERNAL_ERROR = "Interna greška, molimo pokušajte ponovo kasnije" # Internal error, please try again later diff --git a/locale/de/translation.toml b/locale/de/translation.toml index 6c4c87d87..60038ddc5 100644 --- a/locale/de/translation.toml +++ b/locale/de/translation.toml @@ -221,6 +221,7 @@ saveWalletFile = "" # Save Wallet File proposalOverBudget = "" # Over Budget badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) creatingShieldTransaction = "" # Creating SHIELD transaction... +autoShieldTransaction = "" # Auto shield transaction [ALERTS] INTERNAL_ERROR = "Interner Fehler, bitte versuche es später erneut" # Internal error, please try again later diff --git a/locale/en/translation.toml b/locale/en/translation.toml index d0956ed49..9a033a033 100644 --- a/locale/en/translation.toml +++ b/locale/en/translation.toml @@ -221,6 +221,7 @@ saveWalletFile = "Save Wallet File" # Save Wallet File proposalOverBudget = "Over Budget" # Over Budget badSaplingRoot = "There was an error while syncing. Resyncing from scratch (Bad sapling root)" # There was an error while syncing. Resyncing from scratch (Bad sapling root) creatingShieldTransaction = "Creating SHIELD transaction..." # Creating SHIELD transaction... +autoShieldTransaction = "Auto shield transaction" # Auto shield transaction [ALERTS] INTERNAL_ERROR = "Internal error, please try again later" # Internal error, please try again later diff --git a/locale/es-mx/translation.toml b/locale/es-mx/translation.toml index 8799397c0..800fc9c37 100644 --- a/locale/es-mx/translation.toml +++ b/locale/es-mx/translation.toml @@ -221,6 +221,7 @@ saveWalletFile = "" # Save Wallet File proposalOverBudget = "" # Over Budget badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) creatingShieldTransaction = "" # Creating SHIELD transaction... +autoShieldTransaction = "" # Auto shield transaction [ALERTS] INTERNAL_ERROR = "Error interno, vuelve a intentarlo más tarde" # Internal error, please try again later diff --git a/locale/fr/translation.toml b/locale/fr/translation.toml index 3ebe8c866..6b07e0ffe 100644 --- a/locale/fr/translation.toml +++ b/locale/fr/translation.toml @@ -221,6 +221,7 @@ saveWalletFile = "" # Save Wallet File proposalOverBudget = "" # Over Budget badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) creatingShieldTransaction = "" # Creating SHIELD transaction... +autoShieldTransaction = "" # Auto shield transaction [ALERTS] INTERNAL_ERROR = "Erreur interne, veuillez réessayer plus tard" # Internal error, please try again later diff --git a/locale/it/translation.toml b/locale/it/translation.toml index 4af5510b4..2fc7058db 100644 --- a/locale/it/translation.toml +++ b/locale/it/translation.toml @@ -221,6 +221,7 @@ saveWalletFile = "" # Save Wallet File proposalOverBudget = "" # Over Budget badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) creatingShieldTransaction = "" # Creating SHIELD transaction... +autoShieldTransaction = "" # Auto shield transaction [ALERTS] INTERNAL_ERROR = "Errore interno, rirova più tardi" # Internal error, please try again later diff --git a/locale/nl/translation.toml b/locale/nl/translation.toml index e70dc7836..214daba15 100644 --- a/locale/nl/translation.toml +++ b/locale/nl/translation.toml @@ -221,6 +221,7 @@ saveWalletFile = "" # Save Wallet File proposalOverBudget = "" # Over Budget badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) creatingShieldTransaction = "" # Creating SHIELD transaction... +autoShieldTransaction = "" # Auto shield transaction [ALERTS] INTERNAL_ERROR = "Interne fout, probeer het later opnieuw" # Internal error, please try again later diff --git a/locale/ph/translation.toml b/locale/ph/translation.toml index 36d2dba1f..965046197 100644 --- a/locale/ph/translation.toml +++ b/locale/ph/translation.toml @@ -221,6 +221,7 @@ saveWalletFile = "" # Save Wallet File proposalOverBudget = "" # Over Budget badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) creatingShieldTransaction = "" # Creating SHIELD transaction... +autoShieldTransaction = "" # Auto shield transaction [ALERTS] INTERNAL_ERROR = "Internal error, Pakiusap uliting muli" # Internal error, please try again later diff --git a/locale/pl/translation.toml b/locale/pl/translation.toml index 82bfc7344..a9bc631ca 100644 --- a/locale/pl/translation.toml +++ b/locale/pl/translation.toml @@ -221,6 +221,7 @@ saveWalletFile = "" # Save Wallet File proposalOverBudget = "" # Over Budget badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) creatingShieldTransaction = "" # Creating SHIELD transaction... +autoShieldTransaction = "" # Auto shield transaction [ALERTS] INTERNAL_ERROR = "Błąd wewnętrzny, spróbuj ponownie później" # Internal error, please try again later diff --git a/locale/pt-br/translation.toml b/locale/pt-br/translation.toml index b1ec19379..9d6b00072 100644 --- a/locale/pt-br/translation.toml +++ b/locale/pt-br/translation.toml @@ -88,6 +88,7 @@ saveWalletFile = "" # Save Wallet File proposalOverBudget = "" # Over Budget badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) creatingShieldTransaction = "" # Creating SHIELD transaction... +autoShieldTransaction = "" # Auto shield transaction [ALERTS] STAKE_ADDR_SET = "Endereço de Cold Staking definido!
Ao fazer Stake no futuro este endereço irá ser usado." # Cold Address set!
Future stakes will use this address. diff --git a/locale/pt-pt/translation.toml b/locale/pt-pt/translation.toml index ea6d00b9a..91e338e8b 100644 --- a/locale/pt-pt/translation.toml +++ b/locale/pt-pt/translation.toml @@ -88,6 +88,7 @@ saveWalletFile = "" # Save Wallet File proposalOverBudget = "" # Over Budget badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) creatingShieldTransaction = "" # Creating SHIELD transaction... +autoShieldTransaction = "" # Auto shield transaction [ALERTS] STAKE_ADDR_SET = "Endereço de Cold Staking definido!
Ao fazer Stake no futuro irá ser usado este endereço." # Cold Address set!
Future stakes will use this address. diff --git a/locale/template/translation.toml b/locale/template/translation.toml index cd777e5b4..39684d9b0 100644 --- a/locale/template/translation.toml +++ b/locale/template/translation.toml @@ -245,6 +245,7 @@ shieldAddress = "Shield address" cantShieldToExc = "This address does not support shield transfers" badSaplingRoot = "There was an error while syncing. Resyncing from scratch (Bad sapling root)" creatingShieldTransaction = "Creating SHIELD transaction..." +autoShieldTransaction = "Auto shield transaction" [ALERTS] INTERNAL_ERROR = "Internal error, please try again later" diff --git a/locale/uwu/translation.toml b/locale/uwu/translation.toml index 2ec8dde52..c233cf15d 100644 --- a/locale/uwu/translation.toml +++ b/locale/uwu/translation.toml @@ -221,6 +221,7 @@ saveWalletFile = "Save Wawwet File" # Save Wallet File proposalOverBudget = "Over Budgey" # Over Budget badSaplingRoot = "" # There was an error while syncing. Resyncing from scratch (Bad sapling root) creatingShieldTransaction = "" # Creating SHIELD transaction... +autoShieldTransaction = "" # Auto shield transaction [ALERTS] INTERNAL_ERROR = "Internal error, pwease try again later" # Internal error, please try again later diff --git a/scripts/composables/use_wallet.js b/scripts/composables/use_wallet.js index bceb179bb..13c89390d 100644 --- a/scripts/composables/use_wallet.js +++ b/scripts/composables/use_wallet.js @@ -73,6 +73,15 @@ export const useWallet = defineStore('wallet', () => { getEventEmitter().on('shield-loaded-from-disk', () => { hasShield.value = wallet.hasShield(); }); + const sendTransaction = async (network, tx) => { + const res = await network.sendTransaction(tx.serialize()); + if (res) { + await wallet.addTransaction(tx); + } else { + wallet.discardTransaction(tx); + } + return res; + }; const createAndSendTransaction = lockableFunction( async (network, address, value, opts) => { const tx = wallet.createTransaction(address, value, opts); @@ -81,15 +90,17 @@ export const useWallet = defineStore('wallet', () => { } else { await wallet.sign(tx); } - const res = await network.sendTransaction(tx.serialize()); - if (res) { - await wallet.addTransaction(tx); - } else { - wallet.discardTransaction(tx); - } - return res; + return await sendTransaction(network, tx); } ); + const createAuthoshieldTransactions = async (network, address, value) => { + const txs = await wallet.createAutoshieldTransactions(address, value); + let res = true; + for (const tx of txs) { + res = res && (await sendTransaction(network, tx)); + } + return res; + }; const isCreatingTransaction = () => createAndSendTransaction.isLocked(); getEventEmitter().on('toggle-network', async () => { @@ -136,5 +147,6 @@ export const useWallet = defineStore('wallet', () => { createAndSendTransaction, loadFromDisk, coldBalance, + createAuthoshieldTransactions, }; }); diff --git a/scripts/dashboard/Dashboard.vue b/scripts/dashboard/Dashboard.vue index 0779fbb3f..ea4533147 100644 --- a/scripts/dashboard/Dashboard.vue +++ b/scripts/dashboard/Dashboard.vue @@ -198,8 +198,9 @@ function lockWallet() { * Sends a transaction * @param {string} address - Address or contact to send to * @param {number} amount - Amount of PIVs to send + * @param {boolean} isAutoShield - Whether or not this is an autoshield transaction */ -async function send(address, amount, useShieldInputs) { +async function send(address, amount, useShieldInputs, isAutoShield) { // Ensure a wallet is unlocked if (wallet.isViewOnly && !wallet.isHardwareWallet) { if ( @@ -325,9 +326,22 @@ async function send(address, amount, useShieldInputs) { // Create and send the TX try { - await wallet.createAndSendTransaction(getNetwork(), address, nValue, { - useShieldInputs, - }); + if (isAutoShield) { + await wallet.createAuthoshieldTransactions( + getNetwork(), + address, + nValue + ); + } else { + await wallet.createAndSendTransaction( + getNetwork(), + address, + nValue, + { + useShieldInputs, + } + ); + } } catch (e) { console.error(e); createAlert('warning', e); diff --git a/scripts/dashboard/TransferMenu.vue b/scripts/dashboard/TransferMenu.vue index 943840073..8ab1cc026 100644 --- a/scripts/dashboard/TransferMenu.vue +++ b/scripts/dashboard/TransferMenu.vue @@ -17,8 +17,13 @@ const emit = defineEmits([ // Amount of PIVs to send in the selected currency (e.g. USD) const amountCurrency = ref(''); const useShieldInputs = ref(false); +const autoShieldTransaction = ref(false); const color = ref(''); +watch(useShieldInputs, (useShieldInputs) => { + if (!useShieldInputs) autoShieldTransaction.value = false; +}); + const props = defineProps({ show: Boolean, price: Number, @@ -55,7 +60,8 @@ function send() { 'send', sanitizeHTML(address.value), amount.value, - useShieldInputs.value + useShieldInputs.value, + autoShieldTransaction.value ); } @@ -254,6 +260,23 @@ async function selectContact() { }}
+
+
+ + +
+
+
diff --git a/scripts/transaction_builder.js b/scripts/transaction_builder.js index 4c726e82c..d8abfce50 100644 --- a/scripts/transaction_builder.js +++ b/scripts/transaction_builder.js @@ -14,9 +14,9 @@ export class TransactionBuilder { // Part of the tx fee that has been already handled #handledFee = 0; - MIN_FEE_PER_BYTE = 10; + static MIN_FEE_PER_BYTE = 10; // This number is larger or equal than the max size of the script sig for a P2CS and P2PKH transaction - SCRIPT_SIG_MAX_SIZE = 108; + static SCRIPT_SIG_MAX_SIZE = 108; get valueIn() { return this.#valueIn; @@ -35,7 +35,9 @@ export class TransactionBuilder { */ isDust({ out, value }) { // Dust is a transaction such that its creation costs more than its value - return value < out.serialize().length * this.MIN_FEE_PER_BYTE; + return ( + value < out.serialize().length * TransactionBuilder.MIN_FEE_PER_BYTE + ); } constructor() { @@ -52,18 +54,31 @@ export class TransactionBuilder { return new TransactionBuilder(); } + /** + * @returns {number} Amount of fee in sats of a standard transaction based on the + * number of inputs and outputs + */ + static getStandardTxFee(nInputs, nOutputs) { + return ( + (nInputs * 138 + nOutputs * 34 + 21) * + TransactionBuilder.MIN_FEE_PER_BYTE + ); + } + getFee() { //TODO: find a cleaner way to add the dummy signature let scriptSig = []; for (let vin of this.#transaction.vin) { scriptSig.push(vin.scriptSig); // Insert a dummy signature just to compute fees - vin.scriptSig = bytesToHex(Array(this.SCRIPT_SIG_MAX_SIZE).fill(0)); + vin.scriptSig = bytesToHex( + Array(TransactionBuilder.SCRIPT_SIG_MAX_SIZE).fill(0) + ); } const fee = Math.ceil(this.#transaction.serialize().length / 2) * - this.MIN_FEE_PER_BYTE - + TransactionBuilder.MIN_FEE_PER_BYTE - this.#handledFee; // Re-insert whatever was inside before for (let i = 0; i < scriptSig.length; i++) { @@ -174,7 +189,8 @@ export class TransactionBuilder { if (this.isDust({ out, value }) && subtractFeeFromAmt) { return; } - const fee = out.serialize().length * this.MIN_FEE_PER_BYTE; + const fee = + out.serialize().length * TransactionBuilder.MIN_FEE_PER_BYTE; // We have subtracted fees from the value, mark this fee as handled (don't pay them again) if (subtractFeeFromAmt) { out.value -= fee; diff --git a/scripts/wallet.js b/scripts/wallet.js index 81eb1bdc5..880ae71ae 100644 --- a/scripts/wallet.js +++ b/scripts/wallet.js @@ -5,7 +5,7 @@ import { beforeUnloadListener, blockCount } from './global.js'; import { getNetwork } from './network.js'; import { MAX_ACCOUNT_GAP, SHIELD_BATCH_SYNC_SIZE } from './chain_params.js'; import { HistoricalTx, HistoricalTxType } from './historical_tx.js'; -import { COutpoint, Transaction } from './transaction.js'; +import { COutpoint, Transaction, UTXO } from './transaction.js'; import { confirmPopup, createAlert, isShieldAddress } from './misc.js'; import { cChainParams } from './chain_params.js'; import { COIN } from './chain_params.js'; @@ -1072,6 +1072,7 @@ export class Wallet { const value = transaction.shieldOutput[0]?.value || transaction.vout[0].value; try { + //await this.#shield.loadSaplingProver(); const { hex } = await this.#shield.createTransaction({ address: transaction.shieldOutput[0]?.address || @@ -1098,6 +1099,41 @@ export class Wallet { } } + /** + * + */ + async createAutoshieldTransactions(address, value) { + const [intermediaryAddress] = this.getNewAddress(1); + const firstTx = await this.sign( + this.createTransaction( + intermediaryAddress, + value + TransactionBuilder.getStandardTxFee(1, 1), + { + useShieldInputs: true, + } + ) + ); + const txBuilder = new TransactionBuilder() + .addUTXO( + new UTXO({ + outpoint: new COutpoint({ + txid: firstTx.txid, + n: 0, + }), + value: firstTx.vout[0].value, + script: firstTx.vout[0].script, + }) + ) + .addOutput({ + address, + value: firstTx.vout[0].value, + }); + txBuilder.equallySubtractAmt(txBuilder.getFee()); + const tx = txBuilder.build(); + await this.sign(tx); + return [firstTx, tx]; + } + /** * @param {import('./transaction.js').Transaction} transaction - transaction to sign * @throws {Error} if the wallet is view only diff --git a/tests/components/TransferMenu.test.js b/tests/components/TransferMenu.test.js index 9f4a7b28c..ba3cdedbb 100644 --- a/tests/components/TransferMenu.test.js +++ b/tests/components/TransferMenu.test.js @@ -63,7 +63,7 @@ it('Sends transaction correctly', async () => { expect(wrapper.emitted('send')).toBeUndefined(); await wrapper.find('[data-testid=sendButton]').trigger('click'); expect(wrapper.emitted('send')).toStrictEqual([ - ['DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bc', '60', false], + ['DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bc', '60', false, false], ]); }); @@ -74,6 +74,6 @@ it('Sends transaction with shield inputs', async () => { await wrapper.find('[data-testid=sendButton]').trigger('click'); expect(wrapper.emitted('send')).toStrictEqual([ - ['DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bc', '60', true], + ['DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bc', '60', true, false], ]); }); diff --git a/tests/unit/transaction_builder.spec.js b/tests/unit/transaction_builder.spec.js index 3e5c8d8c1..e19d60b06 100644 --- a/tests/unit/transaction_builder.spec.js +++ b/tests/unit/transaction_builder.spec.js @@ -7,6 +7,7 @@ import { UTXO, } from '../../scripts/transaction.js'; import { TransactionBuilder } from '../../scripts/transaction_builder.js'; +import { bytesToHex } from '../../scripts/utils'; describe('Transaction builder tests', () => { it('Builds a transaction correctly', () => { @@ -214,4 +215,75 @@ describe('Transaction builder tests', () => { }) ).toThrow(/address/); }); + + it('returns correct fee on standard tx', () => { + let tx = new TransactionBuilder().build(); + expect(TransactionBuilder.getStandardTxFee(0, 0)).toBe( + (tx.serialize().length / 2) * TransactionBuilder.MIN_FEE_PER_BYTE + ); + tx = new TransactionBuilder() + .addOutput({ + address: 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', + value: 1000, + }) + .build(); + expect(TransactionBuilder.getStandardTxFee(0, 1)).toBe( + (tx.serialize().length / 2) * TransactionBuilder.MIN_FEE_PER_BYTE + ); + tx = new TransactionBuilder() + .addOutput({ + address: 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', + value: 1000, + }) + .addOutput({ + address: 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', + value: 1000, + }) + .build(); + expect(TransactionBuilder.getStandardTxFee(0, 2)).toBe( + (tx.serialize().length / 2) * TransactionBuilder.MIN_FEE_PER_BYTE + ); + tx = new TransactionBuilder() + .addUTXO( + new UTXO({ + outpoint: new COutpoint({ + txid: 'a4dce96d30fd6a5acb63dd25b4d59e4216824ec0bbabe54f237cf9754f9b62bc', + n: 4, + }), + script: bytesToHex( + Array(TransactionBuilder.SCRIPT_SIG_MAX_SIZE).fill(0) + ), + value: 5, + }) + ) + .build(); + expect(TransactionBuilder.getStandardTxFee(1, 0)).toBe( + (tx.serialize().length / 2) * TransactionBuilder.MIN_FEE_PER_BYTE + ); + tx = new TransactionBuilder() + .addUTXO( + new UTXO({ + outpoint: new COutpoint({ + txid: 'a4dce96d30fd6a5acb63dd25b4d59e4216824ec0bbabe54f237cf9754f9b62bc', + n: 4, + }), + script: bytesToHex( + Array(TransactionBuilder.SCRIPT_SIG_MAX_SIZE).fill(0) + ), + value: 5, + }) + ) + .addOutput({ + address: 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', + value: 1000, + }) + .addOutput({ + address: 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb', + value: 1000, + }) + .build(); + expect(TransactionBuilder.getStandardTxFee(1, 2)).toBe( + (tx.serialize().length / 2) * TransactionBuilder.MIN_FEE_PER_BYTE + ); + }); }); diff --git a/tests/unit/wallet/transactions.spec.js b/tests/unit/wallet/transactions.spec.js index b682ef28e..972c979de 100644 --- a/tests/unit/wallet/transactions.spec.js +++ b/tests/unit/wallet/transactions.spec.js @@ -37,7 +37,7 @@ async function checkFees(wallet, tx, feesPerBytes) { } describe('Wallet transaction tests', () => { let wallet; - const MIN_FEE_PER_BYTE = new TransactionBuilder().MIN_FEE_PER_BYTE; + const MIN_FEE_PER_BYTE = TransactionBuilder.MIN_FEE_PER_BYTE; beforeEach(async () => { wallet = await setUpLegacyMainnetWallet();