on master help sign in
Updated and consolidated Bitcoin screen
commit740f3aface6ac9838f1ee291fcfb1c7037f709b3 authorWill Galebach <willbach@gmail.com> dateTue 21 Dec 2021, 15:07 (5 years ago) parent7ad1ff5
7 files changed+236−262
src/form/Inputs.tsx+3−3 src/lib/router.js+1−5 src/views/Bitcoin/Bitcoin.js−44 src/views/Bitcoin/Bitcoin.scss+29 src/views/Bitcoin/Bitcoin.tsx+203 src/views/Bitcoin/SignTransaction.js−160 src/views/Bitcoin/Xpub.js−50
src/form/Inputs.tsx+3−3
@@ -152,10 +152,10 @@ export function PointInput({ name, size = 4, ...rest }) {
152152 );
153153}
154154
155−export function HexInput({ ...rest }) {
155+export function HexInput({ type = 'text', ...rest }) {
156156 return (
157157 <Input
158− type="text"
158+ type={type}
159159 placeholder={PLACEHOLDER_HEX}
160160 autoCapitalize="none"
161161 autoComplete="off"
@@ -181,7 +181,7 @@ export function PrivateKeyInput({ ...rest }) {
181181export function PsbtInput({ ...rest }) {
182182 return (
183183 <HexInput
184− type="text"
184+ type="textarea"
185185 placeholder=""
186186 config={{ parse: stripHexPrefix }}
187187 {...rest}
src/lib/router.js+1−5
@@ -22,9 +22,7 @@ import PartySetPoolSize from 'views/Party/PartySetPoolSize';
2222import InviteCohort from 'views/Invite/Cohort';
2323import AcceptTransfer from 'views/AcceptTransfer.tsx';
2424import CancelTransfer from 'views/CancelTransfer.tsx';
25−import Bitcoin from 'views/Bitcoin/Bitcoin';
26−import Xpub from 'views/Bitcoin/Xpub';
27−import SignTransaction from 'views/Bitcoin/SignTransaction';
25+import Bitcoin from 'views/Bitcoin/Bitcoin.tsx';
2826import TransactionHistory from 'views/Transactions/TransactionHistory';
2927
3028import { ROUTE_NAMES } from './routeNames';
@@ -56,8 +54,6 @@ export const ROUTES = {
5654 [ROUTE_NAMES.RESIDENTS]: Residents,
5755 [ROUTE_NAMES.REQUESTS]: Requests,
5856 [ROUTE_NAMES.BITCOIN]: Bitcoin,
59− [ROUTE_NAMES.BITCOIN_XPUB]: Xpub,
60− [ROUTE_NAMES.BITCOIN_SIGN_TRANSACTION]: SignTransaction,
6157 [ROUTE_NAMES.TRANSACTION_HISTORY]: TransactionHistory,
6258 [ROUTE_NAMES.MIGRATE_L2]: MigrateL2,
6359 [ROUTE_NAMES.OPS]: Ops,
src/views/Bitcoin/Bitcoin.jsdeleted−44
@@ -1,44 +0,0 @@
1−import React, { useCallback } from 'react';
2−import { Grid } from 'indigo-react';
3−
4−import { useLocalRouter } from 'lib/LocalRouter';
5−
6−import View from 'components/View';
7−import { ForwardButton } from 'components/Buttons';
8−
9−export default function Bitcoin() {
10− const { pop, push, names } = useLocalRouter();
11−
12− const goXpub = useCallback(() => push(names.BITCOIN_XPUB), [push, names]);
13− const goSignTransaction = useCallback(
14− () => push(names.BITCOIN_SIGN_TRANSACTION),
15− [push, names]
16− );
17−
18− return (
19− <View pop={pop} inset>
20− <Grid.Item full className="mb4 f5">
21− Bitcoin
22− </Grid.Item>
23− <Grid.Divider />
24− <Grid className="pt2">
25− <Grid.Item
26− full
27− as={ForwardButton}
28− detail="Xpub for Landscape wallet"
29− onClick={goXpub}>
30− Xpub
31− </Grid.Item>
32− <Grid.Divider />
33− <Grid.Item
34− full
35− as={ForwardButton}
36− className="mt1"
37− detail="Sign transaction from Landscape"
38− onClick={goSignTransaction}>
39− Sign Transaction
40− </Grid.Item>
41− </Grid>
42− </View>
43− );
44−}
src/views/Bitcoin/Bitcoin.scssnew file+29
@@ -0,0 +1,29 @@
1+@import 'style/shared/variables.scss';
2+
3+.bitcoin {
4+ font-size: 14px;
5+
6+ .copy-button {
7+ color: white;
8+ background-color: black;
9+ cursor: pointer;
10+ border: none;
11+ border-radius: $medium-border-radius;
12+ font-family: inherit;
13+ font-size: inherit;
14+ height: 32px;
15+ width: 128px;
16+
17+ svg {
18+ margin-right: 4px;
19+ }
20+ }
21+
22+ .body-pane {
23+ height: 300px;
24+
25+ .label {
26+ font-weight: 500;
27+ }
28+ }
29+}
src/views/Bitcoin/Bitcoin.tsxnew file+203
@@ -0,0 +1,203 @@
1+import React, { useMemo, useState } from 'react';
2+import { Grid, Text } from 'indigo-react';
3+import { Box, Button, Row, Icon } from '@tlon/indigo-react';
4+
5+import { useWallet } from 'store/wallet';
6+
7+import * as bitcoin from 'bitcoinjs-lib';
8+import { useLocalRouter } from 'lib/LocalRouter';
9+import { COMMANDS, useFlowCommand } from 'lib/flowCommand';
10+import * as need from 'lib/need';
11+import useCopiable from 'lib/useCopiable';
12+
13+import View from 'components/View';
14+import L2BackHeader from 'components/L2/Headers/L2BackHeader';
15+import Window from 'components/L2/Window/Window';
16+import HeaderPane from 'components/L2/Window/HeaderPane';
17+import BodyPane from 'components/L2/Window/BodyPane';
18+import Highlighted from 'components/Highlighted';
19+import CopiableAddressWrap from 'components/copiable/CopiableAddressWrap';
20+import ViewHeader from 'components/ViewHeader';
21+import SubmitButton from 'form/SubmitButton';
22+import BridgeForm from 'form/BridgeForm';
23+import { PsbtInput } from 'form/Inputs';
24+import { composeValidator, buildPsbtValidator } from 'form/validators';
25+
26+import './Bitcoin.scss';
27+
28+const BITCOIN_MAINNET_INFO = {
29+ messagePrefix: '\x18Bitcoin Signed Message:\n',
30+ bech32: 'bc',
31+ bip32: {
32+ public: 0x04b24746,
33+ private: 0x04b2430c,
34+ },
35+ pubKeyHash: 0x00,
36+ scriptHash: 0x05,
37+ wif: 0x80,
38+};
39+
40+const BITCOIN_TESTNET_INFO = {
41+ messagePrefix: '\x18Bitcoin Signed Message:\n',
42+ bech32: 'tb',
43+ bip32: {
44+ public: 0x045f1cf6,
45+ private: 0x045f18bc,
46+ },
47+ pubKeyHash: 0x6f,
48+ scriptHash: 0xc4,
49+ wif: 0xef,
50+};
51+
52+export default function Bitcoin() {
53+ const { pop }: any = useLocalRouter();
54+ const { urbitWallet }: any = useWallet();
55+ const flow = useFlowCommand();
56+
57+ const [signedTx, setSignedTx] = useState('');
58+ const [psbt, setPsbt] = useState({});
59+
60+ // set to false for BTC testnet
61+ const isMainnet = true;
62+ const { xpub: zpub } = need.urbitWallet(urbitWallet).bitcoinMainnet.keys;
63+ const { xpub: vpub } = need.urbitWallet(urbitWallet).bitcoinTestnet.keys;
64+
65+ const { xprv: zprv } = need.urbitWallet(urbitWallet).bitcoinMainnet.keys;
66+ const { xprv: vprv } = need.urbitWallet(urbitWallet).bitcoinTestnet.keys;
67+
68+ const validate = composeValidator({
69+ unsignedTransaction: buildPsbtValidator(),
70+ });
71+
72+ const xpub = useMemo(() => (isMainnet ? zpub : vpub), [
73+ isMainnet,
74+ zpub,
75+ vpub,
76+ ]);
77+
78+ const [doCopy, didCopy] = useCopiable(xpub);
79+
80+ const onSubmit = values => {
81+ const newPsbt = bitcoin.Psbt.fromBase64(values.unsignedTransaction);
82+
83+ const isTestnet = newPsbt.data.inputs[0].bip32Derivation[0].path.startsWith(
84+ "m/84'/1'/0'/"
85+ );
86+
87+ const derivationPrefix = isTestnet ? "m/84'/1'/0'/" : "m/84'/0'/0'/";
88+
89+ const btcWallet = isTestnet
90+ ? bitcoin.bip32.fromBase58(vprv, BITCOIN_TESTNET_INFO)
91+ : bitcoin.bip32.fromBase58(zprv, BITCOIN_MAINNET_INFO);
92+
93+ try {
94+ const hex = newPsbt.data.inputs
95+ .reduce((psbt, input, idx) => {
96+ // removing already derived part, eg m/84'/0'/0'/0/0 becomes 0/0
97+ const path = input.bip32Derivation[0].path
98+ .split(derivationPrefix)
99+ .join('');
100+ const prv = btcWallet.derivePath(path).privateKey;
101+ return psbt.signInput(idx, bitcoin.ECPair.fromPrivateKey(prv));
102+ }, newPsbt)
103+ .finalizeAllInputs()
104+ .extractTransaction()
105+ .toHex();
106+ setPsbt(newPsbt);
107+ setSignedTx(hex);
108+ } catch (error) {
109+ return {
110+ unsignedTransaction: 'Cannot sign transaction, mismatching keys.',
111+ };
112+ }
113+ };
114+
115+ const initialValues = {};
116+ if (flow && COMMANDS.BITCOIN === flow.kind) {
117+ initialValues.unsignedTransaction = flow.utx;
118+ }
119+
120+ return (
121+ <View
122+ className="bitcoin"
123+ pop={pop}
124+ inset
125+ header={<L2BackHeader />}
126+ hideBack>
127+ <Window className="os-home">
128+ <HeaderPane>
129+ <Row className="header-row">
130+ <h5>Bitcoin</h5>
131+ <Button onClick={doCopy} className="copy-button">
132+ {didCopy ? (
133+ 'Copied!'
134+ ) : (
135+ <>
136+ <Icon icon="Copy" color="#fff" />
137+ Copy Xpub
138+ </>
139+ )}
140+ </Button>
141+ </Row>
142+ </HeaderPane>
143+ <BodyPane>
144+ <Grid className="w-full h-full">
145+ <BridgeForm
146+ validate={validate}
147+ initialValues={initialValues}
148+ onSubmit={onSubmit}>
149+ {({ handleSubmit }) => (
150+ <Grid.Item full className="h-full flex-col justify-between">
151+ <Box className="full">
152+ <Box className="label full">Unsigned Transaction</Box>
153+ <Grid.Item
154+ full
155+ placeholder="0x"
156+ as={PsbtInput}
157+ name="unsignedTransaction"
158+ className="mt2"
159+ />
160+ </Box>
161+ <Grid.Item
162+ full
163+ solid
164+ center
165+ className="mt2"
166+ accessory=""
167+ handleSubmit={handleSubmit}
168+ as={SubmitButton}>
169+ Sign Bitcoin Transaction
170+ </Grid.Item>
171+ </Grid.Item>
172+ )}
173+ </BridgeForm>
174+ {signedTx && (
175+ <Grid.Item full className="mt4" as={Text}>
176+ Transaction outputs:
177+ </Grid.Item>
178+ )}
179+ {signedTx &&
180+ psbt.txOutputs.map(e => (
181+ <Grid.Item full className="mt4" key={e.address}>
182+ <span>{e.address}</span>
183+ <div className="mt1 mono black f6">{e.value} sats</div>
184+ </Grid.Item>
185+ ))}
186+ {signedTx && (
187+ <>
188+ <Grid.Item full className="mt4" as={ViewHeader}>
189+ Signed transaction:
190+ </Grid.Item>
191+ <Grid.Item full as={Text}>
192+ <Highlighted>
193+ <CopiableAddressWrap>{signedTx}</CopiableAddressWrap>
194+ </Highlighted>
195+ </Grid.Item>
196+ </>
197+ )}
198+ </Grid>
199+ </BodyPane>
200+ </Window>
201+ </View>
202+ );
203+}
src/views/Bitcoin/SignTransaction.jsdeleted−160
@@ -1,160 +0,0 @@
1−import React, { useState } from 'react';
2−import { Grid, Text } from 'indigo-react';
3−
4−import { useLocalRouter } from 'lib/LocalRouter';
5−import { COMMANDS, useFlowCommand } from 'lib/flowCommand';
6−
7−import View from 'components/View';
8−import Highlighted from 'components/Highlighted';
9−import CopiableAddressWrap from 'components/copiable/CopiableAddressWrap';
10−import ViewHeader from 'components/ViewHeader';
11−import SubmitButton from 'form/SubmitButton';
12−
13−import { useWallet } from 'store/wallet';
14−
15−import * as need from 'lib/need';
16−import * as bitcoin from 'bitcoinjs-lib';
17−
18−import BridgeForm from 'form/BridgeForm';
19−import { PsbtInput } from 'form/Inputs';
20−
21−import { composeValidator, buildPsbtValidator } from 'form/validators';
22−
23−const BITCOIN_MAINNET_INFO = {
24− messagePrefix: '\x18Bitcoin Signed Message:\n',
25− bech32: 'bc',
26− bip32: {
27− public: 0x04b24746,
28− private: 0x04b2430c,
29− },
30− pubKeyHash: 0x00,
31− scriptHash: 0x05,
32− wif: 0x80,
33−};
34−
35−const BITCOIN_TESTNET_INFO = {
36− messagePrefix: '\x18Bitcoin Signed Message:\n',
37− bech32: 'tb',
38− bip32: {
39− public: 0x045f1cf6,
40− private: 0x045f18bc,
41− },
42− pubKeyHash: 0x6f,
43− scriptHash: 0xc4,
44− wif: 0xef,
45−};
46−
47−export default function SignTransaction() {
48− const { pop } = useLocalRouter();
49− const { urbitWallet } = useWallet();
50− const flow = useFlowCommand();
51−
52− const [signedTx, setSignedTx] = useState('');
53− const [psbt, setPsbt] = useState({});
54−
55− const { xprv: zprv } = need.urbitWallet(urbitWallet).bitcoinMainnet.keys;
56− const { xprv: vprv } = need.urbitWallet(urbitWallet).bitcoinTestnet.keys;
57−
58− const validate = composeValidator({
59− unsignedTransaction: buildPsbtValidator(),
60− });
61−
62− const onSubmit = values => {
63− const newPsbt = bitcoin.Psbt.fromBase64(values.unsignedTransaction);
64−
65− const isTestnet = newPsbt.data.inputs[0].bip32Derivation[0].path.startsWith(
66− "m/84'/1'/0'/"
67− );
68−
69− const derivationPrefix = isTestnet ? "m/84'/1'/0'/" : "m/84'/0'/0'/";
70−
71− const btcWallet = isTestnet
72− ? bitcoin.bip32.fromBase58(vprv, BITCOIN_TESTNET_INFO)
73− : bitcoin.bip32.fromBase58(zprv, BITCOIN_MAINNET_INFO);
74−
75− try {
76− const hex = newPsbt.data.inputs
77− .reduce((psbt, input, idx) => {
78− // removing already derived part, eg m/84'/0'/0'/0/0 becomes 0/0
79− const path = input.bip32Derivation[0].path
80− .split(derivationPrefix)
81− .join('');
82− const prv = btcWallet.derivePath(path).privateKey;
83− return psbt.signInput(idx, bitcoin.ECPair.fromPrivateKey(prv));
84− }, newPsbt)
85− .finalizeAllInputs()
86− .extractTransaction()
87− .toHex();
88− setPsbt(newPsbt);
89− setSignedTx(hex);
90− } catch (error) {
91− return {
92− unsignedTransaction: 'Cannot sign transaction, mismatching keys.',
93− };
94− }
95− };
96−
97− const initialValues = {};
98− if (flow && COMMANDS.BITCOIN === flow.kind) {
99− initialValues.unsignedTransaction = flow.utx;
100− }
101−
102− return (
103− <View pop={pop} inset>
104− <Grid className="mt4">
105− <Grid.Item full className="mt4" as={Text}>
106− Paste unsigned transactions from Landscape here
107− </Grid.Item>
108− <BridgeForm
109− validate={validate}
110− initialValues={initialValues}
111− onSubmit={onSubmit}>
112− {({ handleSubmit }) => (
113− <>
114− <Grid.Item
115− full
116− as={PsbtInput}
117− name="unsignedTransaction"
118− label="Unsigned transaction"
119− className="mt4"
120− />
121− <Grid.Item
122− full
123− solid
124− className="mt2"
125− accessory=""
126− handleSubmit={handleSubmit}
127− as={SubmitButton}>
128− Sign Transaction
129− </Grid.Item>
130− </>
131− )}
132− </BridgeForm>
133− {signedTx && (
134− <Grid.Item full className="mt4" as={Text}>
135− Transaction outputs:
136− </Grid.Item>
137− )}
138− {signedTx &&
139− psbt.txOutputs.map(e => (
140− <Grid.Item full className="mt4" key={e.address}>
141− <span>{e.address}</span>
142− <div className="mt1 mono black f6">{e.value} sats</div>
143− </Grid.Item>
144− ))}
145− {signedTx && (
146− <>
147− <Grid.Item full className="mt4" as={ViewHeader}>
148− Signed transaction:
149− </Grid.Item>
150− <Grid.Item full as={Text}>
151− <Highlighted>
152− <CopiableAddressWrap>{signedTx}</CopiableAddressWrap>
153− </Highlighted>
154− </Grid.Item>
155− </>
156− )}
157− </Grid>
158− </View>
159− );
160−}
src/views/Bitcoin/Xpub.jsdeleted−50
@@ -1,50 +0,0 @@
1−import React from 'react';
2−import { Grid, Text } from 'indigo-react';
3−import { useWallet } from 'store/wallet';
4−
5−import { useLocalRouter } from 'lib/LocalRouter';
6−
7−import ViewHeader from 'components/ViewHeader';
8−import View from 'components/View';
9−import WarningBox from 'components/WarningBox';
10−
11−import CopiableAddressWrap from 'components/copiable/CopiableAddressWrap';
12−import Highlighted from 'components/Highlighted';
13−
14−import * as need from 'lib/need';
15−
16−export default function Xpub() {
17− const { pop } = useLocalRouter();
18− const { urbitWallet } = useWallet();
19−
20− const { xpub: zpub } = need.urbitWallet(urbitWallet).bitcoinMainnet.keys;
21− const { xpub: vpub } = need.urbitWallet(urbitWallet).bitcoinTestnet.keys;
22−
23− // set to false for BTC testnet
24− const isMainnet = true;
25−
26− return (
27− <View pop={pop} inset>
28− <Grid className="mt4">
29− <Grid.Item full as={ViewHeader}>
30− Bitcoin
31− </Grid.Item>
32− <Grid.Item full as={WarningBox}>
33− We do not recommend using this feature for significant amounts of
34− money, since your private keys are exposed to the browser.
35− </Grid.Item>
36− <Grid.Item full className="mt4" as={Text}>
37− The public key derived from your master ticket.
38− </Grid.Item>
39− <Grid.Item full className="mt4" as={Text}>
40− Paste it into landscape while setting up your wallet.
41− </Grid.Item>
42− <Grid.Item full className="mt4" as={Text}>
43− <Highlighted>
44− <CopiableAddressWrap>{isMainnet ? zpub : vpub}</CopiableAddressWrap>
45− </Highlighted>
46− </Grid.Item>
47− </Grid>
48− </View>
49− );
50−}