Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added v2 criptointercambio api #182

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 178 additions & 0 deletions src/partners/criptointercambio.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { asArray, asNumber, asObject, asString } from 'cleaners'

import {
CicTransaction,
CriptointercambioClient,
PartialCicTransaction
} from '../../util/cic-sdk'
import { PartnerPlugin, PluginParams, PluginResult, StandardTx } from '../types'
import { datelog } from '../util'

const asCriptointercambioTx = asObject<PartialCicTransaction>({
id: asString,
payinHash: asString,
payoutHash: asString,
payinAddress: asString,
currencyFrom: asString,
amountFrom: asString,
payoutAddress: asString,
currencyTo: asString,
amountTo: asString,
createdAt: asNumber
})

const asCriptointercambioRawTx = asObject({
status: asString
})

const asCriptointercambioResult = asArray(asCriptointercambioTx)

const MAX_ATTEMPTS = 3
const LIMIT = 300
const QUERY_LOOKBACK = 60 * 60 * 24 * 5 // 5 days

async function getTransactionsPromised(
criptointercambioSDK: CriptointercambioClient,
limit: number,
offset: number,
currencyFrom: string | undefined,
address: string | undefined,
extraId: string | undefined
): Promise<CicTransaction[]> {
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
try {
return await criptointercambioSDK.getTransactions(
limit,
offset,
currencyFrom,
address,
extraId
)
} catch (e) {
if (attempt <= MAX_ATTEMPTS) {
datelog(
`Criptointercambio request have failed. Retry attempt: ${attempt} out of ${MAX_ATTEMPTS}`
)
} else {
throw new Error(
'Unable to fetch transactions data from Criptointercambio'
)
}
}
}
// To avoid undefined casting of return result. We always expect to get into the "else" branch at the end
throw new Error('Unable to fetch transactions data from Criptointercambio')
}

export async function queryCriptointercambio(
pluginParams: PluginParams
): Promise<PluginResult> {
let criptointercambioSDK: CriptointercambioClient
let latestTimeStamp = 0
let offset = 0
let firstAttempt = false
if (typeof pluginParams.settings.latestTimeStamp === 'number') {
latestTimeStamp = pluginParams.settings.latestTimeStamp
}
if (
typeof pluginParams.settings.firstAttempt === 'undefined' ||
pluginParams.settings.firstAttempt === true
) {
firstAttempt = true
}
if (
typeof pluginParams.settings.offset === 'number' &&
firstAttempt === true
) {
offset = pluginParams.settings.offset
}
if (
typeof pluginParams.apiKeys.criptointercambioApiKey === 'string' &&
typeof pluginParams.apiKeys.criptointercambioApiSecret === 'string'
) {
criptointercambioSDK = new CriptointercambioClient(
pluginParams.apiKeys.criptointercambioApiKey,
pluginParams.apiKeys.criptointercambioApiSecret
)
} else {
return {
settings: {
latestTimeStamp: latestTimeStamp
},
transactions: []
}
}

const ssFormatTxs: StandardTx[] = []
let newLatestTimeStamp = latestTimeStamp
let done = false
try {
while (!done) {
datelog(`Query criptointercambio offset: ${offset}`)
const result = await getTransactionsPromised(
criptointercambioSDK,
LIMIT,
offset,
undefined,
undefined,
undefined
)
const txs = asCriptointercambioResult(result)
if (txs.length === 0) {
datelog(`Criptointercambio done at offset ${offset}`)
firstAttempt = false
break
}
for (const rawTx of txs) {
if (asCriptointercambioRawTx(rawTx).status === 'finished') {
const tx = asCriptointercambioTx(rawTx)
const ssTx: StandardTx = {
status: 'complete',
orderId: tx.id,
depositTxid: tx.payinHash,
depositAddress: tx.payinAddress,
depositCurrency: tx.currencyFrom.toUpperCase(),
depositAmount: parseFloat(tx.amountFrom),
payoutTxid: tx.payoutHash,
payoutAddress: tx.payoutAddress,
payoutCurrency: tx.currencyTo.toUpperCase(),
payoutAmount: parseFloat(tx.amountTo),
timestamp: tx.createdAt,
isoDate: new Date(tx.createdAt * 1000).toISOString(),
usdValue: 0,
rawTx
}
ssFormatTxs.push(ssTx)
if (tx.createdAt > newLatestTimeStamp) {
newLatestTimeStamp = tx.createdAt
}
if (
tx.createdAt < latestTimeStamp - QUERY_LOOKBACK &&
!done &&
!firstAttempt
) {
datelog(
`Criptointercambio done: date ${
tx.createdAt
} < ${latestTimeStamp - QUERY_LOOKBACK}`
)
done = true
}
}
}
offset += LIMIT
}
} catch (e) {
datelog(e)
}
return {
settings: { latestTimeStamp: newLatestTimeStamp, firstAttempt, offset },
transactions: ssFormatTxs
}
}

export const criptointercambio: PartnerPlugin = {
queryFunc: queryCriptointercambio,
pluginName: 'Criptointercambio',
pluginId: 'criptointercambio'
}
2 changes: 2 additions & 0 deletions src/queryEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { bity } from './partners/bity'
import { changehero } from './partners/changehero'
import { changelly } from './partners/changelly'
import { changenow } from './partners/changenow'
import { criptointercambio } from './partners/criptointercambio'
import { exolix } from './partners/exolix'
import { foxExchange } from './partners/foxExchange'
import { gebo } from './partners/gebo'
Expand Down Expand Up @@ -124,6 +125,7 @@ const partners = [
changelly,
changenow,
changehero,
criptointercambio,
exolix,
foxExchange,
gebo,
Expand Down
115 changes: 115 additions & 0 deletions util/cic-sdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import axios, { AxiosInstance, AxiosResponse } from 'axios'
import crypto from 'crypto'

interface Result<T> {
result: T
}

interface Error {
error: {
code: number
message: string
}
}

export interface PartialCicTransaction {
id: string
payinHash: string
payoutHash: string
payinAddress: string
currencyFrom: string
amountFrom: string
payoutAddress: string
currencyTo: string
amountTo: string
createdAt: number
}

export interface CicTransaction extends PartialCicTransaction {
trackUrl: string
type: 'fixed' | 'float'
moneyReceived: number
moneySent: number
rate: string
payinConfirmations: string
status: string
payinExtraId?: string
payinExtraIdName?: string
payoutHashLink: string
refundHashLink?: string
amountExpectedFrom: string
payoutExtraId?: string
payoutExtraIdName?: string
refundHash?: string
refundAddress: string
refundExtraId?: string
amountExpectedTo: string
networkFee: string
apiExtraFee: string
totalFee: string
canPush: boolean
canRefund: boolean
}
export class CriptointercambioClient {
private readonly client: AxiosInstance = axios.create({
baseURL: 'https://api.criptointercambio.com/v2',
timeout: 20000
})

constructor(
private readonly apiKey: string,
private readonly secret: string
) {}

getSigningHeaders(body: Record<string, any>): Record<string, string> {
const signature = crypto.sign('sha256', Buffer.from(JSON.stringify(body)), {
key: this.secret,
type: 'pkcs8',
format: 'der'
})
return {
'X-Api-Key': this.apiKey,
'X-Api-Signature': signature.toString('base64')
}
}

private async request<T>(
method: string,
params: Record<string, any>
): Promise<AxiosResponse<T>> {
const body = {
jsonrpc: '2.0',
id: 'cic-transactions',
method,
params
}
const headers = this.getSigningHeaders(body)
return this.client.post<T>('/', body, { headers })
}

async getTransactions(
limit: number,
offset: number,
currency?: string,
address?: string,
extraId?: string
): Promise<CicTransaction[]> {
const result = await this.request<Error | Result<CicTransaction[]>>(
'getTransactions',
{
currency,
address,
extraId,
offset,
limit
}
)
if ('error' in result.data) {
throw new Error(
`[${result.data.error.code}]: Criptointercambio error: ${result.data.error.message}`
)
}

return result.data.result
}
}