Skip to content

Commit 23cb67f

Browse files
committed
added subscription for new blocks and sends data to a url creating a webhook; added websocket to create subscriptions and for future use for custom websocket server
1 parent 3b21892 commit 23cb67f

File tree

8 files changed

+362
-6
lines changed

8 files changed

+362
-6
lines changed

README.md

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@
2121
- [Error Debugging](#error-debugging)
2222
- [Sample Contracts](#sample-contracts)
2323
- [Version Control]($version-control)
24+
- [Connections](#Connections)
2425
- [Endpoint Notes](#endpoint-notes)
2526
- [Get Block Info](#get-block-info)
2627
- [Get Transaction Info](#get-transaction-info)
2728
- [Send Transaction](#send-transaction)
29+
- [Webhook](#webhook)
30+
- [Websockets](#websockets)
2831
- [List of All API Endpoints](#endpoints)
2932

3033
## Quick Start
@@ -338,12 +341,54 @@ Question about storing artifact files (e.g. Token.json) after truffle compiles c
338341
- [stackexchange](https://ethereum.stackexchange.com/questions/19486/storing-a-truffle-contract-interface-in-version-control) - recommendation to store outside
339342
- [artifact-updates](https://github.com/trufflesuite/artifact-updates) - community repo for updating some issues with doing this.
340343

344+
## Connections
345+
346+
Full example of `connections.json`
347+
348+
```
349+
{
350+
"networks": {
351+
"connectApi": "ropsten",
352+
"ganache":{
353+
"url": "http://127.0.0.1:7545",
354+
"websocketUrl": "'ws://localhost:7545",
355+
"networkId": "5777",
356+
"networkName": "ganache",
357+
"networkType": "testrpc",
358+
"token": {
359+
"ownerAddress": "0x451E62137891156215d9D58BeDdc6dE3f30218e7",
360+
"tokenContractAddress": "0x4c59b696552863429d25917b52263532af6e6078",
361+
"migrateContractAddress": "0xcf068555df7eab0a9bad97829aa1a187bbffbdba"
362+
}
363+
},
364+
"ropsten":{
365+
"url": "http://10.10.0.163:8545",
366+
"websocketUrl": "ws://10.10.0.163:8546",
367+
"blockWebhookUrl": "http://4f5a64ba.ngrok.io/api/eth/webhook",
368+
"syncingWebhookUrl": "http://4f5a64ba.ngrok.io/api/eth/webhook",
369+
"testWebhookUrl": "http://4f5a64ba.ngrok.io/api/eth/webhook",
370+
"networkId": 3,
371+
"networkName": "ropsten",
372+
"networkType": "testnet",
373+
"token": {
374+
"ownerAddress": "0x83634a8eaadc34b860b4553e0daf1fac1cb43b1e",
375+
"tokenContractAddress": "0x3e672122bfd3d6548ee1cc4f1fa111174e8465fb",
376+
"migrateContractAddress": "0xa8ebf36b0a34acf98395bc5163103efc37621052"
377+
}
378+
},
379+
"mainnet":{}
380+
}
381+
}
382+
```
383+
341384
## Bugs to Fix
342385

343386
transferOwnership & kill functions both catch an 'invalid address'. The response it 200 with `false` boolean indicating request failed.
344387

345388
`api/eth/tx-from-block/hashStringOrNumber` endpoint seems to fail when connected to Ropsten Testnet but not with ganache local node
346389

390+
`` subscription does not seem to work and or do
391+
347392

348393
## Endpoint Notes
349394

@@ -506,6 +551,85 @@ See [web3 api](http://web3js.readthedocs.io/en/1.0/web3-eth-accounts.html#signtr
506551
}
507552
```
508553

554+
## Webhook
555+
556+
Webhooks allow data to be sent as POST request to an external url specified in the `config/connections.json` iside the `blockWebhookUrl` paramater.
557+
558+
**To use this webhook you need to have websockets connected see [websockets](#websockets) for more information**
559+
560+
```
561+
"ropsten":{
562+
...
563+
"blockWebhookUrl": "http://4f5a64ba.ngrok.io/api/eth/webhook",
564+
"testWebhookUrl": "http://4f5a64ba.ngrok.io/api/eth/webhook",
565+
...
566+
},
567+
```
568+
569+
The `testWebhookUrl` parameter is used for testing but following these instructions:
570+
571+
Run this api server and then run these
572+
573+
```
574+
brew cask install ngrok
575+
ngrok http 3000
576+
```
577+
grab the ngrok url and add it the `testWebhookUrl` parameter e.g.`http://4f5a64ba.ngrok.io/api/eth/webhook`.
578+
579+
Then open the debug url for ngrok e.g. `http://127.0.0.1:4040`
580+
581+
Send a post request to `http://localhost:3000/api/eth/post-to-webhook` and watch two request show, one for the request and the second that send another POST request to the `/api/eth/webhook` endpoint
582+
583+
## Websockets
584+
585+
A websocket connection to the node is used to subscribe to events (e.g. new blocks, transactions, see [web3 api](http://web3js.readthedocs.io/en/1.0/web3-eth-subscribe.html)) and required to use the webhook feature which sends these subscriptions to a specified url.
586+
587+
**Setup**
588+
589+
You need to enable a websocket connection inside the node and add that url to the `connections.json`
590+
591+
```
592+
```
593+
"ropsten":{
594+
...
595+
"websocketUrl": "ws://10.10.0.163:8546",
596+
...
597+
},
598+
```
599+
600+
or `.env` files which will overwrite the connections.json parameter
601+
602+
```
603+
WEBSOCKET_URL=ws://127.0.0.1:8546
604+
```
605+
606+
Websockets are an experimental feature, and the code can be found inside `server/app/helpers/web3-websocket.js`
607+
There is one `subscriptionType` that is corrently tested and working:
608+
609+
```
610+
blockSubscription
611+
```
612+
613+
Another is to get status on the syncing of the node with the most current block. **Needs more testing so use with caution**
614+
615+
```
616+
syncingSubscription
617+
```
618+
619+
**Create subscription**
620+
To create a subscription to block headers so every new block will be emitted and POSTed to a webhook url.
621+
622+
```
623+
http://localhost:3000/api/eth/subscribe-block
624+
```
625+
626+
**Remove Subscription**
627+
628+
```
629+
http://localhost:3000/api/eth/close-subscriptions/blockSubscription
630+
http://localhost:3000/api/eth/close-subscriptions/syncingSubscription
631+
```
632+
509633
## Endpoints
510634
511635
```

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"truffle-contract": "^3.0.5",
2626
"truffle-hdwallet-provider": "0.0.5",
2727
"truffle-wallet-provider": "0.0.5",
28-
"web3": "^1.0.0-beta.34"
28+
"web3": "^1.0.0-beta.34",
29+
"ws": "^5.2.0"
2930
},
3031
"devDependencies": {
3132
"nodemon": "^1.17.4"

server/app/controllers/api-ethereum.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,37 @@ const ethereum = require('../helpers/ethereum.js');
22
const Web3 = require('../helpers/web3.js');
33
const web3 = Web3.web3;
44

5+
const Web3Websocket = require('../helpers/web3-websocket.js');
6+
const web3Socket = Web3Websocket.web3Socket;
7+
const WebSocket = Web3Websocket.WebSocket;
8+
const axios = require('axios')
9+
10+
const Webhook = require('../helpers/webhook.js');
11+
const testWebhookUrl = Webhook.testWebhookUrl;
12+
513
module.exports = {
614
getTest: function (req, res, next) {
715
res.send("Ethereum API")
816
},
17+
testWebhookPost: function (req, res, next) {
18+
let obj = {};
19+
axios.post(testWebhookUrl, req.body)
20+
.then(function (response) {
21+
obj.message = 'sent to webhook url';
22+
obj.data = response.data;
23+
res.send(obj)
24+
})
25+
.catch(function (error) {
26+
obj.error = 'There was an error sending post request to webhook url';
27+
obj.config = error.config;
28+
res.status(404).send(obj).end()
29+
});
30+
},
31+
testWebhook: function (req, res, next) {
32+
console.log('posted into webhook: ', req.body)
33+
// res.send('req.body')
34+
res.send(req.body)
35+
},
936
isSyncing: function (req, res, next) {
1037
ethereum.isSyncing().then((value) => {
1138
return (value.error != null ? res.status(404).send(value).end() : res.send(value));
@@ -98,6 +125,42 @@ module.exports = {
98125
});
99126
})
100127
},
128+
subscribeSyncing: function (req, res, next) {
129+
let obj = {};
130+
ethereum.subscribeSyncing()
131+
.then((value) => {
132+
// console.log('value block', value)
133+
obj.subscriptionOpened = value;
134+
res.send(obj)
135+
}, (rej) => {
136+
obj.subscriptionNotOpened = rej;
137+
res.status(404).send(obj).end();
138+
})
139+
},
140+
subscribeBlock: function (req, res, next) {
141+
let obj = {};
142+
ethereum.subscribeBlock()
143+
.then((value) => {
144+
// console.log('value block', value)
145+
obj.subscriptionOpened = value;
146+
res.send(obj)
147+
}, (rej) => {
148+
obj.subscriptionNotOpened = rej;
149+
res.status(404).send(obj).end();
150+
})
151+
},
152+
closeSubscriptions: function (req, res, next) {
153+
let obj = {};
154+
obj.subscriptionType = req.params.subscriptionType;
155+
156+
ethereum.closeSubscription(obj.subscriptionType).then((value) => {
157+
obj.closed = value;
158+
res.send(obj);
159+
}, (rej) => {
160+
obj.notClosed = rej;
161+
res.send(obj);
162+
})
163+
}
101164
}
102165

103166
async function validateBody(res, body) {

server/app/helpers/ethereum.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ const Web3 = require('../helpers/web3.js');
22
const web3 = Web3.web3;
33
const BigNumber = Web3.BigNumber;
44
const axios = require('axios');
5+
const Web3Websocket = require('../helpers/web3-websocket.js');
6+
const web3Socket = Web3Websocket.web3Socket;
7+
8+
const Webhook = require('../helpers/webhook.js');
9+
const blockWebhookUrl = Webhook.blockWebhookUrl;
10+
const syncingWebhookUrl = Webhook.syncingWebhookUrl;
511

612
const gasAPI = "https://ethgasstation.info/json/ethgasAPI.json";
713
var e; //await function
@@ -262,6 +268,84 @@ Ethereum = {
262268
});
263269
return e;
264270
},
271+
/**
272+
* Close a subscription by type
273+
* @param {String} subscriptionType is the Ethereum.parameter used to store the instance of the subscription e.g. syncingSubscription
274+
* @return {Promise}
275+
*/
276+
closeSubscription: function (subscriptionType) {
277+
return new Promise((resolve, reject) => {
278+
if (Ethereum[subscriptionType] != null) {
279+
return Ethereum[subscriptionType].unsubscribe(function (error, success) {
280+
if (success) {
281+
console.log(`Successfully unsubscribed! for ${subscriptionType}`, success);
282+
resolve(`Successfully unsubscribed! for ${subscriptionType}`)
283+
} else {
284+
console.log('unsubscribed error:', error);
285+
reject(`Could not unsubscribed. There was an error unsubscribing for ${subscriptionType}`)
286+
}
287+
})
288+
} else {
289+
reject(`no open subscriptions for ${subscriptionType}`)
290+
}
291+
})
292+
},
293+
/**
294+
* Parameters used to store subscription instances
295+
* which can then be used elsewhere or to unsubscribe
296+
*/
297+
syncingSubscription: null,
298+
blockSubscription: null,
299+
/**
300+
* Start a subscription to syncing and send data to webhook url
301+
* @return {Promise}
302+
*/
303+
subscribeSyncing: function () {
304+
let obj = {};
305+
return new Promise((resolve, reject) => {
306+
return Ethereum.syncingSubscription = web3Socket.eth.subscribe('syncing', function (error, sync) {
307+
if (error) reject(`There was an error creating a new webhook subscription to syncing: ${error}`)
308+
if (!error) resolve(`Successfully created a webhook subscription to syncing`)
309+
})
310+
.on('data', function (sync) {
311+
console.log('on syncing subscription: ', sync);
312+
/**
313+
* Send post request to url to initiate webhook
314+
*/
315+
axios.post(syncingWebhookUrl, sync)
316+
.catch(function (error) {
317+
obj.error = 'There was an error sending post request to webhook url';
318+
obj.config = error.config;
319+
reject(obj);
320+
});
321+
});
322+
})
323+
},
324+
/**
325+
* Start a subscription to new blocks and send data to webhook url
326+
* @return {Promise}
327+
*/
328+
subscribeBlock: function () {
329+
let obj = {};
330+
return new Promise((resolve, reject) => {
331+
return Ethereum.blockSubscription = web3Socket.eth.subscribe('newBlockHeaders', function (error, sync) {
332+
if (error) reject(`There was an error creating a new webhook subscription to newBlockHeaders: ${error}`)
333+
if (!error) resolve(`Successfully created a webhook subscription to newBlockHeaders`)
334+
})
335+
.on('data', function (sync) {
336+
// console.log('on block subscription: ', sync);
337+
/**
338+
* Send post request to url to initiate webhook
339+
*/
340+
axios.post(blockWebhookUrl, sync)
341+
.catch(function (error) {
342+
obj.error = 'There was an error sending post request to webhook url';
343+
obj.config = error.config;
344+
reject(obj);
345+
});
346+
})
347+
});
348+
},
265349
}
266350

267351
module.exports = Ethereum;

0 commit comments

Comments
 (0)