Skip to content
This repository has been archived by the owner on Apr 2, 2024. It is now read-only.

Commit

Permalink
feat: Add endpoint to retrieve exchange rates (#767)
Browse files Browse the repository at this point in the history
  • Loading branch information
bennycode committed Sep 30, 2022
1 parent 56d9ec8 commit 533cb63
Show file tree
Hide file tree
Showing 6 changed files with 884 additions and 2 deletions.
7 changes: 5 additions & 2 deletions src/client/RESTClient.ts
Expand Up @@ -16,13 +16,14 @@ import {FillAPI} from '../fill';
import querystring from 'querystring';
import {ProfileAPI} from '../profile';
import axiosRetry, {isNetworkOrIdempotentRequestError} from 'axios-retry';
import util from 'util';
import util, {DebugLogger} from 'util';
import {EventEmitter} from 'events';
import {getErrorMessage, gotRateLimited, inAirPlaneMode} from '../error/ErrorUtil';
import {CurrencyAPI} from '../currency';
import {WithdrawAPI} from '../withdraw';
import {TransferAPI} from '../transfer';
import {TimeAPI} from '../time';
import {ExchangeRateAPI} from '../exchange-rate/ExchangeRateAPI';

export interface RESTClient {
on(
Expand All @@ -46,6 +47,7 @@ export class RESTClient extends EventEmitter {

readonly account: AccountAPI;
readonly currency: CurrencyAPI;
readonly exchangeRate: ExchangeRateAPI;
readonly fee: FeeAPI;
readonly fill: FillAPI;
readonly order: OrderAPI;
Expand All @@ -57,7 +59,7 @@ export class RESTClient extends EventEmitter {
readonly withdraw: WithdrawAPI;

private readonly httpClient: AxiosInstance;
private readonly logger: (msg: string, ...param: any[]) => void;
private readonly logger: DebugLogger;

constructor(baseURL: string, private readonly signRequest: (setup: RequestSetup) => Promise<SignedRequest>) {
super();
Expand Down Expand Up @@ -113,6 +115,7 @@ export class RESTClient extends EventEmitter {

this.account = new AccountAPI(this.httpClient);
this.currency = new CurrencyAPI(this.httpClient);
this.exchangeRate = new ExchangeRateAPI();
this.fee = new FeeAPI(this.httpClient);
this.fill = new FillAPI(this.httpClient);
this.order = new OrderAPI(this.httpClient);
Expand Down
12 changes: 12 additions & 0 deletions src/client/WebSocketClient.test.ts
Expand Up @@ -63,6 +63,13 @@ describe('WebSocketClient', () => {
const invalidUrl = 'ws://localhost:50001';
const ws = createWebSocketClient(invalidUrl);
ws.on(WebSocketEvent.ON_ERROR, () => {
/**
* TODO:
* An asynchronous function called its 'done' callback more than once. This is a bug in the spec, beforeAll,
* beforeEach, afterAll, or afterEach function in question. This will be treated as an error in a future
* version. See:
* https://jasmine.github.io/tutorials/upgrading_to_Jasmine_4.0#deprecations-due-to-calling-done-multiple-times
*/
done();
});
ws.connect();
Expand Down Expand Up @@ -93,6 +100,7 @@ describe('WebSocketClient', () => {
done();
});

// TODO: This test appears to be flaky
it('returns true when called after the connection is created', done => {
const ws = createWebSocketClient();

Expand All @@ -109,6 +117,7 @@ describe('WebSocketClient', () => {
ws.connect();
});

// TODO: This test appears to be flaky
it('returns false when called after the connection is closed', done => {
const ws = createWebSocketClient();

Expand Down Expand Up @@ -258,6 +267,7 @@ describe('WebSocketClient', () => {
ws.connect();
});

// TODO: This test appears to be flaky
it('receives typed "snapshot" messages from "level2" channel', done => {
const channel = {
name: WebSocketChannelName.LEVEL2,
Expand Down Expand Up @@ -477,6 +487,7 @@ describe('WebSocketClient', () => {
});

describe('unsubscribe', () => {
// TODO: This test appears to be flaky
it('unsubscribes from all products on a channel', done => {
server.on('connection', socket => {
socket.on('message', (message: string) => {
Expand Down Expand Up @@ -508,6 +519,7 @@ describe('WebSocketClient', () => {
});

describe('setupHeartbeat', () => {
// TODO: This test appears to be flaky
it('sends ping messages within a defined interval', done => {
server.on('connection', socket => {
socket.on('ping', async () => {
Expand Down
36 changes: 36 additions & 0 deletions src/exchange-rate/ExchangeRateAPI.test.ts
@@ -0,0 +1,36 @@
import nock from 'nock';
import getExchangeRates from '../test/fixtures/rest/v2/exchange-rates/GET-200.json';
import getExchangeRatesEUR from '../test/fixtures/rest/v2/exchange-rates/GET-200-EUR.json';
import {ExchangeRateAPI} from './ExchangeRateAPI';

describe('ExchangeRateAPI', () => {
afterEach(() => nock.cleanAll());

beforeEach(() => {
nock.cleanAll();
});

describe('getExchangeRates', () => {
it('returns the exchange rates based on USD by default', async () => {
nock('https://api.coinbase.com')
.get(ExchangeRateAPI.URL.V2_EXCHANGE_RATES)
.query({currency: 'USD'})
.reply(200, JSON.stringify(getExchangeRates));

const rates = await global.client.rest.exchangeRate.getExchangeRates();
expect(rates.currency).toBe('USD');
expect(rates.rates['EUR']).toBe('1.0208');
});

it('returns the exchange rates for a specific currency', async () => {
nock('https://api.coinbase.com')
.get(ExchangeRateAPI.URL.V2_EXCHANGE_RATES)
.query({currency: 'EUR'})
.reply(200, JSON.stringify(getExchangeRatesEUR));

const rates = await global.client.rest.exchangeRate.getExchangeRates('EUR');
expect(rates.currency).toBe('EUR');
expect(rates.rates['IOTX']).toBe('29.4767870302137067');
});
});
});
30 changes: 30 additions & 0 deletions src/exchange-rate/ExchangeRateAPI.ts
@@ -0,0 +1,30 @@
import axios from 'axios';

export interface ExchangeRate {
currency: string;
rates: {[currency: string]: string};
}

/**
* @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-exchange-rates
*/
export class ExchangeRateAPI {
static readonly URL = {
V2_EXCHANGE_RATES: `/v2/exchange-rates`,
};

constructor(private readonly baseURL: string = 'https://api.coinbase.com') {}

/**
* Get current exchange rates. Default base currency is USD, but it can be defined as any supported currency.
* Returned rates will define the exchange rate for one unit of the base currency.
*
* @see https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-exchange-rates#get-exchange-rates
*/
async getExchangeRates(currency: string = 'USD'): Promise<ExchangeRate> {
const response = await axios.get<{
data: ExchangeRate;
}>(`${this.baseURL}${ExchangeRateAPI.URL.V2_EXCHANGE_RATES}`, {params: {currency}});
return response.data.data;
}
}

0 comments on commit 533cb63

Please sign in to comment.