Commit 59d35c30 authored by Bitcoin Please's avatar Bitcoin Please

Refactored Flipstarter pledge manager.

parent 7abdb08c
......@@ -13,7 +13,7 @@ pragma cashscript ^0.4.0;
* support@bchplease.org
*
* This contract enforces a specific market price for payouts to the
* contract's receipient.
* contract's recipient.
*
* A minimum block height is encoded, to ensure that the oracle price is
* current, ie. signed within the last 3 hours.
......@@ -41,7 +41,7 @@ contract MecenasOracle(
/**
* Payout
*
* Will make a payout to the receipient of the contract.
* Will make a payout to the recipient of the contract.
*
* pk : public key of the transaction's originator
* s : signature of the transaction
......
......@@ -29,7 +29,7 @@ pragma cashscript ^0.4.0;
* Mecenas Oracle (Fixed Block)
*
* This contract enforces a specific market price for payouts to the
* contract's receipient.
* contract's recipient.
*
* A minimum block is provided to ensure that the oracle price
*
......@@ -53,7 +53,7 @@ contract MecenasOracleFixed(
/**
* Payout
*
* Will make a payout to the receipient of the contract.
* Will make a payout to the recipient of the contract.
*
* pk : public key of the transaction's originator
* s : signature of the transaction
......
......@@ -82,8 +82,14 @@
</a>
</div>
<Flipstarter v-if="showFlipstarter" />
</div>
<div v-if="showFlipstarter" class="account-content account-table mt-3">
<h3 class="account-title">Add Flipstarter Pledge</h3>
<Flipstarter />
</div>
</div>
</div>
</div>
......@@ -97,10 +103,6 @@
/* Initialize vuex. */
import { mapActions, mapGetters } from 'vuex'
/* Import modules. */
import moment from 'moment'
import Nito from 'nitojs'
/* Import components. */
import Footer from '@/components/Footer'
import Header from '@/components/Header'
......@@ -112,9 +114,6 @@ import Sidenav from '@/components/Account/Sidenav'
/* Import (local) components. */
import Flipstarter from './Backing/Flipstarter'
const DUST = 546
const FEE = 270
export default {
components: {
Footer,
......@@ -138,512 +137,12 @@ export default {
'getMeta',
]),
...mapGetters('wallet', [
'getAccounts',
'getAddress',
'getBalance',
'getCoins',
'getDerivationPath',
'getHDNode',
'getIndices',
]),
},
methods: {
...mapActions('profile', [
'updateMeta',
]),
...mapActions('utils', [
'toast',
]),
...mapActions('wallet', [
'updateCoins',
]),
/**
* Parse Key Hash Unlock Script
*/
parseKeyHashUnlockScript(unlockScript) {
/**
* The signature is the first pushed item, with a varying length
* depending on signature type.
*/
const signature = unlockScript.slice(1, -34)
/* The public key is the last pushed item of 33 bytes. */
const publicKey = unlockScript.slice(-33)
/* Return the parsed unlock script. */
return {
publicKey,
signature
}
},
/**
* Assemble Signature Hash Digest
*/
assembleSighashDigest(
previousTransactionHash,
previousTransactionOutputIndex,
previousTransactionOutputValue,
inputLockScript
) {
/* Initialize an empty array of outpoints. */
const transactionOutpoints = []
/* Initialize value. */
let value = null
/* Set campaign value. */
this.campaignValue = this.userPledge.outputs[0].value
console.log('Campaign value:', this.campaignValue)
/* Set value. */
value = Nito.Utils.encodeNumber(this.campaignValue)
console.log('Encoded value:', value)
/* Set campaign address. */
this.campaignAddress = this.userPledge.outputs[0].address
console.log('Campaign address:', this.campaignAddress)
/* Set locking script. */
const locking_script = Nito.Address.toPubKeyHash(this.campaignAddress)
console.log('Campaign (locking_script):', locking_script)
/* Set current output. */
// NOTE: This is the pledge recipient.
// FIXME: Allow multiple pledge recipients.
const thisOutputs = [{ value, locking_script }]
/* Add each output in the current contract. */
for (const currentOutput in thisOutputs) {
// Add the output value.
transactionOutpoints.push(thisOutputs[currentOutput].value)
// Add the output lockscript.
transactionOutpoints.push(
Nito.Utils.varBuf(thisOutputs[currentOutput].locking_script)
)
}
console.log('Transaction outpoints:', transactionOutpoints)
/* Set version. */
const nVersion = Buffer.from('02000000', 'hex')
/* Set hash previous output. */
const hashPrevouts = Buffer.from(''.padStart(64, '0'), 'hex')
/* Set hash sequence. */
const hashSequence = Buffer.from(''.padStart(64, '0'), 'hex')
/* Set outpoint. */
const outpoint = Buffer.concat([
Nito.Utils.reverseBuffer(previousTransactionHash),
previousTransactionOutputIndex,
])
/* Set script code. */
const scriptCode = Buffer.concat([
Buffer.from('19', 'hex'),
inputLockScript,
])
/* Set (transaction) value. */
value = previousTransactionOutputValue
/* Set sequence. */
const nSequence = Buffer.from('FFFFFFFF', 'hex')
/* Set hash outputs. */
// const hashOutputs = bitbox.Crypto.hash256(
// Buffer.concat(transactionOutpoints)
// )
const hashOutputs = Nito.Crypto.hash(
Buffer.concat(transactionOutpoints), 'sha256sha256'
)
console.log('transactionOutpoints', transactionOutpoints);
console.log('transactionOutpoints (concat)', Buffer.concat(transactionOutpoints).toString('hex'));
console.log('hashOutputs', hashOutputs.toString('hex'));
/* Set locktime. */
const nLocktime = Buffer.from('00000000', 'hex')
/* Set signature hash type. */
const sighashType = Buffer.from('c1000000', 'hex')
// console.log('nVersion', nVersion);
// console.log('hashPrevouts', hashPrevouts);
// console.log('hashSequence', hashSequence);
// console.log('outpoint', outpoint);
// console.log('scriptCode', scriptCode);
// console.log('value', value);
// console.log('nSequence', nSequence);
// console.log('hashOutputs', hashOutputs);
// console.log('nLocktime', nLocktime);
// console.log('sighashType', sighashType);
/* Construct signature hash message. */
const sighashMessage = Buffer.concat([
nVersion,
hashPrevouts,
hashSequence,
outpoint,
scriptCode,
value,
nSequence,
hashOutputs,
nLocktime,
sighashType,
])
console.log('sighashMessage', sighashMessage.toString('hex'));
/* Create signature hash digest (of message). */
const sighashDigest = Nito.Crypto.hash(sighashMessage, 'sha256sha256')
console.log('sighashDigest', sighashDigest.toString('hex'));
/* Return signature hash digest. */
return sighashDigest
},
/**
* Load Funds
*/
async loadFunds(_coin, _address, _satoshis) {
console.log('SENDING COIN', _coin)
if (!_address) {
return console.error('NO output address!')
}
/* Build receivers. */
const receivers = [
{
address: _address,
satoshis: _satoshis,
}
]
const campaignValue = this.userPledge.outputs[0].value
console.log('CAMPAIGN VALUE', campaignValue);
/* Set change address. */
const changeAddress = this.getAddress('change')
console.log('CHANGE ADDRESS', changeAddress)
if (_coin.satoshis - _satoshis > DUST + FEE) {
receivers.push(
{
address: changeAddress,
satoshis: (_coin.satoshis - _satoshis - FEE),
}
)
}
console.log('RECEIVERS', receivers)
/* Set auto fee (flag). */
const autoFee = false
const results = await Nito.Transaction
.sendCoin(_coin, receivers, autoFee)
.catch(err => console.error(err))
console.log('OUTBOX SEND COIN (results):', results)
if (results) {
/* Set message. */
const message = `Your coins have been sent successfully!`
console.log('MESSAGE', message)
/* Display notification. */
// this.$notify({
// message,
// icon: 'ti-info-alt', // ti-info-alt | ti-alert
// verticalAlign: 'top',
// horizontalAlign: 'right',
// type: 'info', // info | danger
// // timeout: 0, // 0: persistent | 5000: default
// })
/* Wait a bit then update coins. */
// FIXME: How long should we wait?
// Probably better to update coins w/out on-chain query?
setTimeout(() => {
/* Update coins. */
// FIXME: Why is this blocking the entire initial UI setup??
this.updateCoins()
}, 2000)
} else {
/* Set message. */
const message = `Oops! Something went wrong and your coin(s) were NOT sent.`
console.error('MESSAGE', message)
/* Display notification. */
// this.$notify({
// message,
// icon: 'ti-alert', // ti-info-alt | ti-alert
// verticalAlign: 'top',
// horizontalAlign: 'right',
// type: 'danger', // info | danger
// // timeout: 0, // 0: persistent | 5000: default
// })
}
},
/**
* Confirm Flipstarter (Pledge)
*
* Transfers the pledge amount into a dedicated UTXO.
*/
async confirmFlipstarter() {
/* Request accounts. */
const accounts = this.getAccounts
console.log('ACCOUNTS', accounts)
/* Validate accounts. */
if (!accounts) {
return null
}
/* Request coins. */
const coins = this.getCoins
console.log('COINS', coins)
/* Validate coins. */
if (!coins) {
return null
}
/* Request metadata. */
const meta = await this.getMeta
console.log('BACKING (meta):', meta)
const spendable = Object.keys(coins).filter(coinid => {
return coins[coinid].status === 'active'
})
console.log('SPENDABLE', spendable)
const locked = Object.keys(coins).filter(coinid => {
return coins[coinid].status === 'locked'
})
console.log('LOCKED', locked)
/* Initialized locked flag. */
let isAlreadyLocked = false
/* Initialize source coin. */
let sourceCoin = null
/* Set donation amount. */
const donation = this.userPledge.donation.amount
console.log('DONATION', donation)
/* Loop through all locked. */
locked.forEach(coinid => {
if (coins[coinid].satoshis === donation) {
/* Set flag. */
isAlreadyLocked = true
/* Set source coin. */
sourceCoin = coins[coinid]
}
})
/* Loop through all spendables. */
// FIXME FOR DEVELOPMENT ONLY
spendable.forEach(coinid => {
if (coins[coinid].satoshis === donation) {
/* Set flag. */
isAlreadyLocked = true
/* Set source coin. */
sourceCoin = coins[coinid]
}
})
/* Validate pledge availability. */
if (!isAlreadyLocked) {
console.error('MAKE THE DEPOSIT')
/* Set pledge address. */
const pledgeAddress = this.getAddress('causes')
console.log('PLEDGE ADDRESS', pledgeAddress)
// TODO Sort spendables by value low->high.
/* Handle all spendable coins. */
for (let i = 0; i < spendable.length; i++) {
/* Set coin. */
const coin = coins[spendable[i]]
/* Validate coin value. */
if (coin.satoshis > donation + DUST + FEE) {
/* Set source coin. */
sourceCoin = coin // FIXME: Search for minimum sufficient coin(s).
console.log('SOURCE COIN', sourceCoin)
break
}
}
/* Validate source coin. */
if (sourceCoin) {
/* Load funds. */
this.loadFunds(sourceCoin, pledgeAddress, donation)
/* Make pledge. */
// this.makePledge(sourceCoin)
// FIXME Wait until coin is in mempool, then `makePledge`
/* Set message. */
const message = `Your coin is ready to be pledged!`
/* Display notification. */
this.toast(['Done!', message, 'success'])
} else {
throw new Error(`Could not find a source coin for [ ${donation} ]`)
}
} else {
console.error('RE-USE CURRENT DEPOSIT', sourceCoin)
/* Make pledge. */
this.makePledge(sourceCoin)
}
},
/**
* Make Pledge
*/
async makePledge(_coin) {
/* Initialize verification key. */
const verificationKey = Nito.Purse.fromWIF(_coin.wif)
console.log('verificationKey', verificationKey, _coin.wif)
/* Set public key. */
const publicKey = verificationKey.publicKey.toString()
console.log('\nPublic key:', publicKey)
/* Set cash address. */
const cashAddress = Nito.Address.toCashAddress(verificationKey)
console.log('FLIPSTARTER (pledge address)', cashAddress)
console.log('USER PLEDGE', this.userPledge)
const alias = this.userPledge.data.alias
console.log('ALIAS:', alias)
const comment = this.userPledge.data.comment
console.log('COMMENT:', comment)
const expires = this.userPledge.expires
console.log('EXPIRES:', expires)
if (!_coin.txid || !_coin.satoshis) {
return console.error('No UTXO available for pledge.')
}
const previousTransactionHash = _coin.txid
const previousTransactionOutputValue = Nito.Utils.encodeNumber(_coin.satoshis)
const previousTransactionOutputIndex = '00000000' // FIXME
const inputLockScript = Nito.Address.toPubKeyHash(cashAddress)
// Validate commitment signature
const verificationMessage = this.assembleSighashDigest(
Buffer.from(previousTransactionHash, 'hex'),
Buffer.from(previousTransactionOutputIndex, 'hex'),
Buffer.from(previousTransactionOutputValue, 'hex'),
Buffer.from(inputLockScript, 'hex')
)
console.log('verificationMessage', verificationMessage.toString('hex'))
const pledgeSig = Nito.Account.sign(verificationMessage, verificationKey)
console.log('PLEDGE SIGNATURE', pledgeSig.toString())
const previous_output_transaction_hash = previousTransactionHash
const previous_output_index = 0 // FIXME
const sequence_number = 4294967295
const unlocking_script =
(pledgeSig.toString().slice(2, 4) === '44' ? '47' : '48') + // FIXME??
pledgeSig.toString() +
'c1' + // NOTE: sigHashType
'21' +
publicKey
console.log('UNLOCKING SCRIPT:', unlocking_script)
const assuranceOutput = {
inputs: [{
previous_output_transaction_hash,
previous_output_index,
sequence_number,
unlocking_script,
}],
data: {
alias,
comment,
},
data_signature: null
}
console.log('ASSURANCE OUTPUT:', assuranceOutput)
/* Encode assurance pledge. */
const encodedPledge = Buffer.from(JSON.stringify(assuranceOutput)).toString('base64')
console.log('Flipstarter encoded pledge (base64):', encodedPledge)
/* Update pledge authorization. */
this.pledgeAuth = encodedPledge
/* Initialize meta. */
const meta = await this.getMeta
console.log('BACKING (meta):', meta)
/* Set coin id. */
// const coinid = `${_coin.txid}:${_coin.vout}`
/* Validate coins. */
// NOTE: Added to schema on 2020.7.27
if (!meta.addresses) {
meta.addresses = {}
}
/* Validate coins. */
// NOTE: Added to schema on 2020.7.27
if (!meta.coins) {
meta.coins = {}
}
/* Update meta data. */
// meta['coins'][coinid] = {
meta['addresses'][this.campaignAddress] = {
label: alias,
comment,
lock: {
isActive: true,
source: 'flipstarter',
createdAt: moment().unix(),
expiresAt: expires,
}
}
/* Update metadata. */
this.updateMeta(meta)
/* Set message. */
// const message = `Your coin has been locked!`
const message = `Your adddress has been locked!`
/* Display notification. */
this.toast(['Done!', message, 'success'])
},
},
created: async function () {
/* Set owner slug. */
......
This diff is collapsed.
......@@ -72,7 +72,7 @@
<span v-if="campaignModel == 'Community Pledge'"></span>
</div>
<div v-if="recipient" class="receipient-address">
<div v-if="recipient" class="recipient-address">
<a :href="'https://explorer.bitcoin.com/bch/address/' + recipient" target="_blank">
<i class="fa fa-check-circle text-success ml-2 mr-1"></i>
<span class="text-secondary">{{recipient}}</span>
......@@ -807,11 +807,11 @@ export default {
font-style: italic;
}
.receipient-address {
.recipient-address {
font-size: 0.8em;
margin-top: -10px;
}
.receipient-address a {
.recipient-address a {