├── .eslintignore
├── server
├── util
│ ├── index.js
│ ├── getURLProtocol.js
│ ├── makeWebSocketConnection.js
│ └── makeRPCRequest.js
├── constants
│ ├── index.js
│ └── methods.js
├── query
│ ├── version.js
│ ├── coinbase.js
│ ├── mining.js
│ ├── gasPrice.js
│ ├── blockNumber.js
│ ├── peerCount.js
│ ├── hashrate.js
│ ├── listening.js
│ ├── protocolVersion.js
│ ├── accounts.js
│ ├── getBalance.js
│ ├── getTransactionCount.js
│ ├── getBlockByHash.js
│ ├── getTransactionByHash.js
│ ├── getBlockByNumber.js
│ ├── getBlockTransactionCountByHash.js
│ ├── getBlockTransactionCountByNumber.js
│ ├── getTransactionByBlockHashAndIndex.js
│ ├── getTransactionByBlockNumberAndIndex.js
│ ├── accountsWithBalances.js
│ └── index.js
├── index.js
└── schema.js
├── .gitignore
├── README.md
├── LICENSE
├── .eslintrc.json
└── package.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | test/**/*.js
2 |
--------------------------------------------------------------------------------
/server/util/index.js:
--------------------------------------------------------------------------------
1 | const getURLProtocol = require('./getURLProtocol')
2 | const makeRPCRequest = require('./makeRPCRequest')
3 | const makeWebSocketConnection = require('./makeWebSocketConnection')
4 |
5 | module.exports = {
6 | getURLProtocol,
7 | makeRPCRequest,
8 | makeWebSocketConnection,
9 | }
10 |
--------------------------------------------------------------------------------
/server/constants/index.js:
--------------------------------------------------------------------------------
1 | const METHODS = require('./methods.js')
2 |
3 | // Set env variables in non-production environments
4 | if (process.env.NODE_ENV !== 'production') {
5 | require('dotenv').config({ path: 'server/.env' })
6 | }
7 |
8 | const { RPC_ENDPOINT } = process.env
9 |
10 | module.exports = {
11 | METHODS,
12 | RPC_ENDPOINT,
13 | }
14 |
--------------------------------------------------------------------------------
/server/util/getURLProtocol.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Extract the protocol of a URL
3 | *
4 | * @example getURLProtocol('ws://localhost:8545') //=> 'ws:'
5 | *
6 | * @param {String} url URL to analyse
7 | * @return {String} Protocol
8 | */
9 | const getURLProtocol = url => {
10 | return url.match(/^(.*)+?\/\//)[1]
11 | }
12 |
13 | module.exports = getURLProtocol
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS generated files #
2 | ######################
3 | .DS_Store
4 | .DS_Store?
5 | ._*
6 | .Spotlight-V100
7 | .Trashes
8 | Icon?
9 | ehthumbs.db
10 | Thumbs.db
11 |
12 | # Logs #
13 | ######################
14 | *.log
15 |
16 | # Dependencies #
17 | ######################
18 | node_modules/
19 |
20 | # Environment #
21 | ######################
22 | .env
23 |
--------------------------------------------------------------------------------
/server/util/makeWebSocketConnection.js:
--------------------------------------------------------------------------------
1 | const WebSocket = require('ws')
2 |
3 | /**
4 | * Connect to WebSocket
5 | *
6 | * @param {String} address WebSocket server address
7 | * @param {Object} eventEmitter Node.js EventEmitter
8 | * @return {Object} WebSocket connection
9 | */
10 | const makeWebSocketConnection = (address, eventEmitter) => {
11 | const ws = new WebSocket(address)
12 |
13 | ws.on('error', error => {
14 | return eventEmitter.emit('error', error)
15 | })
16 |
17 | ws.on('message', data => {
18 | const response = JSON.parse(data)
19 |
20 | return eventEmitter.emit(response.id, response.result)
21 | })
22 |
23 | return ws
24 | }
25 |
26 | module.exports = makeWebSocketConnection
27 |
--------------------------------------------------------------------------------
/server/query/version.js:
--------------------------------------------------------------------------------
1 | const { METHODS } = require('../constants')
2 |
3 | /**
4 | * Returns the current network id.
5 | *
6 | * @example query { version }
7 | *
8 | * @see https://github.com/ethereum/wiki/wiki/JSON-RPC#net_version
9 | *
10 | * @param {Object} _ Parent resolver context
11 | * @param {Object} args Query arguments
12 | * @param {Object} context GraphQL request context
13 | * @param {Object} context.makeRPCRequest RPC request factory
14 | * @return {String}
15 | */
16 | const version = async (_, args, { makeRPCRequest }) => {
17 | try {
18 | const rpc = await makeRPCRequest(METHODS.net.version)
19 |
20 | if (rpc.error) {
21 | throw new Error(rpc.error.message)
22 | }
23 |
24 | return rpc.result
25 | } catch (e) {
26 | console.error(e)
27 | }
28 | }
29 |
30 | module.exports = version
31 |
--------------------------------------------------------------------------------
/server/query/coinbase.js:
--------------------------------------------------------------------------------
1 | const { METHODS } = require('../constants')
2 |
3 | /**
4 | * Returns the client coinbase address.
5 | *
6 | * @example query { coinbase }
7 | *
8 | * @see https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_coinbase
9 | *
10 | * @param {Object} _ Parent resolver context
11 | * @param {Object} args Query arguments
12 | * @param {Object} context GraphQL request context
13 | * @param {Object} context.makeRPCRequest RPC request factory
14 | * @return {String}
15 | */
16 | const coinbase = async (_, args, { makeRPCRequest }) => {
17 | try {
18 | const rpc = await makeRPCRequest(METHODS.eth.coinbase)
19 |
20 | if (rpc.error) {
21 | throw new Error(rpc.error.message)
22 | }
23 |
24 | return rpc.result
25 | } catch (e) {
26 | console.error(e)
27 | }
28 | }
29 |
30 | module.exports = coinbase
31 |
--------------------------------------------------------------------------------
/server/query/mining.js:
--------------------------------------------------------------------------------
1 | const { METHODS } = require('../constants')
2 |
3 | /**
4 | * Returns if client is actively mining new blocks.
5 | *
6 | * @example query { mining }
7 | *
8 | * @see https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_mining
9 | *
10 | * @param {Object} _ Parent resolver context
11 | * @param {Object} args Query arguments
12 | * @param {Object} context GraphQL request context
13 | * @param {Object} context.makeRPCRequest RPC request factory
14 | * @return {Boolean}
15 | */
16 | const mining = async (_, args, { makeRPCRequest }) => {
17 | try {
18 | const rpc = await makeRPCRequest(METHODS.eth.mining)
19 |
20 | if (rpc.error) {
21 | throw new Error(rpc.error.message)
22 | }
23 |
24 | return rpc.result
25 | } catch (e) {
26 | console.error(e)
27 | }
28 | }
29 |
30 | module.exports = mining
31 |
--------------------------------------------------------------------------------
/server/query/gasPrice.js:
--------------------------------------------------------------------------------
1 | const { METHODS } = require('../constants')
2 |
3 | /**
4 | * Returns the current price per gas in wei.
5 | *
6 | * @example query { gasPrice }
7 | *
8 | * @see https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gasprice
9 | *
10 | * @param {Object} _ Parent resolver context
11 | * @param {Object} args Query arguments
12 | * @param {Object} context GraphQL request context
13 | * @param {Object} context.makeRPCRequest RPC request factory
14 | * @return {String}
15 | */
16 | const gasPrice = async (_, args, { makeRPCRequest }) => {
17 | try {
18 | const rpc = await makeRPCRequest(METHODS.eth.gasPrice)
19 |
20 | if (rpc.error) {
21 | throw new Error(rpc.error.message)
22 | }
23 |
24 | return rpc.result
25 | } catch (e) {
26 | console.error(e)
27 | }
28 | }
29 |
30 | module.exports = gasPrice
31 |
--------------------------------------------------------------------------------
/server/query/blockNumber.js:
--------------------------------------------------------------------------------
1 | const { METHODS } = require('../constants')
2 |
3 | /**
4 | * Returns the number of most recent block.
5 | *
6 | * @example query { blockNumber }
7 | *
8 | * @see https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_blocknumber
9 | *
10 | * @param {Object} _ Parent resolver context
11 | * @param {Object} args Query arguments
12 | * @param {Object} context GraphQL request context
13 | * @param {Object} context.makeRPCRequest RPC request factory
14 | * @return {String}
15 | */
16 | const blockNumber = async (_, args, { makeRPCRequest }) => {
17 | try {
18 | const rpc = await makeRPCRequest(METHODS.eth.blockNumber)
19 |
20 | if (rpc.error) {
21 | throw new Error(rpc.error.message)
22 | }
23 |
24 | return rpc.result
25 | } catch (e) {
26 | console.error(e)
27 | }
28 | }
29 |
30 | module.exports = blockNumber
31 |
--------------------------------------------------------------------------------
/server/query/peerCount.js:
--------------------------------------------------------------------------------
1 | const { METHODS } = require('../constants')
2 |
3 | /**
4 | * Returns number of peers currently connected to the client.
5 | *
6 | * @example query { peerCount }
7 | *
8 | * @see https://github.com/ethereum/wiki/wiki/JSON-RPC#net_peercount
9 | *
10 | * @param {Object} _ Parent resolver context
11 | * @param {Object} args Query arguments
12 | * @param {Object} context GraphQL request context
13 | * @param {Object} context.makeRPCRequest RPC request factory
14 | * @return {String}
15 | */
16 | const peerCount = async (_, args, { makeRPCRequest }) => {
17 | try {
18 | const rpc = await makeRPCRequest(METHODS.net.peerCount)
19 |
20 | if (rpc.error) {
21 | throw new Error(rpc.error.message)
22 | }
23 |
24 | return rpc.result
25 | } catch (e) {
26 | console.error(e)
27 | }
28 | }
29 |
30 | module.exports = peerCount
31 |
--------------------------------------------------------------------------------
/server/query/hashrate.js:
--------------------------------------------------------------------------------
1 | const { METHODS } = require('../constants')
2 |
3 | /**
4 | * Returns the number of hashes per second that the node is mining with.
5 | *
6 | * @example query { hashrate }
7 | *
8 | * @see https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_hashrate
9 | *
10 | * @param {Object} _ Parent resolver context
11 | * @param {Object} args Query arguments
12 | * @param {Object} context GraphQL request context
13 | * @param {Object} context.makeRPCRequest RPC request factory
14 | * @return {String}
15 | */
16 | const hashrate = async (_, args, { makeRPCRequest }) => {
17 | try {
18 | const rpc = await makeRPCRequest(METHODS.eth.hashrate)
19 |
20 | if (rpc.error) {
21 | throw new Error(rpc.error.message)
22 | }
23 |
24 | return rpc.result
25 | } catch (e) {
26 | console.error(e)
27 | }
28 | }
29 |
30 | module.exports = hashrate
31 |
--------------------------------------------------------------------------------
/server/query/listening.js:
--------------------------------------------------------------------------------
1 | const { METHODS } = require('../constants')
2 |
3 | /**
4 | * Returns if client is actively listening for network connections.
5 | *
6 | * @example query { listening }
7 | *
8 | * @see https://github.com/ethereum/wiki/wiki/JSON-RPC#net_listening
9 | *
10 | * @param {Object} _ Parent resolver context
11 | * @param {Object} args Query arguments
12 | * @param {Object} context GraphQL request context
13 | * @param {Object} context.makeRPCRequest RPC request factory
14 | * @return {Boolean}
15 | */
16 | const listening = async (_, args, { makeRPCRequest }) => {
17 | try {
18 | const rpc = await makeRPCRequest(METHODS.net.listening)
19 |
20 | if (rpc.error) {
21 | throw new Error(rpc.error.message)
22 | }
23 |
24 | return rpc.result
25 | } catch (e) {
26 | console.error(e)
27 | }
28 | }
29 |
30 | module.exports = listening
31 |
--------------------------------------------------------------------------------
/server/query/protocolVersion.js:
--------------------------------------------------------------------------------
1 | const { METHODS } = require('../constants')
2 |
3 | /**
4 | * Returns the current ethereum protocol version.
5 | *
6 | * @example query { protocolVersion }
7 | *
8 | * @see https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_protocolversion
9 | *
10 | * @param {Object} _ Parent resolver context
11 | * @param {Object} args Query arguments
12 | * @param {Object} context GraphQL request context
13 | * @param {Object} context.makeRPCRequest RPC request factory
14 | * @return {String}
15 | */
16 | const protocolVersion = async (_, args, { makeRPCRequest }) => {
17 | try {
18 | const rpc = await makeRPCRequest(METHODS.eth.protocolVersion)
19 |
20 | if (rpc.error) {
21 | throw new Error(rpc.error.message)
22 | }
23 |
24 | return rpc.result
25 | } catch (e) {
26 | console.error(e)
27 | }
28 | }
29 |
30 | module.exports = protocolVersion
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | > Just a proof-of-concept for now with a few methods implemented!
6 |
7 | ## Setup
8 | ```bash
9 | npm install
10 | ```
11 |
12 | ## Run
13 | ```bash
14 | npm start
15 | ```
16 |
17 | ## Try
18 |
19 | Test it out on `http://localhost:4000/`:
20 | ```js
21 | query {
22 | blockNumber
23 | accounts
24 | getBalance(params:["0x1f0dff2d80c4812adaf0c93349b28abaa9a84710"])
25 | }
26 | ```
27 |
28 | 
29 |
30 | Or with `curl`:
31 | ```bash
32 | curl 'http://localhost:4000/' -H 'Content-Type: application/json' --data-binary '{"query":"query {\n blockNumber\n accounts\n getBalance(params:[\"0x1f0dff2d80c4812adaf0c93349b28abaa9a84710\"])\n}","variables":{},"operationName":null}'
33 | ```
34 |
--------------------------------------------------------------------------------
/server/query/accounts.js:
--------------------------------------------------------------------------------
1 | const { METHODS } = require('../constants')
2 |
3 | /**
4 | * Returns a list of addresses owned by client.
5 | *
6 | * @example query { accounts }
7 | *
8 | * @see https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_accounts
9 | *
10 | * @param {Object} _ Parent resolver context
11 | * @param {Object} args Query arguments
12 | * @param {Array} args.params RPC request parameters
13 | * @param {Object} context GraphQL request context
14 | * @param {Object} context.makeRPCRequest RPC request factory
15 | * @return {Array}
16 | */
17 | const accounts = async (_, { params }, { makeRPCRequest }) => {
18 | try {
19 | const rpc = await makeRPCRequest(METHODS.eth.accounts, params)
20 |
21 | if (rpc.error) {
22 | throw new Error(rpc.error.message)
23 | }
24 |
25 | return rpc.result
26 | } catch (e) {
27 | console.error(e)
28 | }
29 | }
30 |
31 | module.exports = accounts
32 |
--------------------------------------------------------------------------------
/server/query/getBalance.js:
--------------------------------------------------------------------------------
1 | const { METHODS } = require('../constants')
2 |
3 | /**
4 | * Returns the balance of the account of given address.
5 | *
6 | * @example query { getBalance }
7 | *
8 | * @see https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getbalance
9 | *
10 | * @param {Object} _ Parent resolver context
11 | * @param {Object} args Query arguments
12 | * @param {Array} args.params RPC request parameters
13 | * @param {Object} context GraphQL request context
14 | * @param {Object} context.makeRPCRequest RPC request factory
15 | * @return {String}
16 | */
17 | const getBalance = async (_, { params }, { makeRPCRequest }) => {
18 | try {
19 | const rpc = await makeRPCRequest(METHODS.eth.getBalance, params)
20 |
21 | if (rpc.error) {
22 | throw new Error(rpc.error.message)
23 | }
24 |
25 | return rpc.result
26 | } catch (e) {
27 | console.error(e)
28 | }
29 | }
30 |
31 | module.exports = getBalance
32 |
--------------------------------------------------------------------------------
/server/constants/methods.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | eth: {
3 | accounts: 'eth_accounts',
4 | blockNumber: 'eth_blockNumber',
5 | coinbase: 'eth_coinbase',
6 | gasPrice: 'eth_gasPrice',
7 | getBalance: 'eth_getBalance',
8 | getBlockByHash: 'eth_getBlockByHash',
9 | getBlockByNumber: 'eth_getBlockByNumber',
10 | getBlockTransactionCountByHash: 'eth_getBlockTransactionCountByHash',
11 | getBlockTransactionCountByNumber: 'eth_getBlockTransactionCountByNumber',
12 | getTransactionByBlockHashAndIndex: 'eth_getTransactionByBlockHashAndIndex',
13 | getTransactionByBlockNumberAndIndex:
14 | 'eth_getTransactionByBlockNumberAndIndex',
15 | getTransactionByHash: 'eth_getTransactionByHash',
16 | getTransactionCount: 'eth_getTransactionCount',
17 | hashrate: 'eth_hashrate',
18 | mining: 'eth_mining',
19 | protocolVersion: 'eth_protocolVersion',
20 | },
21 | net: {
22 | listening: 'net_listening',
23 | peerCount: 'net_peerCount',
24 | version: 'net_version',
25 | },
26 | }
27 |
--------------------------------------------------------------------------------
/server/query/getTransactionCount.js:
--------------------------------------------------------------------------------
1 | const { METHODS } = require('../constants')
2 |
3 | /**
4 | * Returns the number of transactions sent from an address.
5 | *
6 | * @example query { getTransactionCount(params:["0x..."]) }
7 | *
8 | * @see https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactioncount
9 | *
10 | * @param {Object} _ Parent resolver context
11 | * @param {Object} args Query arguments
12 | * @param {Array} args.params RPC request parameters
13 | * @param {Object} context GraphQL request context
14 | * @param {Object} context.makeRPCRequest RPC request factory
15 | * @return {String}
16 | */
17 | const getTransactionCount = async (_, { params }, { makeRPCRequest }) => {
18 | try {
19 | const rpc = await makeRPCRequest(METHODS.eth.getTransactionCount, params)
20 |
21 | if (rpc.error) {
22 | throw new Error(rpc.error.message)
23 | }
24 |
25 | return rpc.result
26 | } catch (e) {
27 | console.error(e)
28 | }
29 | }
30 |
31 | module.exports = getTransactionCount
32 |
--------------------------------------------------------------------------------
/server/query/getBlockByHash.js:
--------------------------------------------------------------------------------
1 | const { METHODS } = require('../constants')
2 |
3 | /**
4 | * Returns information about a block by block hash.
5 | *
6 | * @example query { getBlockByHash(params:["0x..."]) }
7 | *
8 | * @see https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash
9 | *
10 | * @param {Object} _ Parent resolver context
11 | * @param {Object} args Query arguments
12 | * @param {Array} args.params RPC request parameters
13 | * @param {Object} context GraphQL request context
14 | * @param {Object} context.makeRPCRequest RPC request factory
15 | * @return {Object}
16 | */
17 | const getBlockByHash = async (_, { params }, { makeRPCRequest }) => {
18 | try {
19 | const rpc = await makeRPCRequest(METHODS.eth.getBlockByHash, [
20 | ...params,
21 | true,
22 | ])
23 |
24 | if (rpc.error) {
25 | throw new Error(rpc.error.message)
26 | }
27 |
28 | return rpc.result
29 | } catch (e) {
30 | console.error(e)
31 | }
32 | }
33 |
34 | module.exports = getBlockByHash
35 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | const { GraphQLServer } = require('graphql-yoga')
2 | const boxen = require('boxen')
3 | const chalk = require('chalk')
4 | const EventEmitter = require('events')
5 |
6 | const { RPC_ENDPOINT } = require('./constants')
7 | const Query = require('./query')
8 | const schema = require('./schema.js')
9 | const { makeRPCRequest } = require('./util')
10 |
11 | const eventEmitter = new EventEmitter()
12 |
13 | eventEmitter.setMaxListeners(0)
14 |
15 | const typeDefs = schema
16 |
17 | const resolvers = {
18 | Query: Query,
19 | }
20 |
21 | const context = {
22 | makeRPCRequest: makeRPCRequest(RPC_ENDPOINT, eventEmitter),
23 | }
24 |
25 | const server = new GraphQLServer({ typeDefs, resolvers, context })
26 |
27 | server.start(() => {
28 | const message =
29 | chalk.yellow.bold('eth-graphql') +
30 | '\n\nServer is running on http://localhost:4000\n\n' +
31 | `RPC_ENDPOINT => ${RPC_ENDPOINT}`
32 |
33 | console.log(
34 | boxen(message, {
35 | align: 'center',
36 | borderColor: 'magenta',
37 | margin: 1,
38 | padding: 1,
39 | })
40 | )
41 | })
42 |
--------------------------------------------------------------------------------
/server/query/getTransactionByHash.js:
--------------------------------------------------------------------------------
1 | const { METHODS } = require('../constants')
2 |
3 | /**
4 | * Returns information about a transaction requested by transaction hash.
5 | *
6 | * @example query { getTransactionByHash(params:["0x..."]) }
7 | *
8 | * @see https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionbyhash
9 | *
10 | * @param {Object} _ Parent resolver context
11 | * @param {Object} args Query arguments
12 | * @param {Array} args.params RPC request parameters
13 | * @param {Object} context GraphQL request context
14 | * @param {Object} context.makeRPCRequest RPC request factory
15 | * @return {Object}
16 | */
17 | const getTransactionByHash = async (_, { params }, { makeRPCRequest }) => {
18 | try {
19 | const rpc = await makeRPCRequest(METHODS.eth.getTransactionByHash, params)
20 |
21 | if (rpc.error) {
22 | throw new Error(rpc.error.message)
23 | }
24 |
25 | return rpc.result
26 | } catch (e) {
27 | console.error(e)
28 | }
29 | }
30 |
31 | module.exports = getTransactionByHash
32 |
--------------------------------------------------------------------------------
/server/query/getBlockByNumber.js:
--------------------------------------------------------------------------------
1 | const { METHODS } = require('../constants')
2 |
3 | /**
4 | * Returns information about a block by block number.
5 | *
6 | * @example query { getBlockByNumber(params:["latest"]) }
7 | *
8 | * @see https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbynumber
9 | *
10 | * @param {Object} _ Parent resolver context
11 | * @param {Object} args Query arguments
12 | * @param {Array} args.params RPC request parameters
13 | * @param {Object} context GraphQL request context
14 | * @param {Object} context.makeRPCRequest RPC request factory
15 | * @return {Object}
16 | */
17 | const getBlockByNumber = async (_, { params }, { makeRPCRequest }) => {
18 | try {
19 | const rpc = await makeRPCRequest(METHODS.eth.getBlockByNumber, [
20 | ...params,
21 | true,
22 | ])
23 |
24 | if (rpc.error) {
25 | throw new Error(rpc.error.message)
26 | }
27 |
28 | return rpc.result
29 | } catch (e) {
30 | console.error(e)
31 | }
32 | }
33 |
34 | module.exports = getBlockByNumber
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Luke Hedger
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "es6": true,
4 | "node": true
5 | },
6 | "extends": [
7 | "eslint:recommended"
8 | ],
9 | "parserOptions": {
10 | "ecmaFeatures": {
11 | "experimentalObjectRestSpread": true
12 | },
13 | "ecmaVersion": 8,
14 | "sourceType": "module"
15 | },
16 | "rules": {
17 | "eqeqeq": [
18 | "error",
19 | "always"
20 | ],
21 | "keyword-spacing": [
22 | "error",
23 | {
24 | "before": true,
25 | "after": true
26 | }
27 | ],
28 | "no-console": "off",
29 | "no-irregular-whitespace": "error",
30 | "no-var": "error",
31 | "padding-line-between-statements": [
32 | "error",
33 | {
34 | "blankLine": "always",
35 | "prev": "*",
36 | "next": ["block-like", "function", "return"]
37 | },
38 | {
39 | "blankLine": "always",
40 | "prev": ["const", "let", "var"],
41 | "next": "*"
42 | },
43 | {
44 | "blankLine": "any",
45 | "prev": ["const", "let", "var"],
46 | "next": ["const", "let", "var"]
47 | }
48 | ],
49 | "prefer-const": "error",
50 | "valid-jsdoc": "warn"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/server/query/getBlockTransactionCountByHash.js:
--------------------------------------------------------------------------------
1 | const { METHODS } = require('../constants')
2 |
3 | /**
4 | * Returns the number of transactions in a block from a block matching the given block hash.
5 | *
6 | * @example query { getBlockTransactionCountByHash(params:["0x..."]) }
7 | *
8 | * @see https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblocktransactioncountbyhash
9 | *
10 | * @param {Object} _ Parent resolver context
11 | * @param {Object} args Query arguments
12 | * @param {Array} args.params RPC request parameters
13 | * @param {Object} context GraphQL request context
14 | * @param {Object} context.makeRPCRequest RPC request factory
15 | * @return {String}
16 | */
17 | const getBlockTransactionCountByHash = async (
18 | _,
19 | { params },
20 | { makeRPCRequest }
21 | ) => {
22 | try {
23 | const rpc = await makeRPCRequest(
24 | METHODS.eth.getBlockTransactionCountByHash,
25 | params
26 | )
27 |
28 | if (rpc.error) {
29 | throw new Error(rpc.error.message)
30 | }
31 |
32 | return rpc.result
33 | } catch (e) {
34 | console.error(e)
35 | }
36 | }
37 |
38 | module.exports = getBlockTransactionCountByHash
39 |
--------------------------------------------------------------------------------
/server/query/getBlockTransactionCountByNumber.js:
--------------------------------------------------------------------------------
1 | const { METHODS } = require('../constants')
2 |
3 | /**
4 | * Returns the number of transactions in a block matching the given block number.
5 | *
6 | * @example query { getBlockTransactionCountByNumber(params:["0x..."]) }
7 | *
8 | * @see https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblocktransactioncountbynumber
9 | *
10 | * @param {Object} _ Parent resolver context
11 | * @param {Object} args Query arguments
12 | * @param {Array} args.params RPC request parameters
13 | * @param {Object} context GraphQL request context
14 | * @param {Object} context.makeRPCRequest RPC request factory
15 | * @return {String}
16 | */
17 | const getBlockTransactionCountByNumber = async (
18 | _,
19 | { params },
20 | { makeRPCRequest }
21 | ) => {
22 | try {
23 | const rpc = await makeRPCRequest(
24 | METHODS.eth.getBlockTransactionCountByNumber,
25 | params
26 | )
27 |
28 | if (rpc.error) {
29 | throw new Error(rpc.error.message)
30 | }
31 |
32 | return rpc.result
33 | } catch (e) {
34 | console.error(e)
35 | }
36 | }
37 |
38 | module.exports = getBlockTransactionCountByNumber
39 |
--------------------------------------------------------------------------------
/server/query/getTransactionByBlockHashAndIndex.js:
--------------------------------------------------------------------------------
1 | const { METHODS } = require('../constants')
2 |
3 | /**
4 | * Returns information about a transaction by block hash and transaction index position.
5 | *
6 | * @example query { getTransactionByBlockHashAndIndex(params:["0x...", "0x0"]) }
7 | *
8 | * @see https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionbyblockhashandindex
9 | *
10 | * @param {Object} _ Parent resolver context
11 | * @param {Object} args Query arguments
12 | * @param {Array} args.params RPC request parameters
13 | * @param {Object} context GraphQL request context
14 | * @param {Object} context.makeRPCRequest RPC request factory
15 | * @return {Object}
16 | */
17 | const getTransactionByBlockHashAndIndex = async (
18 | _,
19 | { params },
20 | { makeRPCRequest }
21 | ) => {
22 | try {
23 | const rpc = await makeRPCRequest(
24 | METHODS.eth.getTransactionByBlockHashAndIndex,
25 | params
26 | )
27 |
28 | if (rpc.error) {
29 | throw new Error(rpc.error.message)
30 | }
31 |
32 | return rpc.result
33 | } catch (e) {
34 | console.error(e)
35 | }
36 | }
37 |
38 | module.exports = getTransactionByBlockHashAndIndex
39 |
--------------------------------------------------------------------------------
/server/query/getTransactionByBlockNumberAndIndex.js:
--------------------------------------------------------------------------------
1 | const { METHODS } = require('../constants')
2 |
3 | /**
4 | * Returns information about a transaction by block number and transaction index position.
5 | *
6 | * @example query { getTransactionByBlockNumberAndIndex(params:["0x29c", "0x0"]) }
7 | *
8 | * @see https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionbyblocknumberandindex
9 | *
10 | * @param {Object} _ Parent resolver context
11 | * @param {Object} args Query arguments
12 | * @param {Array} args.params RPC request parameters
13 | * @param {Object} context GraphQL request context
14 | * @param {Object} context.makeRPCRequest RPC request factory
15 | * @return {Object}
16 | */
17 | const getTransactionByBlockNumberAndIndex = async (
18 | _,
19 | { params },
20 | { makeRPCRequest }
21 | ) => {
22 | try {
23 | const rpc = await makeRPCRequest(
24 | METHODS.eth.getTransactionByBlockNumberAndIndex,
25 | params
26 | )
27 |
28 | if (rpc.error) {
29 | throw new Error(rpc.error.message)
30 | }
31 |
32 | return rpc.result
33 | } catch (e) {
34 | console.error(e)
35 | }
36 | }
37 |
38 | module.exports = getTransactionByBlockNumberAndIndex
39 |
--------------------------------------------------------------------------------
/server/query/accountsWithBalances.js:
--------------------------------------------------------------------------------
1 | const unit = require('ethjs-unit')
2 |
3 | const { METHODS } = require('../constants')
4 |
5 | /**
6 | * Returns a list of addresses owned by client with their balance.
7 | *
8 | * @example query { accountsWithBalances { address balance } }
9 | *
10 | * @see https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_accounts
11 | * @see https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getbalance
12 | *
13 | * @param {Object} _ Parent resolver context
14 | * @param {Object} args Query arguments
15 | * @param {Object} context GraphQL request context
16 | * @param {Object} context.makeRPCRequest RPC request factory
17 | * @return {Array}
18 | */
19 | const accountsWithBalances = async (_, args, { makeRPCRequest }) => {
20 | try {
21 | const { result: accounts } = await makeRPCRequest(METHODS.eth.accounts)
22 |
23 | const balances = await Promise.all(
24 | accounts.map(account => makeRPCRequest(METHODS.eth.getBalance, [account]))
25 | )
26 |
27 | const result = balances.map(({ result: balance }, index) => {
28 | return {
29 | address: accounts[index],
30 | balance: unit.fromWei(balance, 'ether'),
31 | }
32 | })
33 |
34 | return result
35 | } catch (e) {
36 | console.error(e)
37 | }
38 | }
39 |
40 | module.exports = accountsWithBalances
41 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eth-graphql",
3 | "version": "1.0.0",
4 | "description": "Ethereum GraphQL API",
5 | "main": "server/index.js",
6 | "lint-staged": {
7 | "*.js": [
8 | "eslint --fix \"server/**/*.js\"",
9 | "prettier --write --no-semi --single-quote --trailing-comma=es5",
10 | "git add"
11 | ]
12 | },
13 | "pre-commit": {
14 | "run": [
15 | "lint:staged",
16 | "test"
17 | ],
18 | "silent": true
19 | },
20 | "scripts": {
21 | "blockchain": "ganache-cli",
22 | "lint:staged": "lint-staged",
23 | "start": "nodemon --watch server server",
24 | "start:prod": "node server",
25 | "test": "jest"
26 | },
27 | "repository": {
28 | "type": "git",
29 | "url": "git+https://github.com/lukehedger/eth-graphql.git"
30 | },
31 | "keywords": [
32 | "ethereum",
33 | "graphql"
34 | ],
35 | "author": "Luke Hedger",
36 | "license": "MIT",
37 | "bugs": {
38 | "url": "https://github.com/lukehedger/eth-graphql/issues"
39 | },
40 | "homepage": "https://github.com/lukehedger/eth-graphql#readme",
41 | "dependencies": {
42 | "boxen": "^1.3.0",
43 | "chalk": "^2.3.0",
44 | "ethjs-unit": "^0.1.6",
45 | "graphql-yoga": "^0.9.0",
46 | "node-fetch": "^1.7.3",
47 | "ws": "^4.0.0"
48 | },
49 | "devDependencies": {
50 | "dotenv": "^4.0.0",
51 | "eslint": "^4.16.0",
52 | "ganache-cli": "^6.1.0-beta.2",
53 | "jest": "^22.1.4",
54 | "lint-staged": "^6.0.1",
55 | "nodemon": "^1.14.11",
56 | "pre-commit": "^1.2.2",
57 | "prettier": "^1.10.2"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/server/query/index.js:
--------------------------------------------------------------------------------
1 | const accounts = require('./accounts.js')
2 | const accountsWithBalances = require('./accountsWithBalances.js')
3 | const blockNumber = require('./blockNumber.js')
4 | const coinbase = require('./coinbase.js')
5 | const gasPrice = require('./gasPrice.js')
6 | const getBalance = require('./getBalance.js')
7 | const getBlockByHash = require('./getBlockByHash.js')
8 | const getBlockByNumber = require('./getBlockByNumber.js')
9 | const getBlockTransactionCountByHash = require('./getBlockTransactionCountByHash.js')
10 | const getBlockTransactionCountByNumber = require('./getBlockTransactionCountByNumber.js')
11 | const getTransactionByBlockHashAndIndex = require('./getTransactionByBlockHashAndIndex.js')
12 | const getTransactionByBlockNumberAndIndex = require('./getTransactionByBlockNumberAndIndex.js')
13 | const getTransactionByHash = require('./getTransactionByHash.js')
14 | const getTransactionCount = require('./getTransactionCount.js')
15 | const hashrate = require('./hashrate.js')
16 | const listening = require('./listening.js')
17 | const mining = require('./mining.js')
18 | const peerCount = require('./peerCount.js')
19 | const protocolVersion = require('./protocolVersion.js')
20 | const version = require('./version.js')
21 |
22 | module.exports = {
23 | accounts,
24 | accountsWithBalances,
25 | blockNumber,
26 | coinbase,
27 | gasPrice,
28 | getBalance,
29 | getBlockByHash,
30 | getBlockByNumber,
31 | getBlockTransactionCountByHash,
32 | getBlockTransactionCountByNumber,
33 | getTransactionByBlockHashAndIndex,
34 | getTransactionByBlockNumberAndIndex,
35 | getTransactionByHash,
36 | getTransactionCount,
37 | hashrate,
38 | listening,
39 | mining,
40 | peerCount,
41 | protocolVersion,
42 | version,
43 | }
44 |
--------------------------------------------------------------------------------
/server/schema.js:
--------------------------------------------------------------------------------
1 | const typeDefs = `
2 | type AccountWithBalance {
3 | address: String!
4 | balance: String!
5 | }
6 |
7 | type Block {
8 | difficulty: String!
9 | extraData: String!
10 | gasLimit: String!
11 | gasUsed: String!
12 | hash: String!
13 | logsBloom: String!
14 | nonce: String!
15 | miner: String!
16 | number: String!
17 | parentHash: String!
18 | receiptsRoot: String!
19 | sha3Uncles: String!
20 | size: String!
21 | stateRoot: String!
22 | totalDifficulty: String!
23 | transactionsRoot: String!
24 | timestamp: String!
25 | transactions: [String]!
26 | uncles: [String]!
27 | }
28 |
29 | type Transaction {
30 | blockHash: String!
31 | blockNumber: String!
32 | from: String!
33 | gas: String!
34 | gasPrice: String!
35 | hash: String!
36 | input: String!
37 | nonce: String!
38 | to: String!
39 | transactionIndex: String!
40 | value: String!
41 | }
42 |
43 | type Query {
44 | accounts(params: [String] = []): [String]!
45 | accountsWithBalances: [AccountWithBalance]!
46 | blockNumber(params: [String] = []): String!
47 | coinbase: String!
48 | gasPrice: String!
49 | getBalance(params: [String] = []): String!
50 | getBlockByHash(params: [String] = []): Block!
51 | getBlockByNumber(params: [String] = []): Block!
52 | getBlockTransactionCountByHash(params: [String] = []): String!
53 | getBlockTransactionCountByNumber(params: [String] = []): String!
54 | getTransactionByBlockHashAndIndex(params: [String] = []): Transaction!
55 | getTransactionByBlockNumberAndIndex(params: [String] = []): Transaction!
56 | getTransactionByHash(params: [String] = []): Transaction!
57 | getTransactionCount(params: [String] = []): String!
58 | hashrate: String!
59 | listening: Boolean!
60 | mining: Boolean!
61 | peerCount: String!
62 | protocolVersion: String!
63 | version: String!
64 | }
65 | `
66 |
67 | module.exports = typeDefs
68 |
--------------------------------------------------------------------------------
/server/util/makeRPCRequest.js:
--------------------------------------------------------------------------------
1 | const fetch = require('node-fetch')
2 | const getURLProtocol = require('./getURLProtocol')
3 | const makeWebSocketConnection = require('./makeWebSocketConnection')
4 |
5 | /**
6 | * Generate RPC request data
7 | *
8 | * @param {String} method RPC method
9 | * @param {Array} params RPC parameters
10 | * @param {Number} [id=1] RPC request ID
11 | * @return {String} Stringified request data
12 | */
13 | const getRPCRequestBody = (method, params, id = 1) => {
14 | return JSON.stringify({
15 | jsonrpc: '2.0',
16 | method: method,
17 | params: params,
18 | id: id,
19 | })
20 | }
21 |
22 | /**
23 | * Send RPC request over HTTP
24 | *
25 | * @param {String} endpoint RPC endpoint
26 | * @param {String} method RPC method
27 | * @param {Array} params RPC parameters
28 | * @return {Object} RPC response body
29 | */
30 | const httpRPCRequest = endpoint => (method, params) => {
31 | return fetch(endpoint, {
32 | method: 'POST',
33 | headers: { 'Content-Type': 'application/json' },
34 | body: getRPCRequestBody(method, params),
35 | }).then(res => res.json())
36 | }
37 |
38 | /**
39 | * Send RPC request over WebSocket
40 | *
41 | * @param {Object} ws RPC WebSocket connection
42 | * @param {Object} eventEmitter Node.js EventEmitter
43 | * @param {String} method RPC method
44 | * @param {Array} params RPC parameters
45 | * @return {Object} RPC response body
46 | */
47 | const wsRPCRequest = (ws, eventEmitter) => (method, params) => {
48 | return new Promise((resolve, reject) => {
49 | const id = new Date().getTime()
50 |
51 | // send message
52 | ws.send(getRPCRequestBody(method, params, id))
53 |
54 | // on error callback
55 | const onError = error => reject({ error })
56 |
57 | // on message callback
58 | const onMessage = result => {
59 | eventEmitter.removeListener('error', onError)
60 |
61 | return resolve({ result })
62 | }
63 |
64 | // listen for error
65 | eventEmitter.once('error', onError)
66 |
67 | // listen for result
68 | eventEmitter.once(id, onMessage)
69 | })
70 | }
71 |
72 | /**
73 | * Construct RPC request
74 | *
75 | * @param {String} endpoint RPC endpoint
76 | * @param {Object} eventEmitter Node.js EventEmitter
77 | * @return {Object} RPC response body
78 | */
79 | const makeRPCRequest = (endpoint, eventEmitter) => {
80 | const protocol = getURLProtocol(endpoint)
81 |
82 | let rpcRequest
83 |
84 | switch (protocol) {
85 | case 'ws:':
86 | rpcRequest = wsRPCRequest(
87 | makeWebSocketConnection(endpoint, eventEmitter),
88 | eventEmitter
89 | )
90 | break
91 | case 'http:':
92 | case 'https:':
93 | rpcRequest = httpRPCRequest(endpoint)
94 | break
95 | }
96 |
97 | return rpcRequest
98 | }
99 |
100 | module.exports = makeRPCRequest
101 |
--------------------------------------------------------------------------------