shrimpy-node-temp
Version:
Client for the Shrimpy API
307 lines (272 loc) • 12 kB
text/typescript
import Decimal from "decimal.js";
import * as rp from 'request-promise-native';
import { AuthenticationProvider } from "./authentication-provider";
import {
IAccount,
IAccountBalance,
IApiKeyPermissions,
IApiKeys,
IStrategy,
ITicker,
ITrade,
ITradeChanges,
IUser,
} from "../models";
import {
IAccountBalanceDto,
IGuidIdResultDto,
INumberIdResultDto,
IRebalancePeriodResultDto,
IStrategyDto,
ITickerDto,
ITradeChangesDto,
ITradeDto,
IUserDto,
} from "../dtos";
import {
AccountBalanceDtoConverter,
DecimalDtoConverter,
StrategyDtoConverter,
TickerDtoConverter,
TradeChangesDtoConverter,
TradeDtoConverter,
UserDtoConverter,
} from "../dto-converters";
export class ShrimpyApiClient {
private _accountBalanceDtoConverter = new AccountBalanceDtoConverter();
private _decimalDtoConverter = new DecimalDtoConverter();
private _userDtoConverter = new UserDtoConverter();
private _strategyDtoConverter = new StrategyDtoConverter();
private _tickerDtoConverter = new TickerDtoConverter();
private _tradeChangesDtoConverter = new TradeChangesDtoConverter();
private _tradeDtoConverter = new TradeDtoConverter();
private _authenticationProvider: AuthenticationProvider | null = null;
constructor(
publicKey?: string,
privateKey?: string,
) {
if (publicKey && privateKey) {
// keys were supplied, use them
this._setApiCredentials(publicKey, privateKey);
}
}
public async getTicker(exchange: string): Promise<ITicker[]> {
const endpoint = `${exchange}/ticker`;
const tickerDtos = await this._callEndpoint<ITickerDto[]>(endpoint, 'GET', null, false);
return tickerDtos.map((tickerDto) => {
return this._tickerDtoConverter.convertFromDto(tickerDto);
});
}
public async getUsers(): Promise<IUser[]> {
const endpoint = 'users';
const userDtos = await this._callEndpoint<IUserDto[]>(endpoint, 'GET', null, true);
return userDtos.map((userDto) => {
return this._userDtoConverter.convertFromDto(userDto);
});
}
public async getUser(userId: string): Promise<IUser> {
const endpoint = `users/${userId}`;
const userDto = await this._callEndpoint<IUserDto>(endpoint, 'GET', null, true);
return this._userDtoConverter.convertFromDto(userDto);
}
public async createUser(): Promise<string> {
const endpoint = 'users';
const result = await this._callEndpoint<IGuidIdResultDto>(endpoint, 'POST', null, true);
return result.id;
}
public async enableUser(userId: string): Promise<void> {
const endpoint = `users/${userId}/enable`;
await this._callEndpoint<any>(endpoint, 'POST', null, true);
}
public async disableUser(userId: string): Promise<void> {
const endpoint = `users/${userId}/disable`;
await this._callEndpoint<any>(endpoint, 'POST', null, true);
}
public async createApiKeys(userId: string): Promise<IApiKeys> {
const endpoint = `users/${userId}/keys`;
return await this._callEndpoint<IApiKeys>(endpoint, 'POST', null, true);
}
public async deleteApiKeys(userId: string, publicKey: string): Promise<void> {
const endpoint = `users/${userId}/keys/${publicKey}`;
return await this._callEndpoint<any>(endpoint, 'DELETE', null, true);
}
public async getApiKeys(userId: string): Promise<string[]> {
const endpoint = `users/${userId}/keys`;
return await this._callEndpoint<string[]>(endpoint, 'GET', null, true);
}
public async setPermissions(
userId: string,
publicKey: string,
account: boolean,
trade: boolean,
): Promise<void> {
const endpoint = `users/${userId}/keys/${publicKey}/permissions`;
const parameters: IApiKeyPermissions = {
account: account,
trade: trade
};
await this._callEndpoint<any>(endpoint, 'POST', parameters, true);
}
public async getPermissions(userId: string, publicKey: string): Promise<IApiKeyPermissions> {
const endpoint = `users/${userId}/keys/${publicKey}/permissions`;
return await this._callEndpoint<IApiKeyPermissions>(endpoint, 'GET', null, true);
}
public async getAccounts(userId: string): Promise<IAccount[]> {
const endpoint = `users/${userId}/accounts`;
return await this._callEndpoint<IAccount[]>(endpoint, 'GET', null, true);
}
public async getAccount(userId: string, accountId: number): Promise<IAccount> {
const endpoint = `users/${userId}/accounts/${accountId}`;
return await this._callEndpoint<IAccount>(endpoint, 'GET', null, true);
}
public async createAccount(
userId: string,
exchange: string,
publicKey: string,
privateKey: string,
passphrase: string,
): Promise<number> {
const endpoint = `users/${userId}/accounts`;
const parameters: { [key: string]: any } = {
exchange: exchange,
publicKey: publicKey,
privateKey: privateKey,
passphrase: passphrase,
};
const result = await this._callEndpoint<INumberIdResultDto>(endpoint, 'POST', parameters, true);
return result.id;
}
public async deleteAccount(userId: string, accountId: number): Promise<void> {
const endpoint = `users/${userId}/accounts/${accountId}`;
await this._callEndpoint<any>(endpoint, 'DELETE', null, true);
}
public async rebalance(userId: string, accountId: number): Promise<void> {
const endpoint = `users/${userId}/accounts/${accountId}/rebalance`;
await this._callEndpoint<any>(endpoint, 'POST', null, true);
}
public async getRebalancePeriod(userId: string, accountId: number): Promise<number> {
const endpoint = `users/${userId}/accounts/${accountId}/rebalance_period`;
const result = await this._callEndpoint<IRebalancePeriodResultDto>(endpoint, 'GET', null, true);
return result.rebalancePeriod;
}
public async setRebalancePeriod(
userId: string,
accountId: number,
rebalancePeriodHours: number
): Promise<void> {
// verify rebalancePeriodHours is an integer
if (!Number.isInteger(rebalancePeriodHours)) {
throw new Error("Invalid rebalance period. Rebalance period must be an integer");
}
const endpoint = `users/${userId}/accounts/${accountId}/rebalance_period`;
const parameters: { rebalancePeriod: number } = {
rebalancePeriod: rebalancePeriodHours
};
await this._callEndpoint<any>(endpoint, 'POST', parameters, true);
}
public async setStrategy(
userId: string,
accountId: number,
strategy: IStrategy,
): Promise<void> {
const endpoint = `users/${userId}/accounts/${accountId}/strategy`;
let parameters: IStrategyDto = this._strategyDtoConverter.convertToDto(strategy);
await this._callEndpoint<any>(endpoint, 'POST', parameters, true);
}
public async getStrategy(
userId: string,
accountId: number,
): Promise<IStrategy> {
const endpoint = `users/${userId}/accounts/${accountId}/strategy`;
const strategyDto = await this._callEndpoint<IStrategyDto>(endpoint, 'GET', null, true);
return this._strategyDtoConverter.convertFromDto(strategyDto);
}
public async allocate(
userId: string,
accountId: number,
strategy: IStrategy,
): Promise<void> {
const endpoint = `users/${userId}/accounts/${accountId}/allocate`;
let parameters: IStrategyDto = this._strategyDtoConverter.convertToDto(strategy);
await this._callEndpoint<any>(endpoint, 'POST', parameters, true);
}
public async createTrade(
userId: string,
accountId: number,
fromSymbol: string,
toSymbol: string,
amount: Decimal
): Promise<string> {
const endpoint = `users/${userId}/accounts/${accountId}/trades`;
let parameters: { fromSymbol: string, toSymbol: string, amount: string } = {
fromSymbol: fromSymbol,
toSymbol: toSymbol,
amount: this._decimalDtoConverter.convertToDto(amount),
};
const result = await this._callEndpoint<IGuidIdResultDto>(endpoint, 'POST', parameters, true);
return result.id;
}
public async getActiveTrades(userId: string, accountId: number): Promise<ITrade[]> {
const endpoint = `users/${userId}/accounts/${accountId}/trades`;
const tradeDtos = await this._callEndpoint<ITradeDto[]>(endpoint, 'GET', null, true);
return tradeDtos.map((tradeDto) => {
return this._tradeDtoConverter.convertFromDto(tradeDto);
});
}
public async getTrade(
userId: string,
accountId: number,
tradeId: string,
): Promise<ITradeChanges> {
const endpoint = `users/${userId}/accounts/${accountId}/trades/${tradeId}`;
const tradeChangesDto = await this._callEndpoint<ITradeChangesDto>(endpoint, 'GET', null, true);
return this._tradeChangesDtoConverter.convertFromDto(tradeChangesDto);
}
public async getBalance(userId: string, accountId: number): Promise<IAccountBalance> {
const endpoint = `users/${userId}/accounts/${accountId}/balance`;
const accountBalanceDto = await this._callEndpoint<IAccountBalanceDto>(endpoint, 'GET', null, true);
return this._accountBalanceDtoConverter.convertFromDto(accountBalanceDto);
}
private _setApiCredentials(publicKey: string, privateKey: string): void {
this._authenticationProvider = new AuthenticationProvider(publicKey, privateKey);
}
private async _callEndpoint<T>(
endPoint: string,
method: 'GET' | 'POST' | 'DELETE',
parameters: { [key: string]: any } | null,
signed: boolean
): Promise<T> {
const pathname = "/v1/" + endPoint;
let options: rp.OptionsWithUri & { headers: { [key: string]: any }} = {
uri: "https://dev-api.shrimpy.io" + pathname,
headers: {
'content-type': 'application/json',
},
method: method
};
if (method == 'POST') {
if (parameters) {
// only attach the body if there are parameters
// no parameters, send empty body
options.body = JSON.stringify(parameters);
} else {
options.body = "";
}
}
if (signed) {
// this method is signed, create the signature
if (!this._authenticationProvider) {
throw new Error(`Cannot send a request to ${endPoint} without api keys. Make sure to pass api keys to the ShrimpyApiClient constructor.`);
}
const nonce = Date.now();
const bodyString = options.body ? options.body : "";
const prehashString = pathname + method + nonce + bodyString;
const signature = this._authenticationProvider.sign(prehashString);
options.headers['DEV-SHRIMPY-API-KEY'] = this._authenticationProvider.publicKey;
options.headers['DEV-SHRIMPY-API-NONCE'] = nonce;
options.headers['DEV-SHRIMPY-API-SIGNATURE'] = signature;
}
const response: string = await rp(options);
return JSON.parse(response);
}
}