basic proto app finished
This commit is contained in:
parent
5349f3b791
commit
bfb63f7759
@ -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;
|
||||
};
|
||||
|
||||
|
||||
@ -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.
@ -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,
|
||||
};
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
});
|
||||
|
||||
95
pages/api/transaction/[uuid]/broadcast.js
Normal file
95
pages/api/transaction/[uuid]/broadcast.js
Normal 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();
|
||||
}
|
||||
};
|
||||
47
pages/api/transaction/[uuid]/index.js
Normal file
47
pages/api/transaction/[uuid]/index.js
Normal 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();
|
||||
}
|
||||
};
|
||||
@ -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 {
|
||||
|
||||
@ -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>{`
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user