-
Notifications
You must be signed in to change notification settings - Fork 95
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,10 @@ require('./sentry'); | |
const ipc = require('electron').ipcRenderer; | ||
const FullNode = require('hsd/lib/node/fullnode'); | ||
const WalletPlugin = require('hsd/lib/wallet/plugin'); | ||
const Script = require('hsd/lib/script/script'); | ||
const remote = require('electron').remote; | ||
const {hashName, types} = require('hsd/lib/covenants/rules'); | ||
const {Output, MTX, Address} = require('hsd/lib/primitives'); | ||
|
||
let hsd = null; | ||
ipc.on('start', (_, prefix, net, apiKey) => { | ||
|
@@ -45,7 +48,7 @@ ipc.on('start', (_, prefix, net, apiKey) => { | |
.then(() => hsd.startSync()) | ||
.catch((e) => { | ||
console.log(e); | ||
ipc.send('error', e) | ||
ipc.send('error', e); | ||
}); | ||
}); | ||
|
||
|
@@ -57,3 +60,122 @@ ipc.on('close', () => { | |
hsd.close() | ||
.then(() => remote.getCurrentWindow().close()); | ||
}); | ||
|
||
ipc.on('finalize-with-payment', (event, name, fundingAddr, nameReceiveAddr, price) => { | ||
(async () => { | ||
const {wdb} = hsd.require('walletdb'); | ||
const wallet = await wdb.get('allison'); | ||
const ns = await wallet.getNameStateByName(name); | ||
const owner = ns.owner; | ||
const coin = await wallet.getCoin(owner.hash, owner.index); | ||
const nameHash = hashName(name); | ||
|
||
const output0 = new Output(); | ||
output0.value = coin.value; | ||
output0.address = new Address().fromString(nameReceiveAddr); | ||
output0.covenant.type = types.FINALIZE; | ||
output0.covenant.pushHash(nameHash); | ||
output0.covenant.pushU32(ns.height); | ||
output0.covenant.push(Buffer.from(name, 'ascii')); | ||
output0.covenant.pushU8(0); // flags, may be required if name was CLAIMed | ||
This comment has been minimized.
Sorry, something went wrong. |
||
output0.covenant.pushU32(ns.claimed); | ||
output0.covenant.pushU32(ns.renewals); | ||
output0.covenant.pushHash(await wdb.getRenewalBlock()); | ||
|
||
const output1 = new Output(); | ||
output1.address = new Address().fromString(fundingAddr); | ||
output1.value = price; | ||
|
||
const mtx = new MTX(); | ||
mtx.addCoin(coin); | ||
mtx.outputs.push(output0); | ||
mtx.outputs.push(output1); | ||
|
||
// Sign | ||
const rings = await wallet.deriveInputs(mtx); | ||
assert(rings.length === 1); | ||
const signed = await mtx.sign( | ||
rings, | ||
Script.hashType.SINGLEREVERSE | Script.hashType.ANYONECANPAY, | ||
); | ||
assert(signed === 1); | ||
|
||
assert(mtx.verify()); | ||
return mtx.encode().toString('hex'); | ||
})().then((hex) => { | ||
ipc.send('finalize-with-payment-reply', hex); | ||
}).catch((e) => { | ||
ipc.send('finalize-with-payment-reply', { | ||
error: e.message, | ||
}); | ||
}); | ||
}); | ||
|
||
ipc.on('claim-paid-transfer', (event, txHex) => { | ||
(async () => { | ||
const {wdb} = hsd.require('walletdb'); | ||
const wallet = await wdb.get('allison'); | ||
const mtx = MTX.decode(Buffer.from(txHex, 'hex')); | ||
|
||
// Bob should verify all the data in the MTX to ensure everything is valid, | ||
// but this is the minimum. | ||
This comment has been minimized.
Sorry, something went wrong.
pinheadmz
Contributor
|
||
const input0 = mtx.input(0).clone(); // copy input with Alice's signature | ||
const coinEntry = await hsd.chain.db.readCoin(input0.prevout); | ||
assert(coinEntry); // ensures that coin exists and is still unspent | ||
|
||
const coin = coinEntry.toCoin(input0.prevout); | ||
assert(coin.covenant.type === types.TRANSFER); | ||
|
||
// Fund the TX. | ||
// The hsd wallet is not designed to handle partially-signed TXs | ||
// or coins from outside the wallet, so a little hacking is needed. | ||
const changeAddress = await wallet.changeAddress(); | ||
const rate = await wdb.estimateFee(); | ||
const coins = await wallet.getSmartCoins(); | ||
// Add the external coin to the coin selector so we don't fail assertions | ||
coins.push(coin); | ||
await mtx.fund(coins, {changeAddress, rate}); | ||
// The funding mechanism starts by wiping out existing inputs | ||
// which for us includes Alice's signature. Replace it from our backup. | ||
mtx.inputs[0].inject(input0); | ||
|
||
// Rearrange outputs. | ||
// Since we added a change output, the SINGELREVERSE is now broken: | ||
This comment has been minimized.
Sorry, something went wrong. |
||
// | ||
// input 0: TRANSFER UTXO --> output 0: FINALIZE covenant | ||
// input 1: Bob's funds --- output 1: payment to Alice | ||
// (null) --- output 2: change to Bob | ||
const outputs = mtx.outputs.slice(); | ||
mtx.outputs = [outputs[0], outputs[2], outputs[1]]; | ||
|
||
// Prepare to wait for mempool acceptance (race condition) | ||
This comment has been minimized.
Sorry, something went wrong.
pinheadmz
Contributor
|
||
const waiter = new Promise((resolve, reject) => { | ||
hsd.mempool.once('tx', resolve); | ||
}); | ||
|
||
// Sign & Broadcast | ||
// Bob uses SIGHASHALL. The final TX looks like this: | ||
// | ||
// input 0: TRANSFER UTXO --> output 0: FINALIZE covenant | ||
// input 1: Bob's funds --- output 1: change to Bob | ||
// (null) --- output 2: payment to Alice | ||
const tx = await wallet.sendMTX(mtx); | ||
assert(tx.verify(mtx.view)); | ||
|
||
// Wait for mempool and check | ||
await waiter; | ||
assert(hsd.mempool.hasEntry(tx.hash())); | ||
})().then((hex) => { | ||
ipc.send('claim-paid-transfer-reply', {}); | ||
}).catch((e) => { | ||
ipc.send('claim-paid-transfer-reply', { | ||
error: e.message, | ||
}); | ||
}); | ||
}); | ||
|
||
function assert(value) { | ||
if (!value) { | ||
throw new Error('Assertion error.'); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
import React, { Component } from 'react'; | ||
import { MiniModal } from '../../components/Modal/MiniModal'; | ||
import { MTX } from 'hsd/lib/primitives'; | ||
import { connect } from 'react-redux'; | ||
import { clientStub as nClientStub } from '../../background/node/client'; | ||
import { showSuccess } from '../../ducks/notifications'; | ||
|
||
const node = nClientStub(() => require('electron').ipcRenderer); | ||
|
||
@connect( | ||
(state) => ({ | ||
network: state.node.network, | ||
}), | ||
(dispatch) => ({ | ||
showSuccess: (message) => dispatch(showSuccess(message)), | ||
}), | ||
) | ||
export default class ClaimNameForPayment extends Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
step: 0, | ||
hex: '', | ||
}; | ||
} | ||
|
||
onClickVerify = () => { | ||
try { | ||
const {network} = this.props; | ||
const mtx = MTX.decode(Buffer.from(this.state.hex, 'hex')); | ||
const firstOutput = mtx.outputs[0]; | ||
const nameReceiveAddr = firstOutput.address.toString(network); | ||
const name = firstOutput.covenant.items[2].toString('ascii'); | ||
const secondOutput = mtx.outputs[1]; | ||
const fundingAddr = secondOutput.address.toString(network); | ||
const price = secondOutput.value; | ||
|
||
this.setState({ | ||
step: 1, | ||
name, | ||
nameReceiveAddr, | ||
fundingAddr, | ||
price, | ||
}); | ||
} catch (e) { | ||
this.setState({ | ||
hexError: 'Invalid hex value.', | ||
}); | ||
} | ||
}; | ||
|
||
onClickTransfer = async () => { | ||
try { | ||
await node.claimPaidTransfer(this.state.hex); | ||
this.props.onClose(); | ||
this.props.showSuccess('Successfully claimed paid transfer. Please wait 1 block for the name to appear.'); | ||
} catch (e) { | ||
this.setState({ | ||
transferError: e.message, | ||
}); | ||
} | ||
}; | ||
|
||
render() { | ||
return ( | ||
<MiniModal title="Claim Name For Payment" onClose={this.props.onClose}> | ||
{this.renderSteps()} | ||
</MiniModal> | ||
); | ||
} | ||
|
||
renderSteps() { | ||
switch (this.state.step) { | ||
case 0: | ||
return this.renderEnterHex(); | ||
case 1: | ||
return this.renderVerify(); | ||
} | ||
} | ||
|
||
renderEnterHex() { | ||
return ( | ||
<> | ||
<p> | ||
If your counterparty has sent you a name transfer for payment, | ||
paste the hex string into the box below to verify it and claim | ||
your name. | ||
</p> | ||
|
||
{this.state.hexError && ( | ||
<p className="claim-name-for-payment-invalid"> | ||
{this.state.hexError} | ||
</p> | ||
)} | ||
|
||
<div className="import-enter__textarea-container"> | ||
<textarea | ||
className="import_enter_textarea" | ||
value={this.state.hex} | ||
onChange={(e) => this.setState({ | ||
hex: e.target.value, | ||
})} | ||
rows={8} | ||
/> | ||
</div> | ||
|
||
<div className="send__actions"> | ||
<button | ||
className="send__cta-btn" | ||
onClick={this.onClickVerify} | ||
disabled={!this.state.hex.length} | ||
> | ||
Verify | ||
</button> | ||
</div> | ||
</> | ||
); | ||
} | ||
|
||
renderVerify() { | ||
return ( | ||
<> | ||
<p> | ||
Please verify all the information below. If anything looks invalid, | ||
please close this window. | ||
</p> | ||
|
||
{this.state.transferError && ( | ||
<p className="claim-name-for-payment-invalid"> | ||
{this.state.transferError} | ||
</p> | ||
)} | ||
|
||
<dl className="claim-name-for-payment-verification"> | ||
<dt>Name</dt> | ||
<dd>{this.state.name}</dd> | ||
<dt>Address Receiving Name</dt> | ||
<dd>{this.state.nameReceiveAddr}</dd> | ||
<dt>Address Receiving Funds</dt> | ||
<dd>{this.state.fundingAddr}</dd> | ||
<dt>Price</dt> | ||
<dd>{this.state.price} HNS</dd> | ||
</dl> | ||
|
||
<div className="claim-name-for-payment__verification-buttons"> | ||
<button | ||
className="abort" | ||
onClick={this.props.onClose} | ||
> | ||
Abort | ||
</button> | ||
<button | ||
className="pay-and-transfer" | ||
onClick={this.onClickTransfer} | ||
> | ||
Pay and Transfer | ||
</button> | ||
</div> | ||
</> | ||
); | ||
} | ||
} |
To make this robust in case someone is transferring a claimed name that had a
weak
flag, see https://github.com/handshake-org/hsd/blob/master/lib/wallet/wallet.js#L3032-L3045