Skip to content

Commit

Permalink
add ability to send tx, form validation on next button
Browse files Browse the repository at this point in the history
Signed-off-by: Gregory Hill <gregorydhill@outlook.com>
  • Loading branch information
gregdhill committed Aug 20, 2020
1 parent 35db1af commit 56a8055
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 21 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Ledger BTC Wallet - React
# Ledger BTC Wallet - React [Testnet]

```bash
HTTPS=true yarn start
Expand Down
18 changes: 14 additions & 4 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export default class App extends Component<Props, State> {

constructor(props: Props) {
super(props);
// TODO: hide buttons pending form validation
this._next = this._next.bind(this);
this._prev = this._prev.bind(this);
}
Expand Down Expand Up @@ -71,6 +70,7 @@ export default class App extends Component<Props, State> {

get previousButton() {
let currentStep = this.state.currentStep;

if (currentStep > 1) {
return (
<Button
Expand All @@ -87,9 +87,19 @@ export default class App extends Component<Props, State> {
}

get nextButton() {
let { currentStep } = this.state;

if (currentStep < 3) {
let { currentStep, accounts, outputs } = this.state;

if (
currentStep === 1 &&
[...accounts].reduce(
(total, [, info]) => (total += info.checked ? 1 : 0),
0
) === 0
) {
return null;
} else if (currentStep === 2 && outputs.size === 0) {
return null;
} else if (currentStep < 3) {
return (
<Button
variant="primary"
Expand Down
15 changes: 15 additions & 0 deletions src/bitcoin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as esplora from "@interlay/esplora-btc-api";
import * as bitcoin from "bitcoinjs-lib";

export interface UTXO {
txid: string;
Expand Down Expand Up @@ -53,6 +54,20 @@ export class BitcoinApi {
};
});
}

async broadcastTx(hex: string) {
const result = await this.txApi.postTx(hex);
return result.data;
}
}

export function getTxLink(txId: string) {
return `https://www.blockchain.com/btc-testnet/tx/${txId}`;
}

export function getTxId(hex: string) {
const tx = bitcoin.Transaction.fromHex(hex);
return tx.getId();
}

export function satToBtc(sat: number) {
Expand Down
92 changes: 76 additions & 16 deletions src/components/SignAndSend.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { Component } from "react";
import { BitcoinApi, satToBtc, UTXO, AccountInfo } from "../bitcoin";
import { BitcoinApi, satToBtc, UTXO, AccountInfo, getTxLink } from "../bitcoin";
import * as ledger from "../ledger";
import { Button, Form, Jumbotron, Alert } from "react-bootstrap";
import { Button, Form, Jumbotron, Alert, Spinner } from "react-bootstrap";
import { FaExternalLinkAlt } from "react-icons/fa";

type FormControlElement =
| HTMLInputElement
Expand All @@ -20,17 +21,34 @@ interface State {
recipient: string;
satoshis: number;
txFee: number;
loading: boolean;
isSigning: boolean;
isSending: boolean;

txId?: string;
txHex?: string;
error?: Error;
}

const LoadingButton = () => (
<Button variant="primary" disabled>
<Spinner
as="span"
animation="border"
size="sm"
role="status"
aria-hidden="true"
/>
<span className="sr-only">Loading...</span>
</Button>
);

export default class SignAndSend extends Component<Props> {
state: State = {
recipient: "",
satoshis: 0,
txFee: 0,
loading: false,
isSigning: false,
isSending: false,
};

componentDidMount() {
Expand All @@ -39,9 +57,26 @@ export default class SignAndSend extends Component<Props> {
this.setState({ satoshis: total });
}

async sendTx(hex: string) {
this.setState({ isSending: true });

try {
const txId = await this.props.apiBtc.broadcastTx(hex);
this.setState({ txId: txId });
} catch (error) {
this.setState({ error: error });
}
this.setState({ isSending: false });
}

async createTransaction() {
// clear error and previous raw tx
this.setState({ error: undefined, txHex: undefined });
this.setState({
error: undefined,
txHex: undefined,
txId: undefined,
isSigning: true,
});
const { recipient, satoshis, txFee } = this.state;
try {
const { appBtc, apiBtc, accounts, outputs } = this.props;
Expand All @@ -67,6 +102,7 @@ export default class SignAndSend extends Component<Props> {
} catch (error) {
this.setState({ error: error });
}
this.setState({ isSigning: false });
}

handleChange(event: React.ChangeEvent<FormControlElement>) {
Expand All @@ -77,7 +113,7 @@ export default class SignAndSend extends Component<Props> {
}

render() {
const { satoshis } = this.state;
const { satoshis, txHex, txId } = this.state;
return (
<div>
<Jumbotron>
Expand Down Expand Up @@ -115,22 +151,46 @@ export default class SignAndSend extends Component<Props> {
</Form.Group>

<Form.Group>
<Button
variant="primary"
type="button"
onClick={() => this.createTransaction()}
>
Sign
</Button>
{!this.state.isSigning && (
<Button
variant="primary"
type="button"
onClick={() => this.createTransaction()}
>
Sign
</Button>
)}
{this.state.isSigning && <LoadingButton />}
</Form.Group>

{this.state.txHex && (
<Form.Group controlId="toHex">
{txHex && (
<Form.Group controlId="txHex">
<Form.Label>Raw Tx</Form.Label>
<Form.Control type="text" value={this.state.txHex} readOnly />
<Form.Control type="text" value={txHex} readOnly />
</Form.Group>
)}

{txHex && !txId && (
<Form.Group controlId="sendTx">
{!this.state.isSending && (
<Button
variant="primary"
type="button"
onClick={() => this.sendTx(txHex)}
>
Send
</Button>
)}
{this.state.isSending && <LoadingButton />}
</Form.Group>
)}

{txId && (
<Button onClick={() => window.open(getTxLink(txId))}>
{txId} <FaExternalLinkAlt className="ml-2" />
</Button>
)}

{this.state.error && (
<Form.Group>
<Alert key="ledgerErr" variant="danger">
Expand Down

0 comments on commit 56a8055

Please sign in to comment.