basic proto app finished

This commit is contained in:
samepant 2020-09-20 14:08:49 -07:00
parent 5349f3b791
commit bfb63f7759
11 changed files with 319 additions and 59 deletions

View File

@ -14,7 +14,7 @@ const baseTX = {
amount: [
{
denom: "uatom",
amount: 0,
amount: "0",
},
],
},
@ -22,7 +22,7 @@ const baseTX = {
],
fee: {
amount: [],
gas: 0,
gas: "0",
},
signatures: null,
memo: "",
@ -52,8 +52,8 @@ class MultiSigForm extends React.Component {
createTransaction = (toAddress, amount, gas) => {
baseTX.value.msg[0].value.to_address = toAddress;
baseTX.value.msg[0].value.from_address = this.props.multiAddress;
baseTX.value.msg[0].value.amount[0].amount = amount;
baseTX.value.fee.gas = gas;
baseTX.value.msg[0].value.amount[0].amount = amount.toString();
baseTX.value.fee.gas = gas.toString();
return baseTX;
};

View File

@ -1,7 +1,14 @@
import axios from "axios";
import fileDownload from "js-file-download";
import React from "react";
const JsonDropDown = ({ object }) => {
const [isOpen, setOpen] = React.useState(false);
let json = object;
if (typeof object === "string") {
json = JSON.parse(object);
}
return (
<div>
<button
@ -11,9 +18,7 @@ const JsonDropDown = ({ object }) => {
>
{isOpen ? "Hide" : "Show"} JSON
</button>
{isOpen && (
<pre>{object && JSON.stringify(JSON.parse(object), null, 2)}</pre>
)}
{isOpen && <pre>{object && JSON.stringify(json, null, 2)}</pre>}
<style jsx>{`
button {
display: block;
@ -51,6 +56,7 @@ export default class TransactionSigning extends React.Component {
componentDidUpdate(prevProps, prevState) {
if (!prevProps.transaction && this.props.transaction) {
this.setState({ transaction: this.props.transaction });
console.log(JSON.parse(this.props.transaction.signatures));
}
}
@ -58,14 +64,39 @@ export default class TransactionSigning extends React.Component {
const signatureFile = e.target.files[0];
const reader = new FileReader();
reader.addEventListener("load", (event) => {
reader.addEventListener("load", async (event) => {
// do the upload
console.log(event.target.result);
const signature = JSON.parse(event.target.result);
const res = await axios.post(
`/api/transaction/${this.state.transaction.uuid}`,
{
signature: signature,
}
);
const updatedTx = res.data;
this.setState({
transaction: updatedTx,
});
});
reader.readAsText(signatureFile);
};
handleBroadcast = async () => {
this.setState({ processing: true });
const res = await axios.get(
`/api/transaction/${this.state.transaction.uuid}/broadcast`
);
this.setState({
transaction: res.data,
processing: false,
});
};
clickFileUpload = () => {
this.fileInput.current.click();
};
@ -76,7 +107,7 @@ export default class TransactionSigning extends React.Component {
<p className="required-sigs">
{(this.state.transaction &&
this.state.transaction.signatures &&
this.state.transaction.signatures.length) ||
JSON.parse(this.state.transaction.signatures).length) ||
0}{" "}
of {this.props.multi && this.props.multi.multi_threshold} signatures
uploaded
@ -98,9 +129,9 @@ export default class TransactionSigning extends React.Component {
<div className="instructions">
Download this and sign the transaction by running:
<pre>
gaiacli tx sign (path/to/unsigned.json) --multisig=
{this.props.multi && this.props.multi.address}{" "}
--output-document signed.json
gaiacli tx sign {"{"}path/to/unsigned.json{"}"} --multisig=
{this.props.multi && this.props.multi.address} --from {"{"}
local-key-name{"}"} --output-document signed.json
</pre>
</div>
<div className="json">
@ -113,19 +144,50 @@ export default class TransactionSigning extends React.Component {
{this.state.transaction && this.state.transaction.signatures && (
<div className="tx-piece">
<h3>Current signatures</h3>
{this.state.transaction.signatures.map((signature) => (
<div className="group">
<div className="json">
<JsonDropDown object={signature} />
</div>
</div>
))}
<div className="signatures">
{JSON.parse(this.state.transaction.signatures).map(
(signature, i) => (
<div className="group signature">
<h4>Sig {i + 1}</h4>
<div className="json">
<JsonDropDown object={signature} />
</div>
</div>
)
)}
</div>
</div>
)}
</div>
<button className="upload-signature" onClick={this.clickFileUpload}>
Upload Signature
</button>
{this.state.transaction && !this.state.transaction.completed_tx && (
<div>
{(this.state.transaction.signatures &&
JSON.parse(this.state.transaction.signatures).length) >=
(this.props.multi && this.props.multi.multi_threshold) ? (
<div>
{this.state.processing ? (
<button className="main broadcast processing" disabled>
Processing
</button>
) : (
<button
className="main broadcast"
onClick={this.handleBroadcast}
>
Sign and Broadcast Transaction
</button>
)}
</div>
) : (
<button
className="main upload-signature"
onClick={this.clickFileUpload}
>
Upload Signature
</button>
)}
</div>
)}
<input
id="fileInput"
type="file"
@ -134,6 +196,12 @@ export default class TransactionSigning extends React.Component {
accept=".json"
onChange={this.handleFileSelection}
/>
{this.state.transaction && this.state.transaction.completed_tx && (
<div className="transaction-response">
<h3>Transaction info</h3>
<pre>{this.state.transaction.completed_tx}</pre>
</div>
)}
<style jsx>{`
.hero {
width: 100%;
@ -168,6 +236,7 @@ export default class TransactionSigning extends React.Component {
border: 1px solid rebeccapurple;
border-radius: 1em;
padding: 1em;
margin-bottom: 1em;
width: 60%;
}
.instructions {
@ -180,6 +249,22 @@ export default class TransactionSigning extends React.Component {
.group {
display: flex;
}
.signatures {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.group.signature {
flex-direction: column;
width: 48%;
margin-bottom: 1em;
}
.group.signature .json {
width: 100%;
}
h4 {
margin: 0 0 0.5em;
}
pre {
font-size: 10px;
white-space: pre-wrap;
@ -196,11 +281,17 @@ export default class TransactionSigning extends React.Component {
padding: 0.5em 1em;
cursor: pointer;
}
button.upload-signature {
button.main {
width: 60%;
margin: 2em auto;
font-size: 1.3em;
}
button.broadcast {
background: coral;
}
button.broadcast.processing {
font-style: italic;
}
button.upload-signature:hover {
background: rebeccapurple;
}

Binary file not shown.

View File

@ -8,7 +8,8 @@ const exec = require("../utilities/promiseExec");
// - gaiacli locks its keystore when writing, so running concurrent commands will fail,
// use sequential operators (like for loops) and not async ones (like forEach)
//
// - Be wary of newline characters in the returned value :)
// - There are newline characters in some of the return values. Newline characters get
// stripped when the return value is a single line
// KEY WRAPPERS
@ -21,12 +22,15 @@ const createKey = (props) => {
return exec(`gaiacli keys add ${props.keyName} --pubkey=${props.pubkey}`);
};
const getMultiAddress = (props) => {
const getMultiAddress = async (props) => {
// props = {
// keyName: 'string',
// }
return exec(`gaiacli keys show ${props.keyName} -a`);
const rawReturn = await exec(`gaiacli keys show ${props.keyName} -a`);
const cleaned = rawReturn.replace(/(\r\n|\n|\r)/gm, "");
return cleaned;
};
const createMultiSigKey = (props) => {
@ -52,34 +56,36 @@ const broadcastTX = (props) => {
// txFilename: 'string',
// }
return exec(`gaiacli tx broadcast ${txFilename}`);
return exec(`gaiacli tx broadcast ${props.txFilename} --dry-run`);
};
const signMulti = (props) => {
// props = {
// keyName: 'string',
// signatureFiles: array of filepaths,
// unsignedFile: 'filepath'
// }
const signatureFileString = "";
let signatureFileString = "";
for (var i = 0; i < props.signatureFiles.length; i++) {
signatureFileString += props.signatureFiles[i];
if (i !== signatureFiles.length - 1) {
if (i !== props.signatureFiles.length - 1) {
// add space
signatureFileString += " ";
}
}
return exec(
`gaiacli tx multisign unsigned.json ${keyName} ${signatureFileString}`
`gaiacli tx multisign ${props.unsignedFile} ${props.keyName} ${signatureFileString}`
);
};
module.exports = {
broadcastTX,
createMultiSigKey,
createKey,
listKeys,
getMultiAddress,
listKeys,
signMulti,
};

View File

@ -42,9 +42,6 @@ const post = async (req, res) => {
});
let multiAddress = gaiaWrap.getMultiAddress({ keyName: multiName });
// strip newlines
multiAddress = multiAddress.replace(/(\r\n|\n|\r)/gm, "");
// save multisig
const saveResult = queries.insertMultiKey.run({
key_name: multiName,

View File

@ -1,6 +1,5 @@
const exec = require("../../utilities/promiseExec");
const { v4: uuidv4 } = require("uuid");
const { queries } = require("../../database/connectDatabase");
const { queries } = require("../../../database/connectDatabase");
const post = async (req, res) => {
try {
@ -24,7 +23,7 @@ const post = async (req, res) => {
: [];
transaction.signatures.push(signature);
queries.updateTransactionSignatures({
queries.updateTransactionSignatures.run({
signatures: JSON.stringify(transaction.signatures),
uuid: transaction.uuid,
});

View File

@ -0,0 +1,95 @@
const fs = require("fs");
const gaiaWrap = require("../../../../lib/gaiaWrap");
const path = require("path");
const { v4: uuidv4 } = require("uuid");
const { queries } = require("../../../../database/connectDatabase");
const get = async (req, res) => {
try {
const {
query: { uuid },
} = req;
// get corresponding multi record
const transaction = queries.getTransactionForUUID.get(uuid);
if (!transaction) {
res.status(404).send();
} else {
const multi = queries.getMultiFromUUID.get(transaction.multi_key_name);
const signatures = JSON.parse(transaction.signatures);
if (signatures.length < multi.multi_threshold) {
res.status(400).send("Not enough signatures");
} else {
// create the files and multisign the tx
const unsignedJsonPath = `${transaction.uuid}-unsigned.json`;
fs.writeFileSync(unsignedJsonPath, transaction.unsigned);
const signatureFilePaths = [];
for (var i = 0; i < signatures.length; i++) {
const signature = signatures[i];
const filePath = `${transaction.uuid}-sign${i}.json`;
fs.writeFileSync(filePath, JSON.stringify(signature));
signatureFilePaths.push(filePath);
}
let signedTransaction = await gaiaWrap.signMulti({
keyName: multi.key_name,
signatureFiles: signatureFilePaths,
unsignedFile: unsignedJsonPath,
});
signedTransaction = signedTransaction.replace(/(\r\n|\n|\r)/gm, "");
// save signed tx
queries.updateTransactionSigned.run({
uuid: transaction.uuid,
signed: signedTransaction,
});
// save signed transaction
const signedTxPath = `${transaction.uuid}-signed.json`;
fs.writeFileSync(signedTxPath, signedTransaction);
// broadcast tx
const broadcastRes = await gaiaWrap.broadcastTX({
txFilename: signedTxPath,
});
queries.updateTransactionCompleted.run({
completed_tx: broadcastRes,
uuid: transaction.uuid,
});
const updatedTransaction = queries.getTransactionForUUID.get(uuid);
res.status(200).send(updatedTransaction);
// delete unsigned and signatures
fs.unlink(unsignedJsonPath, (err) => {
throw err;
});
for (var i = 0; i < signatureFilePaths.length; i++) {
fs.unlink(signatureFilePaths[i], (err) => {
throw err;
});
}
}
}
} catch (err) {
console.log(err);
res.status(500).send(err.message);
}
};
export default (req, res) => {
switch (req.method) {
case "GET":
get(req, res);
break;
default:
res.status(404).send();
}
};

View File

@ -0,0 +1,47 @@
const { v4: uuidv4 } = require("uuid");
const { queries } = require("../../../../database/connectDatabase");
const post = async (req, res) => {
try {
const {
query: { uuid },
} = req;
// req.body should have signature, a json object of the
// signature for this transaction
const { signature } = req.body;
// get corresponding transaction record
const transaction = queries.getTransactionForUUID.get(uuid);
if (!transaction) {
res.status(404).send();
} else {
// add signature to existing signatures and save
transaction.signatures = transaction.signatures
? JSON.parse(transaction.signatures)
: [];
transaction.signatures.push(signature);
queries.updateTransactionSignatures.run({
signatures: JSON.stringify(transaction.signatures),
uuid: transaction.uuid,
});
const updatedTransaction = queries.getTransactionForUUID.get(uuid);
res.status(200).send(updatedTransaction);
}
} catch (err) {
console.log(err);
res.status(500).send(err.message);
}
};
export default (req, res) => {
switch (req.method) {
case "POST":
post(req, res);
break;
default:
res.status(404).send();
}
};

View File

@ -1,6 +1,5 @@
const exec = require("../../utilities/promiseExec");
const { v4: uuidv4 } = require("uuid");
const { queries } = require("../../database/connectDatabase");
const { queries } = require("../../../database/connectDatabase");
const post = async (req, res) => {
try {

View File

@ -37,19 +37,6 @@ export default ({ transactions }) => {
From here you can create and view transactions
</p>
<TransactionForm multiAddress={address} />
<div className="existing-transactions">
<h2>Transaction List</h2>
{transactions && transactions.length > 0 ? (
<ul>
{transactions.map((transaction) => (
<li key={transaction.id}>{transaction.id}</li>
))}
</ul>
) : (
<p>No transactions yet</p>
)}
</div>
</div>
<style jsx>{`

View File

@ -1,4 +1,3 @@
import fileDownload from "js-file-download";
import Head from "../../../../components/head";
import { queries } from "../../../../database/connectDatabase";
import TransactionSigning from "../../../../components/TransactionSigning";
@ -24,13 +23,31 @@ export async function getStaticProps(context) {
}
export default ({ transaction, multi }) => {
const txInfo = (transaction && JSON.parse(transaction.unsigned)) || null;
return (
<div>
<Head title="Home" />
<div className="hero">
<h1 className="title">multisig: {multi && multi.address}</h1>
<h2>transaction: {transaction && transaction.uuid}</h2>
{txInfo && (
<div className="transaction-info">
<h2>Transaction Info</h2>
<ul>
<li>
<span>To:</span> {txInfo.value.msg[0].value.to_address}
</li>
<li>
<span>From:</span> {txInfo.value.msg[0].value.from_address}
</li>
<li>
<span>Amount:</span>{" "}
{txInfo.value.msg[0].value.amount[0].amount} uatom
</li>
<li>
<span>To:</span> {txInfo.value.fee.gas} uatom
</li>
</ul>
</div>
)}
<p className="description">
Download the unsigned transaction json to sign. Once the necessary
signatures are gathered here, you will be able to broadcast your
@ -40,6 +57,28 @@ export default ({ transaction, multi }) => {
</div>
<style jsx>{`
.transaction-info {
text-align: left;
border: 1px solid rebeccapurple;
border-radius: 1em;
padding: 0.5em 1em;
width: 60%;
margin: 2em auto 1em;
}
.transaction-info ul {
list-style: none;
padding: 0;
font-size: 1.2em;
}
.transaction-info span {
font-weight: bold;
margin-right: 0.5em;
}
.transaction-info li {
margin: 0.5em 0;
}
.hero {
width: 100%;
text-align: center;
@ -54,7 +93,7 @@ export default ({ transaction, multi }) => {
h2 {
font-size: 1.5em;
margin: 1em 0;
margin: 0.25em 0;
font-weight: bold;
}
.title {