Skip to content

Commit

Permalink
Merge pull request #488 from cosmos/matt/301-lcd-client
Browse files Browse the repository at this point in the history
 Move LCD client into this repo
  • Loading branch information
faboweb authored Feb 22, 2018
2 parents add0edd + 3ca44b1 commit ca8582d
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 23 deletions.
105 changes: 105 additions & 0 deletions app/src/renderer/lcdClient.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
'use strict'

const axios = require('axios')

// returns an async function which makes a request for the given
// HTTP method (GET/POST/DELETE/etc) and path (/foo/bar)
function req (method, path) {
return async function (data) {
return await this.request(method, path, data)
}
}

// returns an async function which makes a request for the given
// HTTP method and path, which accepts arguments to be appended
// to the path (/foo/{arg}/...)
function argReq (method, path) {
return async function (args, data) {
// `args` can either be a single value or an array
if (Array.isArray(args)) {
args = args.join('/')
}
return await this.request(method, `${path}/${args}`, data)
}
}

class Client {
constructor (server = 'http://localhost:8998') {
this.server = server
}

async request (method, path, data) {
try {
let res = await axios[method.toLowerCase()](this.server + path, data)
return res.data
} catch (resError) {
if (!resError.response || !resError.response.data) {
throw resError
}
let data = resError.response.data
// server responded with error message, create an Error from that
let error = Error(data.error)
error.code = data.code
throw error
}
}
}

let fetchAccount = argReq('GET', '/query/account')
let fetchNonce = argReq('GET', '/query/nonce')

Object.assign(Client.prototype, {
sign: req('POST', '/sign'),
postTx: req('POST', '/tx'),

// keys
generateKey: req('POST', '/keys'),
listKeys: req('GET', '/keys'),
getKey: argReq('GET', '/keys'),
updateKey: argReq('PUT', '/keys'),
deleteKey: argReq('DELETE', '/keys'),
recoverKey: req('POST', '/keys/recover'),

// coins
buildSend: req('POST', '/build/send'),
async queryAccount (address) {
try {
return await fetchAccount.call(this, address)
} catch (err) {
// if account not found, return null instead of throwing
if (err.message.includes('account bytes are empty')) {
return null
}
throw err
}
},
coinTxs: argReq('GET', '/tx/coin'),

// nonce
async queryNonce (address) {
try {
return await fetchNonce.call(this, address)
} catch (err) {
// if nonce not found, return 0 instead of throwing
if (err.message.includes('nonce empty')) {
return 0
}
throw err
}
},

// Tendermint RPC
status: req('GET', '/tendermint/status'),

// staking
candidate: argReq('GET', '/query/stake/candidates'),
candidates: req('GET', '/query/stake/candidates'),
buildDelegate: req('POST', '/build/stake/delegate'),
buildUnbond: req('POST', '/build/stake/unbond'),
bondingsByDelegator: argReq('GET', '/tx/bondings/delegator'),
bondingsByValidator: argReq('GET', '/tx/bondings/validator')

// TODO: separate API registration for different modules
})

module.exports = Client
2 changes: 1 addition & 1 deletion app/src/renderer/node.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict'
const RpcClient = require('tendermint')
const RestClient = require('cosmos-sdk')
const RestClient = require('./lcdClient.js')

module.exports = function (nodeIP, relayPort, lcdPort) {
const RELAY_SERVER = 'http://localhost:' + relayPort
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@
"axios": "^0.17.0",
"casual": "^1.5.19",
"chart.js": "^2.6.0",
"cosmos-sdk": "^1.5.1",
"deterministic-tar": "^0.1.2",
"deterministic-zip": "^1.0.5",
"electron": "^1.7.5",
Expand Down
125 changes: 125 additions & 0 deletions test/unit/specs/lcdClient.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
let axios = require('axios')
let LcdClient = require('../../../app/src/renderer/lcdClient.js')

describe('LCD Client', () => {
let client = new LcdClient()

it('makes a GET request with no args', async () => {
axios.get = jest.fn()
.mockReturnValueOnce(Promise.resolve({
data: { foo: 'bar' }
}))

let res = await client.status()
expect(res).toEqual({ foo: 'bar' })
expect(axios.get.mock.calls[0]).toEqual([
'http://localhost:8998/tendermint/status',
undefined
])
})

it('makes a GET request with one arg', async () => {
axios.get = jest.fn()
.mockReturnValueOnce(Promise.resolve({
data: { foo: 'bar' }
}))

let res = await client.getKey('myKey')
expect(res).toEqual({ foo: 'bar' })
expect(axios.get.mock.calls[0]).toEqual([
'http://localhost:8998/keys/myKey',
undefined
])
})

it('makes a GET request with multiple args', async () => {
axios.get = jest.fn()
.mockReturnValueOnce(Promise.resolve({
data: { foo: 'bar' }
}))

let res = await client.bondingsByDelegator([ 'foo', 'bar' ])
expect(res).toEqual({ foo: 'bar' })
expect(axios.get.mock.calls[0]).toEqual([
'http://localhost:8998/tx/bondings/delegator/foo/bar',
undefined
])
})

it('makes a POST request', async () => {
axios.post = jest.fn()
.mockReturnValueOnce(Promise.resolve({
data: { foo: 'bar' }
}))

let res = await client.generateKey()
expect(res).toEqual({ foo: 'bar' })
expect(axios.post.mock.calls[0]).toEqual([
'http://localhost:8998/keys',
undefined
])
})

it('makes a POST request with args and data', async () => {
axios.put = jest.fn()
.mockReturnValueOnce(Promise.resolve({
data: { foo: 'bar' }
}))

let res = await client.updateKey('myKey', { abc: 123 })
expect(res).toEqual({ foo: 'bar' })
expect(axios.put.mock.calls[0]).toEqual([
'http://localhost:8998/keys/myKey',
{ abc: 123 }
])
})

it('makes a GET request with an error', async () => {
axios.get = jest.fn()
.mockReturnValueOnce(Promise.reject({
response: {
data: {
error: 'foo',
code: 123
}
}
}))

try {
await client.status()
} catch (err) {
expect(err.message).toBe('foo')
expect(err.code).toBe(123)
}
expect(axios.get.mock.calls[0]).toEqual([
'http://localhost:8998/tendermint/status',
undefined
])
})

it('does not throw error for empty results', async () => {
axios.get = jest.fn()
.mockReturnValueOnce(Promise.reject({
response: {
data: {
error: 'account bytes are empty',
code: 1
}
}
}))
let res = await client.queryAccount('address')
expect(res).toBe(null)

axios.get = jest.fn()
.mockReturnValueOnce(Promise.reject({
response: {
data: {
error: 'nonce empty',
code: 2
}
}
}))
res = await client.queryNonce('address')
expect(res).toBe(0)
})
})
22 changes: 1 addition & 21 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -391,13 +391,6 @@ aws4@^1.2.1, aws4@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"

axios@^0.16.2:
version "0.16.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.16.2.tgz#ba4f92f17167dfbab40983785454b9ac149c3c6d"
dependencies:
follow-redirects "^1.2.3"
is-buffer "^1.1.5"

axios@^0.17.0, axios@^0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.17.1.tgz#2d8e3e5d0bdbd7327f91bc814f5c57660f81824d"
Expand Down Expand Up @@ -1874,13 +1867,6 @@ cosmiconfig@^2.1.0, cosmiconfig@^2.1.1:
parse-json "^2.2.0"
require-from-string "^1.1.0"

cosmos-sdk@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/cosmos-sdk/-/cosmos-sdk-1.5.1.tgz#4f648aa7eaa781883dfa1bef9ee78b4fba299cf3"
dependencies:
axios "^0.16.2"
old "^0.2.0"

crc32-stream@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-1.0.1.tgz#ce2c5dc3bd8ffb3830f9cb47f540222c63c90fab"
Expand Down Expand Up @@ -3246,7 +3232,7 @@ flatten@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"

follow-redirects@^1.2.3, follow-redirects@^1.2.5:
follow-redirects@^1.2.5:
version "1.4.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.4.1.tgz#d8120f4518190f55aac65bb6fc7b85fcd666d6aa"
dependencies:
Expand Down Expand Up @@ -5803,12 +5789,6 @@ old@^0.1.3:
dependencies:
object-assign "^4.1.0"

old@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/old/-/old-0.2.0.tgz#ae75a9f33bae7cb3fe06312899b7ae5a73ba24ef"
dependencies:
object-assign "^4.1.0"

on-finished@~2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
Expand Down

0 comments on commit ca8582d

Please sign in to comment.