mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-09-21 07:28:30 +03:00
btc: add signing through bridge
This commit is contained in:
parent
aa7811c2f5
commit
4bd945ee6e
201
pkg/btc-wallet/src/js/components/lib/bridgeInvoice.js
Normal file
201
pkg/btc-wallet/src/js/components/lib/bridgeInvoice.js
Normal file
@ -0,0 +1,201 @@
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Icon,
|
||||
StatelessTextInput as Input,
|
||||
Row,
|
||||
Text,
|
||||
Button,
|
||||
Col,
|
||||
} from '@tlon/indigo-react';
|
||||
|
||||
import * as bitcoin from 'bitcoinjs-lib';
|
||||
import * as kg from 'urbit-key-generation';
|
||||
|
||||
import Sent from './sent.js'
|
||||
|
||||
import { satsToCurrency } from '../../lib/util.js';
|
||||
|
||||
window.bitcoin = bitcoin;
|
||||
window.kg = kg;
|
||||
|
||||
export default class BridgeInvoice extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
externalPsbt: '',
|
||||
ready: false,
|
||||
error: false,
|
||||
sent: false,
|
||||
};
|
||||
|
||||
this.checkExternalPsbt = this.checkExternalPsbt.bind(this);
|
||||
this.broadCastTx = this.broadCastTx.bind(this);
|
||||
this.sendBitcoin = this.sendBitcoin.bind(this);
|
||||
}
|
||||
|
||||
broadCastTx(psbtHex) {
|
||||
let command = {
|
||||
'broadcast-tx': psbtHex
|
||||
}
|
||||
return this.props.api.btcWalletCommand(command)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.open('https://bridge.urbit.org/?kind=btc&utx=' + this.props.psbt);
|
||||
}
|
||||
|
||||
sendBitcoin(psbt) {
|
||||
|
||||
try {
|
||||
const hex = bitcoin.Psbt.fromBase64(psbt).validateSignaturesOfAllInputs().toHex();
|
||||
this.broadCastTx(hex).then(res => this.setState({sent: true}));
|
||||
}
|
||||
|
||||
catch(e) {
|
||||
this.setState({error: true});
|
||||
}
|
||||
}
|
||||
|
||||
checkExternalPsbt(e){
|
||||
let externalPsbt = e.target.value;
|
||||
let ready = (externalPsbt.length > 0);
|
||||
let error = false;
|
||||
this.setState({externalPsbt, ready, error});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { stopSending, payee, denomination, satsAmount, psbt, currencyRates } = this.props;
|
||||
const { sent, error, externalPsbt } = this.state;
|
||||
|
||||
let inputColor = 'black';
|
||||
let inputBg = 'white';
|
||||
let inputBorder = 'lightGray';
|
||||
|
||||
if (error) {
|
||||
inputColor = 'red';
|
||||
inputBg = 'veryLightRed';
|
||||
inputBorder = 'red';
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{ sent ?
|
||||
<Sent
|
||||
payee={payee}
|
||||
stopSending={stopSending}
|
||||
denomination={denomination}
|
||||
currencyRates={currencyRates}
|
||||
satsAmount={satsAmount}
|
||||
/> :
|
||||
<Col
|
||||
height='400px'
|
||||
width='100%'
|
||||
backgroundColor='white'
|
||||
borderRadius='48px'
|
||||
mb={5}
|
||||
p={5}
|
||||
>
|
||||
<Row
|
||||
justifyContent='space-between'
|
||||
alignItems='center'
|
||||
>
|
||||
<Text bold fontSize={1}>Invoice</Text>
|
||||
<Icon
|
||||
icon='X'
|
||||
cursor='pointer'
|
||||
onClick={() => stopSending()}
|
||||
/>
|
||||
</Row>
|
||||
<Box
|
||||
mt={4}
|
||||
backgroundColor='rgba(0, 159, 101, 0.05)'
|
||||
borderRadius='12px'
|
||||
>
|
||||
<Box
|
||||
padding={4}
|
||||
>
|
||||
<Row>
|
||||
<Text fontSize='14px' fontWeight='500'>You are sending</Text>
|
||||
</Row>
|
||||
<Row
|
||||
mt={2}
|
||||
>
|
||||
<Text
|
||||
color='green'
|
||||
fontSize='14px'
|
||||
>{satsToCurrency(satsAmount, denomination, currencyRates)}</Text>
|
||||
<Text
|
||||
ml={2}
|
||||
fontSize='14px'
|
||||
color='gray'
|
||||
>{`${satsAmount} sats`}</Text>
|
||||
</Row>
|
||||
<Row
|
||||
mt={2}
|
||||
>
|
||||
<Text fontSize='14px'>To:</Text>
|
||||
<Text
|
||||
ml={2}
|
||||
fontSize='14px'
|
||||
style={{'display': 'block', 'overflow-wrap': 'anywhere'}}
|
||||
>{payee}</Text>
|
||||
</Row>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box mt={3}>
|
||||
<Text fontSize='14px' fontWeight='500'>
|
||||
Bridge signed transaction
|
||||
</Text>
|
||||
</Box>
|
||||
<Box mt={1} mb={2}>
|
||||
<Text gray fontSize='14px'>
|
||||
Copy the signed transaction from Bridge
|
||||
</Text>
|
||||
</Box>
|
||||
<Input
|
||||
value={this.state.externalPsbt}
|
||||
fontSize='14px'
|
||||
placeholder='cHNidP8BAHEBAAAAAXqmzdCZ4uv...'
|
||||
autoCapitalize='none'
|
||||
autoCorrect='off'
|
||||
color={inputColor}
|
||||
backgroundColor={inputBg}
|
||||
borderColor={inputBorder}
|
||||
style={{'line-height': '4'}}
|
||||
onChange={this.checkExternalPsbt}
|
||||
/>
|
||||
{error &&
|
||||
<Row>
|
||||
<Text
|
||||
fontSize='14px'
|
||||
color='red'
|
||||
mt={2}>
|
||||
Invalid signed bitcoin transaction
|
||||
</Text>
|
||||
</Row>
|
||||
}
|
||||
<Row
|
||||
flexDirection='row-reverse'
|
||||
mt={4}
|
||||
>
|
||||
<Button
|
||||
primary
|
||||
children='Send BTC'
|
||||
mr={3}
|
||||
fontSize={1}
|
||||
borderRadius='24px'
|
||||
py='24px'
|
||||
px='24px'
|
||||
onClick={() => this.sendBitcoin(externalPsbt)}
|
||||
disabled={!this.state.ready || error}
|
||||
style={{cursor: (this.state.ready && !error) ? "pointer" : "default"}}
|
||||
/>
|
||||
</Row>
|
||||
</Col>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
@ -154,6 +154,7 @@ export default class Invoice extends Component {
|
||||
<Text
|
||||
ml={2}
|
||||
fontSize='14px'
|
||||
style={{'display': 'block', 'overflow-wrap': 'anywhere'}}
|
||||
>{payee}</Text>
|
||||
</Row>
|
||||
</Box>
|
||||
@ -201,7 +202,7 @@ export default class Invoice extends Component {
|
||||
px='24px'
|
||||
onClick={() => this.sendBitcoin(this.state.masterTicket, psbt)}
|
||||
disabled={!this.state.ready || error}
|
||||
style={{cursor: this.state.ready ? "pointer" : "default"}}
|
||||
style={{cursor: (this.state.ready && !error) ? "pointer" : "default"}}
|
||||
/>
|
||||
</Row>
|
||||
</Col>
|
||||
|
@ -12,8 +12,10 @@ import {
|
||||
} from '@tlon/indigo-react';
|
||||
|
||||
import Invoice from './invoice.js'
|
||||
import BridgeInvoice from './bridgeInvoice.js'
|
||||
import FeePicker from './feePicker.js'
|
||||
import Error from './error.js'
|
||||
import Signer from './signer.js'
|
||||
|
||||
import { validate } from 'bitcoin-address-validation';
|
||||
|
||||
@ -45,11 +47,15 @@ export default class Send extends Component {
|
||||
feeValue: "mid",
|
||||
showModal: false,
|
||||
note: '',
|
||||
choosingSignMethod: false,
|
||||
signMethod: 'Sign Transaction',
|
||||
};
|
||||
|
||||
this.initPayment = this.initPayment.bind(this);
|
||||
this.checkPayee = this.checkPayee.bind(this);
|
||||
this.feeSelect = this.feeSelect.bind(this);
|
||||
this.toggleSignMethod = this.toggleSignMethod.bind(this);
|
||||
this.setSignMethod = this.setSignMethod.bind(this);
|
||||
}
|
||||
|
||||
feeSelect(which) {
|
||||
@ -57,24 +63,28 @@ export default class Send extends Component {
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
if (this.props.network === 'bitcoin'){
|
||||
// TODO switch this to bitcoin
|
||||
if (this.props.network === 'testnet'){
|
||||
let url = "https://bitcoiner.live/api/fees/estimates/latest";
|
||||
fetch(url).then(res => res.json()).then(n => {
|
||||
let estimates = Object.keys(n.estimates);
|
||||
let mid = Math.floor(estimates.length/2)
|
||||
let high = estimates.length - 1;
|
||||
console.log(n);
|
||||
this.setState({
|
||||
feeChoices: {
|
||||
high: [30, n.estimates[30]["sat_per_vbyte"]],
|
||||
mid: [360, n.estimates[360]["sat_per_vbyte"]],
|
||||
low: [1440, n.estimates[1440]["sat_per_vbyte"]],
|
||||
low: [30, n.estimates[30]["sat_per_vbyte"]],
|
||||
mid: [180, n.estimates[180]["sat_per_vbyte"]],
|
||||
high: [360, n.estimates[360]["sat_per_vbyte"]],
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
setSignMethod(signMethod) {
|
||||
this.setState({signMethod});
|
||||
}
|
||||
|
||||
checkPayee(e){
|
||||
store.handleEvent({data: {error: ''}});
|
||||
|
||||
@ -125,6 +135,10 @@ export default class Send extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
toggleSignMethod(toggle) {
|
||||
this.setState({choosingSignMethod: !toggle});
|
||||
}
|
||||
|
||||
initPayment() {
|
||||
if (this.state.payeeType === 'ship') {
|
||||
let command = {
|
||||
@ -180,13 +194,13 @@ export default class Send extends Component {
|
||||
|
||||
|
||||
const { api, value, conversion, stopSending, denomination, psbt, currencyRates, error } = this.props;
|
||||
const { denomAmount, satsAmount, signing, payee } = this.state;
|
||||
const { denomAmount, satsAmount, signing, payee, choosingSignMethod, signMethod } = this.state;
|
||||
|
||||
const signReady = (this.state.ready && (parseInt(this.state.satsAmount) > 0)) && !signing;
|
||||
|
||||
return (
|
||||
<>
|
||||
{ (signing && psbt) ?
|
||||
let invoice = null;
|
||||
if (signMethod === 'Sign Transaction') {
|
||||
invoice =
|
||||
<Invoice
|
||||
api={api}
|
||||
psbt={psbt}
|
||||
@ -195,7 +209,23 @@ export default class Send extends Component {
|
||||
payee={payee}
|
||||
denomination={denomination}
|
||||
satsAmount={satsAmount}
|
||||
/> :
|
||||
/>
|
||||
} else if (signMethod === 'Sign with Bridge') {
|
||||
invoice =
|
||||
<BridgeInvoice
|
||||
api={api}
|
||||
psbt={psbt}
|
||||
currencyRates={currencyRates}
|
||||
stopSending={stopSending}
|
||||
payee={payee}
|
||||
denomination={denomination}
|
||||
satsAmount={satsAmount}
|
||||
/>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{ (signing && psbt) ? invoice :
|
||||
<Col
|
||||
width='100%'
|
||||
backgroundColor='white'
|
||||
@ -366,26 +396,37 @@ export default class Send extends Component {
|
||||
<Row
|
||||
flexDirection='row-reverse'
|
||||
alignItems="center"
|
||||
>
|
||||
<Button
|
||||
primary
|
||||
children='Sign Transaction'
|
||||
fontSize={1}
|
||||
fontWeight='bold'
|
||||
borderRadius='24px'
|
||||
mt={4}
|
||||
py='24px'
|
||||
px='24px'
|
||||
onClick={this.initPayment}
|
||||
color={signReady ? "white" : "lighterGray"}
|
||||
backgroundColor={signReady ? "blue" : "veryLightGray"}
|
||||
disabled={!signReady}
|
||||
border="none"
|
||||
style={{cursor: signReady ? "pointer" : "default"}}
|
||||
/>
|
||||
>
|
||||
<Signer
|
||||
signReady={signReady}
|
||||
choosingSignMethod={choosingSignMethod}
|
||||
signMethod={signMethod}
|
||||
setSignMethod={this.setSignMethod}
|
||||
initPayment={this.initPayment} />
|
||||
{ (!(signing && !error)) ? null :
|
||||
<LoadingSpinner mr={2} background="midOrange" foreground="orange"/>
|
||||
}
|
||||
<Button
|
||||
width='48px'
|
||||
children={
|
||||
<Icon
|
||||
icon={choosingSignMethod ? 'X' : 'Ellipsis'}
|
||||
color={signReady ? 'blue' : 'lighterGray'}
|
||||
/>
|
||||
}
|
||||
fontSize={1}
|
||||
fontWeight='bold'
|
||||
borderRadius='24px'
|
||||
mr={2}
|
||||
py='24px'
|
||||
px='24px'
|
||||
onClick={() => this.toggleSignMethod(choosingSignMethod)}
|
||||
color={signReady ? 'white' : 'lighterGray'}
|
||||
backgroundColor={signReady ? 'rgba(33, 157, 255, 0.2)' : 'veryLightGray'}
|
||||
disabled={!signReady}
|
||||
border='none'
|
||||
style={{cursor: signReady ? 'pointer' : 'default'}} />
|
||||
</Row>
|
||||
</Col>
|
||||
}
|
||||
|
55
pkg/btc-wallet/src/js/components/lib/signer.js
Normal file
55
pkg/btc-wallet/src/js/components/lib/signer.js
Normal file
@ -0,0 +1,55 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
} from '@tlon/indigo-react';
|
||||
|
||||
export default function Signer(props) {
|
||||
const { signReady, initPayment, choosingSignMethod, signMethod, setSignMethod } = props;
|
||||
|
||||
return (
|
||||
choosingSignMethod ?
|
||||
<Box
|
||||
borderRadius='24px'
|
||||
backgroundColor='rgba(33, 157, 255, 0.2)'
|
||||
>
|
||||
<Button
|
||||
border='none'
|
||||
backgroundColor='transparent'
|
||||
fontWeight='bold'
|
||||
cursor='pointer'
|
||||
color={(signMethod === 'Sign Transaction') ? 'blue' : 'lightBlue'}
|
||||
py='24px'
|
||||
px='24px'
|
||||
onClick={() => setSignMethod('Sign Transaction')}
|
||||
children='Sign Transaction' />
|
||||
<Button
|
||||
border='none'
|
||||
backgroundColor='transparent'
|
||||
fontWeight='bold'
|
||||
cursor='pointer'
|
||||
color={(signMethod === 'Sign with Bridge') ? 'blue' : 'lightBlue'}
|
||||
py='24px'
|
||||
px='24px'
|
||||
onClick={() => setSignMethod('Sign with Bridge')}
|
||||
children='Sign with Bridge' />
|
||||
</Box>
|
||||
:
|
||||
<Button
|
||||
primary
|
||||
children={signMethod}
|
||||
fontSize={1}
|
||||
fontWeight='bold'
|
||||
borderRadius='24px'
|
||||
py='24px'
|
||||
px='24px'
|
||||
onClick={initPayment}
|
||||
color={signReady ? 'white' : 'lighterGray'}
|
||||
backgroundColor={signReady ? 'blue' : 'veryLightGray'}
|
||||
disabled={!signReady}
|
||||
border='none'
|
||||
style={{cursor: signReady ? 'pointer' : 'default'}}
|
||||
/>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user