@@ -0,0 +1,138 @@
1+import React, { useState } from 'react';
2+import { Grid, Text } from 'indigo-react';
3+import { useWallet } from 'store/wallet';
4+import { usePointCursor } from 'store/pointCursor';
5+
6+import { useLocalRouter } from 'lib/LocalRouter';
7+
8+import ViewHeader from 'components/ViewHeader';
9+import View from 'components/View';
10+import WarningBox from 'components/WarningBox';
11+
12+import CopiableAddressWrap from 'components/CopiableAddressWrap';
13+import Highlighted from 'components/Highlighted';
14+
15+import { PsbtInput } from 'form/Inputs';
16+import BridgeForm from 'form/BridgeForm';
17+
18+import * as need from 'lib/need';
19+import * as bitcoin from 'bitcoinjs-lib';
20+import ob from 'urbit-ob';
21+import { Buffer } from 'buffer';
22+import { composeValidator, buildPsbtValidator } from 'form/validators';
23+
24+export default function Bitcoin() {
25+ const { pop } = useLocalRouter();
26+ const { urbitWallet } = useWallet();
27+ const { pointCursor } = usePointCursor();
28+
29+ const empty = new bitcoin.Psbt();
30+
31+ const [signedTx, setSignedTx] = useState('');
32+ const [psbt, setPsbt] = useState(empty);
33+
34+ const pointName = ob.patp(need.point(pointCursor));
35+
36+ const privKey = need.urbitWallet(urbitWallet).bitcoin.keys.private;
37+ const pubKey = need.urbitWallet(urbitWallet).bitcoin.keys.public;
38+ const chain = need.urbitWallet(urbitWallet).bitcoin.keys.chain;
39+
40+ const hd = bitcoin.bip32.fromPrivateKey(
41+ Buffer.from(privKey, 'hex'),
42+ Buffer.from(chain, 'hex'),
43+ bitcoin.networks.bitcoin
44+ );
45+
46+ const xPub = bitcoin.bip32
47+ .fromPublicKey(
48+ Buffer.from(pubKey, 'hex'),
49+ Buffer.from(chain, 'hex'),
50+ bitcoin.networks.bitcoin
51+ )
52+ .toBase58();
53+
54+ const validate = composeValidator({
55+ unsignedTransaction: buildPsbtValidator(hd),
56+ });
57+
58+ const onValues = ({ valid, values, form }) => {
59+ if (valid) {
60+ const newPsbt = bitcoin.Psbt.fromHex(values.unsignedTransaction);
61+ const hex = newPsbt
62+ .signAllInputsHD(hd)
63+ .finalizeAllInputs()
64+ .extractTransaction()
65+ .toHex();
66+
67+ if (psbt.toHex() !== newPsbt.toHex()) {
68+ setPsbt(newPsbt);
69+ }
70+ setSignedTx(hex);
71+ } else {
72+ setSignedTx('');
73+ }
74+ };
75+
76+ return (
77+ <View pop={pop} inset>
78+ <Grid className="mt4">
79+ <Grid.Item full as={ViewHeader}>
80+ Bitcoin
81+ </Grid.Item>
82+ <Grid.Item full as={WarningBox}>
83+ We do not recommend using this feature for significant amounts of
84+ money, since your private keys are exposed to the browser.
85+ </Grid.Item>
86+ <Grid.Item full className="mt4" as={Text}>
87+ Below is the bitcoin extended public key derived from your point
88+ {' ' + pointName}. You can paste it to Landscape to generate an
89+ unsigned transaction.
90+ </Grid.Item>
91+ <Grid.Item full className="mt4" as={Text}>
92+ <Highlighted>
93+ <CopiableAddressWrap>{xPub}</CopiableAddressWrap>
94+ </Highlighted>
95+ </Grid.Item>
96+ <Grid.Item full className="mt4" as={Text}>
97+ Next, paste the unsigned transaction below to sign it with your
98+ private key.
99+ </Grid.Item>
100+ <BridgeForm validate={validate} onValues={onValues}>
101+ {() => (
102+ <Grid.Item
103+ full
104+ as={PsbtInput}
105+ name="unsignedTransaction"
106+ label="Unsigned transaction"
107+ className="mt4"
108+ />
109+ )}
110+ </BridgeForm>
111+ {signedTx && (
112+ <Grid.Item full className="mt4" as={Text}>
113+ Transaction outputs:
114+ </Grid.Item>
115+ )}
116+ {signedTx &&
117+ psbt.txOutputs.map(e => (
118+ <Grid.Item full className="mt4">
119+ <span>{e.address}</span>
120+ <div className="mt1 mono black f6">{e.value} Satoshis</div>
121+ </Grid.Item>
122+ ))}
123+ {signedTx && (
124+ <>
125+ <Grid.Item full className="mt4" as={ViewHeader}>
126+ Signed transaction:
127+ </Grid.Item>
128+ <Grid.Item full as={Text}>
129+ <Highlighted>
130+ <CopiableAddressWrap>{signedTx}</CopiableAddressWrap>
131+ </Highlighted>
132+ </Grid.Item>
133+ </>
134+ )}
135+ </Grid>
136+ </View>
137+ );
138+}