Skip to content

Commit 3b21892

Browse files
committed
updated ethereum api to allow signing and sending raw transactions with private keys;
1 parent 53fe093 commit 3b21892

File tree

5 files changed

+226
-4
lines changed

5 files changed

+226
-4
lines changed

README.md

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,20 @@ The following endpoints can be used to get information about blocks and even tra
358358

359359
- `api/eth/block/2?showTx=true&useString=latest` : 1 is the blocknumber, the query `showTx` will show the transaction object in the output when set to true, and the query `useString` will overwrite the block number and use one of the strings used in the [getBlock api](http://web3js.readthedocs.io/en/1.0/web3-eth.html#getblock) such as `latest` to get the most recent block.
360360

361+
### Create address
362+
363+
GET request
364+
```
365+
http://localhost:3000/api/eth/create-account
366+
```
367+
368+
### Get public address from private key
369+
370+
GET request
371+
```
372+
http://localhost:3000/api/eth/get-account/{privatekey}
373+
```
374+
361375
### Get Transaction info
362376

363377

@@ -425,7 +439,7 @@ Note: you may notice that `nonce` is not a parameter accepted for changing becau
425439

426440
### Send Transaction
427441

428-
Sending a transaction uses the `paramsUpdated` object and passes it to `web3.eth.sendTransaction(paramsUpdated)`
442+
Sending a transaction uses the `paramsUpdated` object and passes it to `web3.eth.sendTransaction(paramsUpdated)` and **requires a the from address to be an unlocked keystore address from inside the node**
429443

430444
output
431445

@@ -444,6 +458,54 @@ output
444458
}
445459
```
446460

461+
### Sign and Send Manually
462+
463+
To sign with a private key and send the raw transaction follow these instructions for constructing your request:
464+
465+
#### Get Transaction Info
466+
467+
`http://localhost:3000/api/eth/send-tx-info` POST request with the following json to see your data:
468+
469+
```
470+
{
471+
"from": "0xA8D26e47CD1CB6Fc0803cA7D64D395bb38bc33de",
472+
"to": "0x87E426ff6Deb0dF15CD98CAae0F63cf027D711b3",
473+
"value": "0.002"
474+
}
475+
```
476+
477+
If you are sending transactions from the same `from` address consecutively, you will need to increase the nonce by one so add it to your request
478+
479+
```
480+
"nonce": 1
481+
```
482+
You will see the a change in the nonce. You want to pay attention to the `paramsToSign` parameter because it will be used to sign the transaction next.
483+
484+
#### Sign transaction
485+
486+
`http://localhost:3000/api/eth/sign-tx` and pass the POST request json:
487+
488+
```
489+
{
490+
"from": "0xA8D26e47CD1CB6Fc0803cA7D64D395bb38bc33de",
491+
"to": "0x87E426ff6Deb0dF15CD98CAae0F63cf027D711b3",
492+
"value": "0.002",
493+
"privateKey": "0x7f74657374320000000000000000000000000000000000000000000000000057"
494+
}
495+
```
496+
See [web3 api](http://web3js.readthedocs.io/en/1.0/web3-eth-accounts.html#signtransaction) for details on the out put and grab the `rawTransaction` parameter hex encoded transaction
497+
498+
499+
#### Send Signed Transaction
500+
501+
`http://localhost:3000/api/eth/send-signed-tx` POST request with json:
502+
503+
```
504+
{
505+
"rawTransaction": "0xf86b04850218711a008252089487e426ff6deb0df15cd98caae0f63cf027d711b387071afd498d00008029a063542de27e66ea4a92f1270cbef3cd6ed9ad0b8bafbdcd0cedfcc25e2d731da7a05a7fb9dec086a6b1e4c0c19410b1b2f10a76c9f6198351731e92096534b67624"
506+
}
507+
```
508+
447509
## Endpoints
448510

449511
```
@@ -587,6 +649,12 @@ output
587649
"GET"
588650
]
589651
},
652+
{
653+
"path": "/api/eth/get-account/:privateKey",
654+
"methods": [
655+
"GET"
656+
]
657+
},
590658
{
591659
"path": "/api/eth/accounts",
592660
"methods": [
@@ -599,6 +667,18 @@ output
599667
"POST"
600668
]
601669
},
670+
{
671+
"path": "/api/eth/sign-tx",
672+
"methods": [
673+
"POST"
674+
]
675+
},
676+
{
677+
"path": "/api/eth/send-signed-tx",
678+
"methods": [
679+
"POST"
680+
]
681+
},
602682
{
603683
"path": "/api/eth/send-tx",
604684
"methods": [

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"debug": "~2.6.9",
1616
"dotenv": "^5.0.1",
1717
"ejs": "~2.5.7",
18+
"ethereumjs-tx": "^1.3.4",
1819
"express": "~4.16.0",
1920
"express-list-endpoints": "^3.0.1",
2021
"http-errors": "~1.6.2",

server/app/controllers/api-ethereum.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ module.exports = {
5353
return res.send(value)
5454
});
5555
},
56+
privateKeyToAccount: function (req, res, next) {
57+
ethereum.privateKeyToAccount(req.params.privateKey).then((value) => {
58+
return res.send(value)
59+
});
60+
},
5661
getAccounts: function (req, res, next) {
5762
ethereum.getAccounts().then((value) => {
5863
return res.send(value)
@@ -66,6 +71,25 @@ module.exports = {
6671
});
6772
})
6873
},
74+
signTransaction: async function (req, res, next) {
75+
await validateBody(res, req.body)
76+
.then((obj) => {
77+
let validPrivateKey = ethereum.validatePrivateKey(obj.privateKey);
78+
if (validPrivateKey) {
79+
ethereum.signTransaction(obj).then((value) => {
80+
return (value.error != null ? res.status(404).send(value).end() : res.send(value))
81+
});
82+
}
83+
else {
84+
return res.status(404).send(new Object({ error: "Missing or Invalid private Key" })).end()
85+
}
86+
})
87+
},
88+
sendSignedTransaction: async function (req, res, next) {
89+
ethereum.sendSignedTransaction(req.body.rawTransaction).then((value) => {
90+
return (value.error != null ? res.status(404).send(value).end() : res.send(value))
91+
});
92+
},
6993
sendTransaction: async function (req, res, next) {
7094
await validateBody(res, req.body)
7195
.then((obj) => {
@@ -80,6 +104,7 @@ async function validateBody(res, body) {
80104
return new Promise((resolve, reject) => {
81105
if (typeof body === 'object' && Object.keys(body).length != 0) {
82106
let obj = {};
107+
obj.from = body.from != null ? body.from : null;
83108
obj.from = body.from != null && web3.utils.isAddress(body.from) ? body.from : obj.error = 'invalid address';
84109
obj.to = body.to != null && web3.utils.isAddress(body.to) ? body.to : obj.error = 'invalid address';
85110
obj.value = body.value != null && typeof body.value === 'string' ? body.value : obj.error = "value missing or is not a string number";
@@ -88,6 +113,8 @@ async function validateBody(res, body) {
88113
}
89114
obj.gas = body.gas != null ? body.gas : null;
90115
obj.gasPrice = body.gasPrice != null && typeof body.gasPrice === 'string' ? body.gasPrice : null;
116+
obj.privateKey = body.privateKey != null ? body.privateKey : null;
117+
obj.nonce = body.nonce != null ? body.nonce : null;
91118
if (obj.error) {
92119
res.status(404).send(obj).end();
93120
} else {
@@ -97,4 +124,4 @@ async function validateBody(res, body) {
97124
res.status(404).send('missing or invalid request json object').end();
98125
}
99126
})
100-
}
127+
}

server/app/helpers/ethereum.js

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,27 @@ const gasAPI = "https://ethgasstation.info/json/ethgasAPI.json";
77
var e; //await function
88

99
Ethereum = {
10+
/**
11+
* validate private key by checking its public key is valid
12+
* Can prob use a more standard way instead
13+
* @param {string} privateKey is the string of the key passed
14+
* @return {boolean} true is valid
15+
*/
16+
validatePrivateKey: function (privateKey) {
17+
if (privateKey != null) {
18+
let accountObject = web3.eth.accounts.privateKeyToAccount(privateKey);
19+
let publicAddress = accountObject.address;
20+
if (web3.utils.isAddress(publicAddress)) {
21+
return true;
22+
}
23+
return false;
24+
}
25+
return false;
26+
},
27+
/**
28+
* syncing refers to the node being up to date with regards to the latest blockchain block number
29+
* @return {(Object|boolean)} return true if up to date and object of current/highest block height otherwise
30+
*/
1031
isSyncing: async function () {
1132
let obj = {};
1233
return await web3.eth.isSyncing()
@@ -128,6 +149,18 @@ Ethereum = {
128149
resolve(e);
129150
}))
130151
},
152+
/**
153+
* Gets the public address from private key
154+
* @param {(string|hex)} privateKey is a valid private key
155+
*/
156+
privateKeyToAccount: async function (privateKey) {
157+
return new Promise(((resolve, reject) => {
158+
// e = web3.eth.accounts.privateKeyToAccount("0x137450319cc21f2d3130a1b54f5796f1534a5b6f4c2cdd57e692f06518fa9d9a");
159+
e = web3.eth.accounts.privateKeyToAccount(privateKey);
160+
// console.log('new', e);
161+
resolve(e);
162+
}))
163+
},
131164
/**
132165
* Get all accounts generated and saved to node
133166
* @return {(Promise|Object.<Array>)} obj with array of all accounts
@@ -162,6 +195,52 @@ Ethereum = {
162195
});
163196
return e;
164197
},
198+
/**
199+
* Sign transaction with tx object and private key
200+
* @param {Object} txObject is the transaction object
201+
* @return {(Promise|Object)} signed object with rawTransaction data to be used for sending transaction
202+
*/
203+
signTransaction: async function (txObject) {
204+
let obj = {};
205+
e = await processTxInfoData(txObject)
206+
.then(function (result) {
207+
return web3.eth.accounts.signTransaction(result.paramsToSign, txObject.privateKey)
208+
.then(function (result) {
209+
return result;
210+
}).catch(function (err) {
211+
// console.log('sign error', err.message);
212+
obj.error = err.message;
213+
return obj
214+
});
215+
}).catch(function (err) {
216+
// console.log('tx info error', err.message);
217+
obj.error = err.message
218+
return obj;
219+
});
220+
return e;
221+
},
222+
/**
223+
* Send signed transaction
224+
* @param {string} signedTransactionData is the hex encoded data of the raw transaction to send
225+
* @return {(Promise|Object)}
226+
*/
227+
sendSignedTransaction: async function (signedTransactionData) {
228+
let obj = {};
229+
return new Promise(((resolve, reject) => {
230+
let e = web3.eth.sendSignedTransaction(signedTransactionData, (error, hash) => {
231+
if (!error) {
232+
// console.log('sent hash', hash);
233+
obj.txHash = hash;
234+
resolve(obj);
235+
} else {
236+
// console.log('sent error', error.message);
237+
obj.error = error.message;
238+
resolve(obj)
239+
}
240+
})
241+
return e;
242+
}));
243+
},
165244
/**
166245
* Send a transaction with node accounts to avoid using private keys
167246
* The transaction object passed comes from the completed object
@@ -229,12 +308,26 @@ async function processTxInfoData(txObject) {
229308
}
230309
// let gasPrice = gasPriceTypeCustom != null ? gasPrices[gasPriceTypeCustom] * 1000000000 : gasPriceDefault;
231310

311+
// get nonce
312+
let nonce;
313+
await web3.eth.getTransactionCount(txObject.from).then((result) => {
314+
// add nonce to current nonce if passed to request else return current nonce
315+
if (txObject.nonce) {
316+
nonce = result + txObject.nonce;
317+
} else {
318+
nonce = result;
319+
}
320+
})
321+
.catch((error) => {
322+
reject(new Error('failed to get nonce'))
323+
});
324+
232325
// set tx object to calculate transaction gas
233326
let params = {
234327
"from": txObject.from,
235328
"to": txObject.to,
236329
"gasPrice": gasPrice,
237-
// "nonce": nonce,
330+
"nonce": nonce,
238331
"value": wei
239332
};
240333
// get transaction gas
@@ -260,9 +353,23 @@ async function processTxInfoData(txObject) {
260353
"to": txObject.to,
261354
"gas": gas,
262355
"gasPrice": gasPrice,
263-
// "nonce": nonce,
356+
"nonce": nonce,
357+
"value": wei
358+
};
359+
let paramsToSign1 = {
360+
"to": txObject.to,
361+
"gas": gas,
362+
"gasPrice": gasPrice,
363+
"nonce": nonce,
264364
"value": wei
265365
};
366+
let paramsToSign = {
367+
"to": web3.utils.toHex(txObject.to),
368+
"gas": web3.utils.toHex(gas),
369+
"gasPrice": web3.utils.toHex(gasPrice),
370+
"nonce": web3.utils.toHex(nonce),
371+
"value": web3.utils.toHex(wei)
372+
};
266373
obj.amountToSendEther = txObject.value;
267374
obj.amountToSendWei = wei;
268375
obj.gasPrices = gasPrices;
@@ -273,6 +380,8 @@ async function processTxInfoData(txObject) {
273380
obj.estimatedGas = estimatedGas;
274381
obj.gas = gas;
275382
obj.params = params;
383+
obj.paramsToSign1 = paramsToSign1;
384+
obj.paramsToSign = paramsToSign;
276385
obj.paramsUpdated = paramsUpdated;
277386
resolve(obj);
278387
})

server/app/routes/api-ethereum.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,13 @@ router.get('/block/:blockNumber', ethereumApi.getBlock);
2323
router.get('/tx/:transactionHash', ethereumApi.getTransaction);
2424
router.get('/tx-from-block/:hashStringOrNumber', ethereumApi.getTransactionFromBlock);
2525
router.get('/create-account', ethereumApi.createAccount);
26+
router.get('/get-account/:privateKey', ethereumApi.privateKeyToAccount);
2627
router.get('/accounts', ethereumApi.getAccounts);
2728
router.post('/send-tx-info', ethereumApi.sendTransactionInfo);
29+
router.post('/sign-tx',ethereumApi.signTransaction);//takes tx object and private key and creates rawData
30+
router.post('/send-signed-tx',ethereumApi.sendSignedTransaction);//send signed data
31+
// router.post('/sign-send-tx',ethereumApi.sendSignedTransaction);//signs and sends tx
32+
// these endpoints use the keystore addresses
2833
router.post('/send-tx', ethereumApi.sendTransaction);
2934

3035
module.exports = router;

0 commit comments

Comments
 (0)