Skip to content
This repository has been archived by the owner on Sep 27, 2024. It is now read-only.

Commit

Permalink
Implement a checkbox to subtract fee from transaction amount in Publi…
Browse files Browse the repository at this point in the history
…c Spend.

Part of #82.
  • Loading branch information
sproxet committed Oct 8, 2019
1 parent 1b4f537 commit 7b07e5e
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 16 deletions.
21 changes: 13 additions & 8 deletions src/daemon/zcoind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,15 +365,16 @@ export class Zcoind {

// Publicly send amount satoshi XZC to recipient. resolve()s with txid, or reject()s if we have insufficient funds
// or the call fails for some other reason.
async publicSend(auth: string, label: string, recipient: string, amount: number, feePerKb: number): Promise<string> {
async publicSend(auth: string, label: string, recipient: string, amount: number, feePerKb: number, subtractFeeFromAmount: boolean): Promise<string> {
const data: {txid: string} = await this.send(auth, 'create', 'sendZcoin', {
addresses: {
[recipient]: {
label,
amount
}
},
feePerKb
feePerKb,
subtractFeeFromAmount
});

return data.txid;
Expand Down Expand Up @@ -406,13 +407,17 @@ export class Zcoind {
});
}

// Calculate a transaction fee. addresses is a map of {recipient: satoshiAmount} pairs; feePerKb is the satoshi
// fee per kilobyte for the generated transaction. We reject() the promise if the zcoind call fails or received data
// is invalid.
async calcTxFee(feePerKb: number, addresses: Record<string, number>): Promise<number> {
// Calculate a transaction fee. feePerKb is the satoshi fee per kilobyte for the generated transaction.
//
// We resolve() with the calculated fee in satoshi.
// We reject() the promise if the zcoind call fails or received data is invalid.
async calcTxFee(feePerKb: number, address: string, amount: number, subtractFeeFromAmount: boolean): Promise<number> {
let data = await this.send(null, 'get', 'txFee', {
addresses,
feePerKb
addresses: {
[address]: amount
},
feePerKb,
subtractFeeFromAmount
});

if (typeof data.fee === 'number') {
Expand Down
146 changes: 139 additions & 7 deletions src/renderer/components/PaymentSidebars/Send.vue
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,20 @@
>
Very slightly less will be sent because of fees.
</div>
<div
v-else
class="subtract-fee-from-amount-checkbox"
>
<input
v-model="subtractFeeFromAmount"
type="checkbox"
name="subtractFeeFromAmount"
/>

<label for="subtractFeeFromAmount">
Take Transaction Fee From Amount
</label>
</div>

<div class="amount-available">
{{ convertToCoin(availableBalance) }} XZC available for {{ privateOrPublic }} send.
Expand All @@ -112,6 +126,41 @@
</fieldset>
</div>

<div
v-if="!!transactionFee"
class="totals"
>
<div class="field amount">
<label>
Recipient will receive:
</label>

<div class="value">
{{ convertToCoin(amountToReceive) }} XZC
</div>
</div>

<div class="field fee">
<label>
Transaction fee:
</label>

<div class="value">
{{ convertToCoin(transactionFee) }} XZC
</div>
</div>

<div class="field total">
<label>
Total:
</label>

<div class="value">
{{ convertToCoin(totalAmount) }} XZC
</div>
</div>
</div>

<div class="buttons">
<base-button
v-if="['confirm', 'passphrase', 'incorrectPassphrase', 'error'].includes(sendPopoverStep)"
Expand All @@ -136,7 +185,7 @@
v-if="sendPopoverStep === 'initial'"
color="green"
class="expanded"
:disabled="!canBeginSend"
:disabled="!(canBeginSend && transactionFee)"
@click.prevent="beginWaitToConfirmStep"
>
Send
Expand Down Expand Up @@ -190,8 +239,8 @@
v-if="['waitToConfirm', 'confirm'].includes(sendPopoverStep)"
:label="label"
:address="address"
:amount="satoshiAmount"
:fee="0"
:amount="amountToReceive"
:fee="transactionFee"
/>

<send-step-passphrase
Expand Down Expand Up @@ -262,6 +311,7 @@ export default {
label: this.$route.query.label || '',
amount: this.$route.query.amount || '',
address: this.$route.query.address || '',
subtractFeeFromAmount: true,
passphrase: '',
errorMessage: '',
Expand All @@ -275,7 +325,13 @@ export default {
// error -> initial
// incorrectPassphrase -> initial | passphrase
// complete -> initial
sendPopoverStep: 'initial'
sendPopoverStep: 'initial',
// This will be updated in watch() as computed properties can't use async.
transactionFee: 0,
// TODO: Right now we're just hardcoding this. It should be made user configurable.
txFeePerKb: 1
}
},
Expand Down Expand Up @@ -312,11 +368,27 @@ export default {
return this.privateOrPublic === 'private' ? this.availableZerocoin : this.availableXzc;
},
// This is the amount the user entered in satoshis.
satoshiAmount () {
return convertToSatoshi(this.amount);
},
// This is the amount the user will receive. It may be less than satoshiAmount.
amountToReceive () {
return this.subtractFeeFromAmount ? this.satoshiAmount - this.transactionFee : this.satoshiAmount;
},
// This is the total amount that will be sent, including transaction fee.
totalAmount () {
return this.subtractFeeFromAmount ? this.satoshiAmount : this.satoshiAmount + this.transactionFee;
},
// We can begin the send if the fee has been shown (if required) and the form is valid.
canBeginSend () {
return this.isValidated && (!this.privateOrPublic === 'private' || this.transactionFee > 0);
},
isValidated () {
// this.errors was already calculated when amount and address were entered.
return !!(this.amount && this.address && !this.validationErrors.items.length);
},
Expand All @@ -338,14 +410,28 @@ export default {
classes: 'error',
show: true
})
},
}
},
watch: {
$route(to) {
this.address = to.query.address || '';
this.label = to.query.label || '';
this.amount = to.query.amount || '';
},
address: {
handler: 'maybeShowFee',
immediate: true
},
amount: {
handler: 'maybeShowFee',
immediate: true
},
isValidated: {
handler: 'maybeShowFee'
}
},
Expand Down Expand Up @@ -394,6 +480,24 @@ export default {
methods: {
convertToCoin,
maybeShowFee () {
if (this.privateOrPublic === 'private' || !this.isValidated) {
this.transactionFee = 0;
return;
}
// First set transactionFee to 0. This is so the user can't hit Send before we've shown the fee.
this.transactionFee = 0;
this.$daemon.calcTxFee(this.txFeePerKb, this.address, this.satoshiAmount, this.subtractFeeFromAmount)
.then(r => {
this.transactionFee = r;
})
.catch(e => {
this.transactionFee = 0;
})
},
cleanupForm () {
this.label = '';
this.amount = '';
Expand Down Expand Up @@ -440,7 +544,8 @@ export default {
if (this.privateOrPublic === 'private') {
await this.$daemon.privateSend(passphrase, this.label, this.address, this.satoshiAmount);
} else {
await this.$daemon.publicSend(passphrase, this.label, this.address, this.satoshiAmount, 1);
await this.$daemon.publicSend(passphrase, this.label, this.address, this.satoshiAmount,
this.txFeePerKb, this.subtractFeeFromAmount);
}
} catch (e) {
// Error code -14 indicates an incorrect passphrase.
Expand Down Expand Up @@ -490,7 +595,7 @@ export default {
.description {
@include description();
margin-bottom: emRhythm(7);
margin-bottom: 1em;
}
}
Expand Down Expand Up @@ -540,13 +645,40 @@ fieldset {
font-size: 0.85em;
}
.subtract-fee-from-amount-checkbox {
font-weight: bold;
}
.amount-available {
text-align: right;
color: $color--polo-dark;
font-style: italic;
}
}
.totals {
margin-top: -3em;
.field {
font-weight: bold;
label, .value {
display: inline;
}
.value {
color: $color--green-dark;
}
}
.total {
border-top: {
color: black;
width: 5px;
}
}
}
.button-wrap {
padding-bottom: emRhythm(5);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export default {
font-weight: bold;
}
.fee, .total {
.amount, .fee, .total {
text-align: right;
}
Expand Down

0 comments on commit 7b07e5e

Please sign in to comment.