diff --git a/locale/cnr/translation.toml b/locale/cnr/translation.toml
index 90d2c3514..12dee5057 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
@@ -333,3 +334,4 @@ CONFIRM_LEDGER_TX = "" # Confirm this transaction matches the one on your {hardw
CONFIRM_LEDGER_TX_OUT = "" # You will send {value} {ticker} to
{address}
MISSING_FUNDS = "" # Balance is too small! Missing {sats} sats!
MISSING_SHIELD = "" # Shield is not enabled in this wallet!
+AUTO_SHIELDING_TO_SHIELD_ADDRESS = "" # Cannot auto shield to a shield address!
diff --git a/locale/de/translation.toml b/locale/de/translation.toml
index 6c4c87d87..7a6226e42 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
@@ -333,3 +334,4 @@ CONFIRM_LEDGER_TX = "" # Confirm this transaction matches the one on your {hardw
CONFIRM_LEDGER_TX_OUT = "" # You will send {value} {ticker} to
{address}
MISSING_FUNDS = "" # Balance is too small! Missing {sats} sats!
MISSING_SHIELD = "" # Shield is not enabled in this wallet!
+AUTO_SHIELDING_TO_SHIELD_ADDRESS = "" # Cannot auto shield to a shield address!
diff --git a/locale/en/translation.toml b/locale/en/translation.toml
index d0956ed49..2ebb3a3c7 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
@@ -333,3 +334,4 @@ CONFIRM_LEDGER_TX = "Confirm this transaction matches the one on your {hardwareW
CONFIRM_LEDGER_TX_OUT = "You will send {value} {ticker} to
{address}
" # You will send {value} {ticker} to
{address}
MISSING_FUNDS = "Balance is too small! Missing {sats} sats!" # Balance is too small! Missing {sats} sats!
MISSING_SHIELD = "Shield is not enabled in this wallet!" # Shield is not enabled in this wallet!
+AUTO_SHIELDING_TO_SHIELD_ADDRESS = "Cannot auto shield to a shield address!" # Cannot auto shield to a shield address!
diff --git a/locale/es-mx/translation.toml b/locale/es-mx/translation.toml
index 8799397c0..557b5c2cb 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
@@ -333,3 +334,4 @@ CONFIRM_LEDGER_TX = "" # Confirm this transaction matches the one on your {hardw
CONFIRM_LEDGER_TX_OUT = "" # You will send {value} {ticker} to
{address}
MISSING_FUNDS = "" # Balance is too small! Missing {sats} sats!
MISSING_SHIELD = "" # Shield is not enabled in this wallet!
+AUTO_SHIELDING_TO_SHIELD_ADDRESS = "" # Cannot auto shield to a shield address!
diff --git a/locale/fr/translation.toml b/locale/fr/translation.toml
index 3ebe8c866..57788276e 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
@@ -333,3 +334,4 @@ CONFIRM_LEDGER_TX = "" # Confirm this transaction matches the one on your {hardw
CONFIRM_LEDGER_TX_OUT = "" # You will send {value} {ticker} to
{address}
MISSING_FUNDS = "" # Balance is too small! Missing {sats} sats!
MISSING_SHIELD = "" # Shield is not enabled in this wallet!
+AUTO_SHIELDING_TO_SHIELD_ADDRESS = "" # Cannot auto shield to a shield address!
diff --git a/locale/it/translation.toml b/locale/it/translation.toml
index 4af5510b4..5c92b05ee 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
@@ -333,3 +334,4 @@ CONFIRM_LEDGER_TX = "Conferma che questa transazione combacia quella nel tuo {ha
CONFIRM_LEDGER_TX_OUT = "Manderai {value} {ticker} a
{address}
" # You will send {value} {ticker} to
{address}
MISSING_FUNDS = "Il bilancio non è sufficiente! Mancano {sats} sats!" # Balance is too small! Missing {sats} sats!
MISSING_SHIELD = "Lo Shield non è abilitato nel wallet!" # Shield is not enabled in this wallet!
+AUTO_SHIELDING_TO_SHIELD_ADDRESS = "" # Cannot auto shield to a shield address!
diff --git a/locale/nl/translation.toml b/locale/nl/translation.toml
index e70dc7836..af2da80f8 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
@@ -333,3 +334,4 @@ CONFIRM_LEDGER_TX = "" # Confirm this transaction matches the one on your {hardw
CONFIRM_LEDGER_TX_OUT = "" # You will send {value} {ticker} to
{address}
MISSING_FUNDS = "" # Balance is too small! Missing {sats} sats!
MISSING_SHIELD = "" # Shield is not enabled in this wallet!
+AUTO_SHIELDING_TO_SHIELD_ADDRESS = "" # Cannot auto shield to a shield address!
diff --git a/locale/ph/translation.toml b/locale/ph/translation.toml
index 36d2dba1f..8daedffc1 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
@@ -333,3 +334,4 @@ CONFIRM_LEDGER_TX = "" # Confirm this transaction matches the one on your {hardw
CONFIRM_LEDGER_TX_OUT = "" # You will send {value} {ticker} to
{address}
MISSING_FUNDS = "" # Balance is too small! Missing {sats} sats!
MISSING_SHIELD = "" # Shield is not enabled in this wallet!
+AUTO_SHIELDING_TO_SHIELD_ADDRESS = "" # Cannot auto shield to a shield address!
diff --git a/locale/pl/translation.toml b/locale/pl/translation.toml
index 82bfc7344..63972985d 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
@@ -333,3 +334,4 @@ CONFIRM_LEDGER_TX = "" # Confirm this transaction matches the one on your {hardw
CONFIRM_LEDGER_TX_OUT = "" # You will send {value} {ticker} to
{address}
MISSING_FUNDS = "" # Balance is too small! Missing {sats} sats!
MISSING_SHIELD = "" # Shield is not enabled in this wallet!
+AUTO_SHIELDING_TO_SHIELD_ADDRESS = "" # Cannot auto shield to a shield address!
diff --git a/locale/pt-br/translation.toml b/locale/pt-br/translation.toml
index b1ec19379..13ea80fc4 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.
@@ -132,3 +133,4 @@ CONFIRM_LEDGER_TX = "" # Confirm this transaction matches the one on your {hardw
CONFIRM_LEDGER_TX_OUT = "" # You will send {value} {ticker} to
{address}
MISSING_FUNDS = "" # Balance is too small! Missing {sats} sats!
MISSING_SHIELD = "" # Shield is not enabled in this wallet!
+AUTO_SHIELDING_TO_SHIELD_ADDRESS = "" # Cannot auto shield to a shield address!
diff --git a/locale/pt-pt/translation.toml b/locale/pt-pt/translation.toml
index ea6d00b9a..9032e6fdf 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.
@@ -132,3 +133,4 @@ CONFIRM_LEDGER_TX = "" # Confirm this transaction matches the one on your {hardw
CONFIRM_LEDGER_TX_OUT = "" # You will send {value} {ticker} to
{address}
MISSING_FUNDS = "" # Balance is too small! Missing {sats} sats!
MISSING_SHIELD = "" # Shield is not enabled in this wallet!
+AUTO_SHIELDING_TO_SHIELD_ADDRESS = "" # Cannot auto shield to a shield address!
diff --git a/locale/template/translation.toml b/locale/template/translation.toml
index cd777e5b4..adc38913a 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"
@@ -356,4 +357,5 @@ CONFIRM_POPUP_DELETE_ACCOUNT_TITLE = 'Are you sure?'
CONFIRM_LEDGER_TX = 'Confirm this transaction matches the one on your {hardwareWallet}'
CONFIRM_LEDGER_TX_OUT = 'You will send {value} {ticker} to
{address}
'
MISSING_FUNDS = 'Balance is too small! Missing {sats} sats!'
-MISSING_SHIELD = "Shield is not enabled in this wallet!"
\ No newline at end of file
+MISSING_SHIELD = "Shield is not enabled in this wallet!"
+AUTO_SHIELDING_TO_SHIELD_ADDRESS = "Cannot auto shield to a shield address!"
\ No newline at end of file
diff --git a/locale/uwu/translation.toml b/locale/uwu/translation.toml
index 2ec8dde52..1a0aaab21 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
@@ -333,3 +334,4 @@ CONFIRM_LEDGER_TX = "" # Confirm this transaction matches the one on your {hardw
CONFIRM_LEDGER_TX_OUT = "" # You will send {value} {ticker} to
{address}
MISSING_FUNDS = "" # Balance is too small! Missing {sats} sats!
MISSING_SHIELD = "" # Shield is not enabled in this wallet!
+AUTO_SHIELDING_TO_SHIELD_ADDRESS = "" # Cannot auto shield to a shield address!
diff --git a/scripts/composables/use_wallet.js b/scripts/composables/use_wallet.js
index bceb179bb..905d07ed6 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 createAutoshieldTransactions = 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,
+ createAutoshieldTransactions,
};
});
diff --git a/scripts/contacts-book.js b/scripts/contacts-book.js
index 08b51564b..041301a31 100644
--- a/scripts/contacts-book.js
+++ b/scripts/contacts-book.js
@@ -7,6 +7,7 @@ import {
createAlert,
createQR,
getImageFile,
+ isShieldAddress,
isValidPIVXAddress,
isXPub,
sanitizeHTML,
@@ -935,8 +936,12 @@ export async function guiSetAccountName(strDOM) {
/**
* Get the address color based on the validity of an address/contact
* @param {string} address
+ * @param {boolean} [invalidateShieldAddresses] - if set to true, shield addresses will return as invalid color
*/
-export async function getAddressColor(address) {
+export async function getAddressColor(
+ address,
+ invalidateShieldAddresses = false
+) {
// If the value is empty, we don't do any checks and simply reset the colourcoding
if (!address) {
return '';
@@ -955,6 +960,9 @@ export async function getAddressColor(address) {
// Yep, nice!
return 'green';
}
+ if (invalidateShieldAddresses && isShieldAddress(address)) {
+ return '#b20000';
+ }
if (isValidPIVXAddress(address)) {
// Yep!
return 'green';
diff --git a/scripts/dashboard/Dashboard.vue b/scripts/dashboard/Dashboard.vue
index 0779fbb3f..35c369729 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 (
@@ -251,6 +252,10 @@ async function send(address, amount, useShieldInputs) {
}
}
+ if (isAutoShield && isShieldAddress(address)) {
+ return createAlert('warning', ALERTS.AUTO_SHIELDING_TO_SHIELD_ADDRESS);
+ }
+
// If this is an XPub, we'll fetch their last used 'index', and derive a new public key for enhanced privacy
if (isXPub(address)) {
const cNet = getNetwork();
@@ -325,9 +330,22 @@ async function send(address, amount, useShieldInputs) {
// Create and send the TX
try {
- await wallet.createAndSendTransaction(getNetwork(), address, nValue, {
- useShieldInputs,
- });
+ if (isAutoShield) {
+ await wallet.createAutoshieldTransactions(
+ 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..b2829959c 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,
@@ -35,9 +40,16 @@ const address = computed({
},
set(value) {
emit('update:address', value);
- getAddressColor(value).then((c) => (color.value = c));
+ getAddressColor(
+ value,
+ autoShieldTransaction.value || !props.shieldEnabled
+ ).then((c) => (color.value = c));
},
});
+watch(autoShieldTransaction, () => {
+ // Trigger address setter so we can recolor shield addresses
+ address.value = address.value;
+});
const amount = computed({
get() {
return props.amount;
@@ -55,7 +67,8 @@ function send() {
'send',
sanitizeHTML(address.value),
amount.value,
- useShieldInputs.value
+ useShieldInputs.value,
+ autoShieldTransaction.value
);
}
@@ -254,6 +267,23 @@ async function selectContact() {
}}
+
+
+
+
+
+
+
diff --git a/scripts/transaction_builder.js b/scripts/transaction_builder.js
index 4c726e82c..ebe5a3c58 100644
--- a/scripts/transaction_builder.js
+++ b/scripts/transaction_builder.js
@@ -4,6 +4,7 @@ import { OP } from './script.js';
import { hexToBytes, bytesToHex, dSHA256 } from './utils.js';
import { isShieldAddress, isExchangeAddress } from './misc.js';
import { SAPLING_TX_VERSION } from './chain_params.js';
+import { numToVarInt } from './encoding.js';
/**
* @class Builds a non-signed transaction
*/
@@ -14,9 +15,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 +36,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 +55,37 @@ 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) {
+ const inputVarIntLength = numToVarInt(nInputs).length;
+ const outputVarIntLength = numToVarInt(nOutputs).length;
+ return (
+ (nInputs * (TransactionBuilder.SCRIPT_SIG_MAX_SIZE + 41) +
+ nOutputs * 34 +
+ 8 +
+ inputVarIntLength +
+ outputVarIntLength) *
+ 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 +196,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 fd864bb04..60b99cae0 100644
--- a/scripts/wallet.js
+++ b/scripts/wallet.js
@@ -5,8 +5,13 @@ 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 { confirmPopup, createAlert, isShieldAddress } from './misc.js';
+import { COutpoint, Transaction, UTXO } from './transaction.js';
+import {
+ confirmPopup,
+ createAlert,
+ isShieldAddress,
+ isValidPIVXAddress,
+} from './misc.js';
import { cChainParams } from './chain_params.js';
import { COIN } from './chain_params.js';
import { ALERTS, tr, translation } from './i18n.js';
@@ -1102,6 +1107,50 @@ export class Wallet {
}
}
+ /**
+ * @param {string} address
+ * @param {number} value - Value to send in satoshi
+ * @returns {Transaction[]} Two transactions, the first one deshielding `value`, the second one sending `value` to `address`
+ */
+ async createAutoshieldTransactions(address, value) {
+ if (!this.hasShield()) {
+ throw new Error(
+ 'trying to create a shield transaction without having shield enabled'
+ );
+ }
+ if (isShieldAddress(address) || !isValidPIVXAddress(address))
+ throw new Error('Invalid address');
+ 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..bf22fb3b7 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,80 @@ describe('Transaction builder tests', () => {
})
).toThrow(/address/);
});
+
+ it('returns correct fee on standard tx', () => {
+ let tx = new TransactionBuilder().build();
+ tx.version = 1;
+ expect(TransactionBuilder.getStandardTxFee(0, 0)).toBe(
+ (tx.serialize().length / 2) * TransactionBuilder.MIN_FEE_PER_BYTE
+ );
+ tx = new TransactionBuilder()
+ .addOutput({
+ address: 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb',
+ value: 1000,
+ })
+ .build();
+ tx.version = 1;
+ 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();
+ tx.version = 1;
+ 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();
+ tx.version = 1;
+ 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();
+ tx.version = 1;
+ 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..4f87b6434 100644
--- a/tests/unit/wallet/transactions.spec.js
+++ b/tests/unit/wallet/transactions.spec.js
@@ -1,6 +1,6 @@
import { Wallet } from '../../../scripts/wallet.js';
import { Mempool } from '../../../scripts/mempool.js';
-import { setUpLegacyMainnetWallet } from '../../utils/test_utils';
+import { PIVXShield, setUpLegacyMainnetWallet } from '../../utils/test_utils';
import { describe, it, vi, afterAll, expect } from 'vitest';
import {
COutpoint,
@@ -11,6 +11,8 @@ import {
import 'fake-indexeddb/auto';
import { TransactionBuilder } from '../../../scripts/transaction_builder.js';
+import { setUpHDMainnetWallet } from '../../utils/test_utils';
+import { COIN } from '../../../scripts/chain_params';
vi.stubGlobal('localStorage', { length: 0 });
vi.mock('../../../scripts/global.js');
@@ -36,8 +38,11 @@ async function checkFees(wallet, tx, feesPerBytes) {
expect(fees).toBeLessThanOrEqual((feesPerBytes + 1) * nBytes);
}
describe('Wallet transaction tests', () => {
+ /**
+ * @type {Wallet}
+ */
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();
@@ -376,6 +381,68 @@ describe('Wallet transaction tests', () => {
expect(spy).toBeCalledWith(tx);
});
+ it('throws when calling auto shield transaction on a legacy wallet', () => {
+ wallet.setShield(null);
+ expect(
+ wallet.createAutoshieldTransactions(
+ 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb',
+ 1
+ )
+ ).rejects.toThrowError('shield enabled');
+ });
+
+ it('creates auto shield transaction correctly', async () => {
+ wallet = await setUpHDMainnetWallet(true);
+ let shieldTx;
+ PIVXShield.prototype.createTransaction.mockImplementationOnce(
+ ({ address, amount }) => {
+ shieldTx = new TransactionBuilder()
+ .addOutput({
+ address,
+ value: amount,
+ })
+ .build();
+ return {
+ hex: shieldTx.serialize(),
+ };
+ }
+ );
+ const txs = await wallet.createAutoshieldTransactions(
+ 'DLabsktzGMnsK5K9uRTMCF6NoYNY6ET4Bb',
+ 1 * COIN
+ );
+ expect(txs).toHaveLength(2);
+ expect(txs[0].serialize()).toBe(shieldTx.serialize());
+ expect(txs[1].vin).toStrictEqual([
+ new CTxIn({
+ outpoint: new COutpoint({
+ txid: shieldTx.txid,
+ n: 0,
+ }),
+ scriptSig:
+ '47304402203185d51275f4bf0057ffc9191b58d2e26052b67f7767542bbb7960cae3b26b97022037e86cc2a0c74f33e3490283c0cf7ab42414b9c7574c0a9defb8c21b32ad8a9b012102ecd478b763612a71351b16165dac15afd591c33e28cf7ca7b45b7189691b6373',
+ }),
+ ]);
+ expect(txs[1].vout).toStrictEqual([
+ new CTxOut({
+ script: '76a914a95cc6408a676232d61ec29dc56a180b5847835788ac',
+ value: 1 * COIN,
+ }),
+ ]);
+ });
+
+ it('throws when auto shielding to invalid or shield addresses', () => {
+ expect(
+ wallet.createAutoshieldTransactions('Invalidaddress', 1)
+ ).rejects.toThrowError('Invalid address');
+ expect(
+ wallet.createAutoshieldTransactions(
+ 'ps1a0x2few52sy3t0nrdhun0re4c870e04w448qpa7c26qjw9ljs4quhja40hat95f7hy8tcuvcn2s',
+ 1
+ )
+ ).rejects.toThrowError('Invalid address');
+ });
+
afterAll(() => {
vi.clearAllMocks();
});