Skip to content

Commit

Permalink
Use Infura API v3
Browse files Browse the repository at this point in the history
  • Loading branch information
whymarrh committed Sep 7, 2020
1 parent 34d0cd2 commit 6b3b5d0
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 63 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
const createInfuraProvider = require('eth-json-rpc-infura/src/createProvider')
const Ethjs = require('ethjs')

const provider = createInfuraProvider({ network: 'mainnet' })
const provider = createInfuraProvider({ network: 'mainnet', projectId: 'example' })
const eth = new Ethjs(provider)
```

Expand All @@ -19,5 +19,5 @@ const createInfuraMiddleware = require('eth-json-rpc-infura')
const RpcEngine = require('json-rpc-engine')

const engine = new RpcEngine()
engine.push(createInfuraMiddleware({ network: 'ropsten' }))
engine.push(createInfuraMiddleware({ network: 'ropsten', projectId: 'example' }))
```
61 changes: 28 additions & 33 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware
const { ethErrors: rpcErrors } = require('eth-rpc-errors')
const fetch = require('node-fetch')

const POST_METHODS = ['eth_call', 'eth_estimateGas', 'eth_sendRawTransaction']
const RETRIABLE_ERRORS = [
// ignore server overload errors
'Gateway timeout',
Expand All @@ -19,9 +18,15 @@ module.exports.fetchConfigFromReq = fetchConfigFromReq
function createInfuraMiddleware (opts = {}) {
const network = opts.network || 'mainnet'
const maxAttempts = opts.maxAttempts || 5
const { source } = opts
const { source, projectId, headers = {} } = opts

// validate options
if (!projectId || typeof projectId !== 'string') {
throw new Error(`Invalid value for 'projectId': "${projectId}"`)
}
if (!headers || typeof headers !== 'object') {
throw new Error(`Invalid value for 'headers': "${headers}"`)
}
if (!maxAttempts) {
throw new Error(`Invalid value for 'maxAttempts': "${maxAttempts}" (${typeof maxAttempts})`)
}
Expand All @@ -31,7 +36,7 @@ function createInfuraMiddleware (opts = {}) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
// attempt request
await performFetch(network, req, res, source)
await performFetch(network, projectId, headers, req, res, source)
// request was successful
break
} catch (err) {
Expand Down Expand Up @@ -67,8 +72,8 @@ function isRetriableError (err) {
return RETRIABLE_ERRORS.some((phrase) => errMessage.includes(phrase))
}

async function performFetch (network, req, res, source) {
const { fetchUrl, fetchParams } = fetchConfigFromReq({ network, req, source })
async function performFetch (network, projectId, extraHeaders, req, res, source) {
const { fetchUrl, fetchParams } = fetchConfigFromReq({ network, projectId, extraHeaders, req, source })
const response = await fetch(fetchUrl, fetchParams)
const rawData = await response.text()
// handle errors
Expand All @@ -77,7 +82,7 @@ async function performFetch (network, req, res, source) {
case 405:
throw rpcErrors.rpc.methodNotFound()

case 418:
case 429:
throw createRatelimitError()

case 503:
Expand All @@ -103,36 +108,26 @@ async function performFetch (network, req, res, source) {
res.error = data.error
}

function fetchConfigFromReq ({ network, req, source }) {
function fetchConfigFromReq ({ network, projectId, extraHeaders, req, source }) {
const requestOrigin = req.origin || 'internal'
const cleanReq = normalizeReq(req)
const { method, params } = cleanReq

const fetchParams = {}
let fetchUrl = `https://api.infura.io/v1/jsonrpc/${network}`
const isPostMethod = POST_METHODS.includes(method)
if (isPostMethod) {
fetchParams.method = 'POST'
fetchParams.headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
}
if (source) {
fetchParams.headers['Infura-Source'] = `${source}/${requestOrigin}`
}
fetchParams.body = JSON.stringify(cleanReq)
} else {
fetchParams.method = 'GET'
if (source) {
fetchParams.headers = {
'Infura-Source': `${source}/${requestOrigin}`,
}
}
const paramsString = encodeURIComponent(JSON.stringify(params))
fetchUrl += `/${method}?params=${paramsString}`
const headers = {
...extraHeaders,
'Accept': 'application/json',
'Content-Type': 'application/json',
}

if (source) {
headers['Infura-Source'] = `${source}/${requestOrigin}`
}

return { fetchUrl, fetchParams }
return {
fetchUrl: `https://${network}.infura.io/v3/${projectId}`,
fetchParams: {
method: 'POST',
headers,
body: JSON.stringify(normalizeReq(req)),
},
}
}

// strips out extra keys that could be rejected by strict nodes like parity
Expand Down
89 changes: 61 additions & 28 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,36 @@
const test = require('tape')
const { fetchConfigFromReq } = require('../src')
const createInfuraMiddleware = require('../src')
const { fetchConfigFromReq } = createInfuraMiddleware

test('createInfuraMiddleware throws with an invalid projectId', (t) => {
t.throws(() => createInfuraMiddleware({ projectId: 42 }), /Invalid value for 'projectId'/u)
t.throws(() => createInfuraMiddleware({ projectId: null }), /Invalid value for 'projectId'/u)
t.throws(() => createInfuraMiddleware({ projectId: undefined }), /Invalid value for 'projectId'/u)
t.throws(() => createInfuraMiddleware({ projectId: '' }), /Invalid value for 'projectId'/u)
t.end()
})

test('fetchConfigFromReq - basic', (t) => {

const network = 'mainnet'
const projectId = 'foo'
const req = {
jsonrpc: '2.0',
id: 1,
method: 'eth_getBlockByNumber',
params: ['0x482103', true],
}

const { fetchUrl, fetchParams } = fetchConfigFromReq({ network, req, source: 'eth-json-rpc-infura' })
t.equals(fetchUrl, 'https://api.infura.io/v1/jsonrpc/mainnet/eth_getBlockByNumber?params=%5B%220x482103%22%2Ctrue%5D')
const { fetchUrl, fetchParams } = fetchConfigFromReq({ network, req, source: 'eth-json-rpc-infura', projectId })
t.equals(fetchUrl, 'https://mainnet.infura.io/v3/foo')
t.deepEquals(fetchParams, {
method: 'GET',
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Infura-Source': 'eth-json-rpc-infura/internal',
},
})
t.end()

})

test('fetchConfigFromReq - basic: no source specified', (t) => {

const network = 'mainnet'
const req = {
method: 'eth_getBlockByNumber',
params: ['0x482103', true],
}

const { fetchUrl, fetchParams } = fetchConfigFromReq({ network, req })
t.equals(fetchUrl, 'https://api.infura.io/v1/jsonrpc/mainnet/eth_getBlockByNumber?params=%5B%220x482103%22%2Ctrue%5D')
t.deepEquals(fetchParams, {
method: 'GET',
body: '{"id":1,"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x482103",true]}',
})
t.end()

Expand All @@ -41,21 +39,24 @@ test('fetchConfigFromReq - basic: no source specified', (t) => {
test('fetchConfigFromReq - basic', (t) => {

const network = 'ropsten'
const projectId = 'foo'
const req = {
jsonrpc: '2.0',
id: 1,
method: 'eth_sendRawTransaction',
params: ['0x0102030405060708090a0b0c0d0e0f'],
}

const { fetchUrl, fetchParams } = fetchConfigFromReq({ network, req, source: 'eth-json-rpc-infura' })
t.equals(fetchUrl, 'https://api.infura.io/v1/jsonrpc/ropsten')
const { fetchUrl, fetchParams } = fetchConfigFromReq({ network, req, source: 'eth-json-rpc-infura', projectId })
t.equals(fetchUrl, 'https://ropsten.infura.io/v3/foo')
t.deepEquals(fetchParams, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Infura-Source': 'eth-json-rpc-infura/internal',
},
body: JSON.stringify(req),
body: '{"id":1,"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x0102030405060708090a0b0c0d0e0f"]}',
})
t.end()

Expand All @@ -64,14 +65,15 @@ test('fetchConfigFromReq - basic', (t) => {
test('fetchConfigFromReq - strip non-standard keys', (t) => {

const network = 'ropsten'
const projectId = 'foo'
const req = {
method: 'eth_sendRawTransaction',
params: ['0x0102030405060708090a0b0c0d0e0f'],
origin: 'happydapp.eth',
}

const { fetchUrl, fetchParams } = fetchConfigFromReq({ network, req })
t.equals(fetchUrl, 'https://api.infura.io/v1/jsonrpc/ropsten')
const { fetchUrl, fetchParams } = fetchConfigFromReq({ network, req, projectId })
t.equals(fetchUrl, 'https://ropsten.infura.io/v3/foo')
const parsedReq = JSON.parse(fetchParams.body)
t.notOk('origin' in parsedReq, 'non-standard key removed from req')
t.end()
Expand All @@ -81,14 +83,17 @@ test('fetchConfigFromReq - strip non-standard keys', (t) => {
test('fetchConfigFromReq - source specified for request origin in header', (t) => {

const network = 'ropsten'
const projectId = 'foo'
const req = {
jsonrpc: '2.0',
id: 1,
method: 'eth_sendRawTransaction',
params: ['0x0102030405060708090a0b0c0d0e0f'],
origin: 'happydapp.eth',
}

const { fetchUrl, fetchParams } = fetchConfigFromReq({ network, req, source: 'eth-json-rpc-infura' })
t.equals(fetchUrl, 'https://api.infura.io/v1/jsonrpc/ropsten')
const { fetchUrl, fetchParams } = fetchConfigFromReq({ network, req, source: 'eth-json-rpc-infura', projectId })
t.equals(fetchUrl, 'https://ropsten.infura.io/v3/foo')
t.deepEquals(fetchParams, {
method: 'POST',
headers: {
Expand All @@ -101,3 +106,31 @@ test('fetchConfigFromReq - source specified for request origin in header', (t) =
t.end()

})

test('fetchConfigFromReq - extraHeaders specified', (t) => {

const network = 'ropsten'
const projectId = 'foo'
const extraHeaders = { 'User-Agent': 'app/1.0' }
const req = {
jsonrpc: '2.0',
id: 1,
method: 'eth_sendRawTransaction',
params: ['0x0102030405060708090a0b0c0d0e0f'],
origin: 'happydapp.eth',
}

const { fetchUrl, fetchParams } = fetchConfigFromReq({ network, req, projectId, extraHeaders })
t.equals(fetchUrl, 'https://ropsten.infura.io/v3/foo')
t.deepEquals(fetchParams, {
method: 'POST',
headers: {
'User-Agent': 'app/1.0',
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: fetchParams.body,
})
t.end()

})

0 comments on commit 6b3b5d0

Please sign in to comment.