Skip to content

Commit 7ef765e

Browse files
authored
Merge pull request #2959 from DFXswiss/develop
2 parents 576cb55 + 938dbbe commit 7ef765e

59 files changed

Lines changed: 1472 additions & 155 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* @typedef {import('typeorm').MigrationInterface} MigrationInterface
3+
* @typedef {import('typeorm').QueryRunner} QueryRunner
4+
*/
5+
6+
/**
7+
* @class
8+
* @implements {MigrationInterface}
9+
*/
10+
module.exports = class AddRefAsset1768344518359 {
11+
name = 'AddRefAsset1768344518359'
12+
13+
/**
14+
* @param {QueryRunner} queryRunner
15+
*/
16+
async up(queryRunner) {
17+
await queryRunner.query(`ALTER TABLE "asset" ADD "refEnabled" bit NOT NULL CONSTRAINT "DF_d2c85e8cbdbff07a1dcd8d17797" DEFAULT 0`);
18+
await queryRunner.query(`ALTER TABLE "user" ADD "refAssetId" int`);
19+
await queryRunner.query(`ALTER TABLE "user" ADD CONSTRAINT "FK_20e823fee19baff0c5090ab72df" FOREIGN KEY ("refAssetId") REFERENCES "asset"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
20+
}
21+
22+
/**
23+
* @param {QueryRunner} queryRunner
24+
*/
25+
async down(queryRunner) {
26+
await queryRunner.query(`ALTER TABLE "user" DROP CONSTRAINT "FK_20e823fee19baff0c5090ab72df"`);
27+
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "refAssetId"`);
28+
await queryRunner.query(`ALTER TABLE "asset" DROP CONSTRAINT "DF_d2c85e8cbdbff07a1dcd8d17797"`);
29+
await queryRunner.query(`ALTER TABLE "asset" DROP COLUMN "refEnabled"`);
30+
}
31+
}

src/integration/exchange/dto/exchange-tx.dto.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ export class ExchangeTxDto {
55
exchange: ExchangeName;
66
type: ExchangeTxType;
77
externalId: string;
8-
externalCreated: Date;
9-
externalUpdated: Date;
8+
externalCreated?: Date;
9+
externalUpdated?: Date;
1010
status: string;
1111
amount: number;
1212
feeAmount: number;
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// --- TRANSACTION TYPES --- //
2+
3+
export enum ScryptTransactionType {
4+
WITHDRAWAL = 'Withdrawal',
5+
DEPOSIT = 'Deposit',
6+
}
7+
8+
export enum ScryptTransactionStatus {
9+
COMPLETED = 'Completed',
10+
FAILED = 'Failed',
11+
REJECTED = 'Rejected',
12+
}
13+
14+
export interface ScryptBalance {
15+
Currency: string;
16+
Amount: string;
17+
AvailableAmount: string;
18+
Equivalent?: {
19+
Currency: string;
20+
Amount: string;
21+
AvailableAmount: string;
22+
};
23+
}
24+
25+
export interface ScryptBalanceTransaction {
26+
TransactionID: string;
27+
ClReqID?: string;
28+
Currency: string;
29+
TransactionType: ScryptTransactionType;
30+
Status: ScryptTransactionStatus;
31+
Quantity: string;
32+
Fee?: string;
33+
TxHash?: string;
34+
RejectReason?: string;
35+
RejectText?: string;
36+
Timestamp?: string;
37+
TransactTime?: string;
38+
}
39+
40+
export interface ScryptWithdrawResponse {
41+
id: string;
42+
status: ScryptTransactionStatus;
43+
}
44+
45+
export interface ScryptWithdrawStatus {
46+
id: string;
47+
status: ScryptTransactionStatus;
48+
txHash?: string;
49+
amount?: number;
50+
rejectReason?: string;
51+
rejectText?: string;
52+
}
53+
54+
// --- TRADE TYPES --- //
55+
56+
export enum ScryptTradeSide {
57+
BUY = 'Buy',
58+
SELL = 'Sell',
59+
}
60+
61+
export enum ScryptTradeStatus {
62+
PENDING = 'Pending',
63+
CONFIRMED = 'Confirmed',
64+
CANCELED = 'Canceled',
65+
}
66+
67+
export interface ScryptTrade {
68+
Timestamp: string;
69+
Symbol: string;
70+
OrderID: string;
71+
TradeID: string;
72+
Side: ScryptTradeSide;
73+
TransactTime: string;
74+
ExecType: string;
75+
Currency: string;
76+
Price?: string;
77+
Quantity: string;
78+
Amount: string;
79+
Fee: string;
80+
FeeCurrency?: string;
81+
TradeStatus: ScryptTradeStatus;
82+
AmountCurrency: string;
83+
QuoteID?: string;
84+
RFQID?: string;
85+
CustomerUser?: string;
86+
AggressorSide?: ScryptTradeSide;
87+
DealtCurrency?: string;
88+
}
89+
90+
// --- ORDER TYPES --- //
91+
92+
export enum ScryptOrderStatus {
93+
NEW = 'New',
94+
PARTIALLY_FILLED = 'PartiallyFilled',
95+
FILLED = 'Filled',
96+
CANCELED = 'Canceled',
97+
PENDING_CANCEL = 'PendingCancel',
98+
REJECTED = 'Rejected',
99+
PENDING_NEW = 'PendingNew',
100+
PENDING_REPLACE = 'PendingReplace',
101+
}
102+
103+
export enum ScryptOrderSide {
104+
BUY = 'Buy',
105+
SELL = 'Sell',
106+
}
107+
108+
export enum ScryptOrderType {
109+
MARKET = 'Market',
110+
LIMIT = 'Limit',
111+
}
112+
113+
export enum ScryptTimeInForce {
114+
FILL_AND_KILL = 'FillAndKill',
115+
FILL_OR_KILL = 'FillOrKill',
116+
GOOD_TILL_CANCEL = 'GoodTillCancel',
117+
}
118+
119+
export interface ScryptExecutionReport {
120+
ClOrdID: string;
121+
OrigClOrdID?: string;
122+
OrderID?: string;
123+
Symbol: string;
124+
Side: string;
125+
OrdStatus: ScryptOrderStatus;
126+
ExecType?: string;
127+
OrderQty: string;
128+
CumQty: string;
129+
LeavesQty: string;
130+
AvgPx?: string;
131+
Price?: string;
132+
OrdRejReason?: string;
133+
CxlRejReason?: string;
134+
Text?: string;
135+
}
136+
137+
export interface ScryptOrderResponse {
138+
id: string;
139+
status: ScryptOrderStatus;
140+
}
141+
142+
export interface ScryptOrderInfo {
143+
id: string;
144+
orderId?: string;
145+
symbol: string;
146+
side: string;
147+
status: ScryptOrderStatus;
148+
quantity: number;
149+
filledQuantity: number;
150+
remainingQuantity: number;
151+
avgPrice?: number;
152+
price?: number;
153+
rejectReason?: string;
154+
}
155+
156+
// --- MARKET DATA TYPES --- //
157+
158+
export interface ScryptPriceLevel {
159+
Price: string;
160+
Size: string;
161+
}
162+
163+
export interface ScryptMarketDataSnapshot {
164+
Timestamp: string;
165+
Symbol: string;
166+
Status: string;
167+
Bids: ScryptPriceLevel[];
168+
Offers: ScryptPriceLevel[];
169+
}
170+
171+
export interface ScryptOrderBook {
172+
bids: Array<{ price: number; size: number }>;
173+
offers: Array<{ price: number; size: number }>;
174+
}
175+
176+
// --- SECURITY TYPES --- //
177+
178+
export interface ScryptSecurity {
179+
Symbol: string;
180+
BaseCurrency: string;
181+
QuoteCurrency: string;
182+
MinimumSize?: string;
183+
MaximumSize?: string;
184+
MinPriceIncrement?: string;
185+
MinSizeIncrement?: string;
186+
}

src/integration/exchange/entities/exchange-tx.entity.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,6 @@ export const ExchangeSyncs: ExchangeSync[] = [
137137
tokenReplacements: [],
138138
},
139139
{ exchange: ExchangeName.BINANCE, tradeTokens: ['BTC', 'USDT'], tokenReplacements: [['BTCB', 'BTC']] },
140+
{ exchange: ExchangeName.MEXC, tokens: ['ZCHF', 'XMR', 'USDT', 'ZANO', 'fUSD'], tokenReplacements: [] },
141+
{ exchange: ExchangeName.SCRYPT, tokens: [], tokenReplacements: [] },
140142
];

src/integration/exchange/mappers/exchange-tx.mapper.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
import { Trade, Transaction } from 'ccxt';
22
import { ExchangeTxDto } from '../dto/exchange-tx.dto';
3+
import {
4+
ScryptBalanceTransaction,
5+
ScryptTrade,
6+
ScryptTradeSide,
7+
ScryptTradeStatus,
8+
ScryptTransactionStatus,
9+
ScryptTransactionType,
10+
} from '../dto/scrypt.dto';
311
import { ExchangeTxType } from '../entities/exchange-tx.entity';
412
import { ExchangeName } from '../enums/exchange.enum';
513

614
export class ExchangeTxMapper {
15+
// --- CCXT TRANSACTIONS --- //
716
static mapDeposits(transactions: Transaction[], exchange: ExchangeName): ExchangeTxDto[] {
817
return transactions
918
.filter((d) => d.type === 'deposit')
@@ -70,4 +79,76 @@ export class ExchangeTxMapper {
7079
side: t.side,
7180
}));
7281
}
82+
83+
// --- SCRYPT TRANSACTIONS --- //
84+
static mapScryptTransactions(transactions: ScryptBalanceTransaction[], exchange: ExchangeName): ExchangeTxDto[] {
85+
return transactions.map((t) => ({
86+
exchange,
87+
type: this.mapScryptTransactionType(t.TransactionType),
88+
externalId: t.TransactionID,
89+
externalCreated: t.TransactTime ? new Date(t.TransactTime) : null,
90+
externalUpdated: t.Timestamp ? new Date(t.Timestamp) : null,
91+
status: this.mapScryptStatus(t.Status),
92+
amount: parseFloat(t.Quantity) || 0,
93+
feeAmount: t.Fee ? parseFloat(t.Fee) : 0,
94+
feeCurrency: t.Currency,
95+
currency: t.Currency,
96+
txId: t.TxHash,
97+
}));
98+
}
99+
100+
private static mapScryptTransactionType(type: ScryptTransactionType): ExchangeTxType {
101+
switch (type) {
102+
case ScryptTransactionType.DEPOSIT:
103+
return ExchangeTxType.DEPOSIT;
104+
case ScryptTransactionType.WITHDRAWAL:
105+
return ExchangeTxType.WITHDRAWAL;
106+
default:
107+
throw new Error(`Unknown Scrypt transaction type: ${type}`);
108+
}
109+
}
110+
111+
private static mapScryptStatus(status: ScryptTransactionStatus): string {
112+
switch (status) {
113+
case ScryptTransactionStatus.COMPLETED:
114+
return 'ok';
115+
case ScryptTransactionStatus.FAILED:
116+
case ScryptTransactionStatus.REJECTED:
117+
return 'failed';
118+
default:
119+
return 'pending';
120+
}
121+
}
122+
123+
// --- SCRYPT TRADES --- //
124+
static mapScryptTrades(trades: ScryptTrade[], exchange: ExchangeName): ExchangeTxDto[] {
125+
return trades.map((t) => ({
126+
exchange,
127+
type: ExchangeTxType.TRADE,
128+
externalId: t.TradeID,
129+
externalCreated: new Date(t.TransactTime),
130+
externalUpdated: new Date(t.Timestamp),
131+
status: this.mapScryptTradeStatus(t.TradeStatus),
132+
amount: parseFloat(t.Quantity) || 0,
133+
feeAmount: parseFloat(t.Fee) || 0,
134+
feeCurrency: t.FeeCurrency ?? t.Currency,
135+
symbol: t.Symbol.replace('-', '/'),
136+
side: t.Side === ScryptTradeSide.BUY ? 'buy' : 'sell',
137+
price: t.Price ? parseFloat(t.Price) : undefined,
138+
cost: parseFloat(t.Amount) || 0,
139+
order: t.OrderID,
140+
}));
141+
}
142+
143+
private static mapScryptTradeStatus(status: ScryptTradeStatus): string {
144+
switch (status) {
145+
case ScryptTradeStatus.CONFIRMED:
146+
return 'ok';
147+
case ScryptTradeStatus.CANCELED:
148+
return 'canceled';
149+
case ScryptTradeStatus.PENDING:
150+
default:
151+
return 'pending';
152+
}
153+
}
73154
}
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1-
import { Injectable } from '@nestjs/common';
1+
import { Inject, Injectable } from '@nestjs/common';
22
import { StrategyRegistry } from 'src/subdomains/supporting/common/strategy-registry';
3+
import { ExchangeName } from '../enums/exchange.enum';
34
import { ExchangeService } from './exchange.service';
5+
import { ScryptService } from './scrypt.service';
46

57
@Injectable()
68
export class ExchangeRegistryService extends StrategyRegistry<string, ExchangeService> {
9+
@Inject() private readonly scryptService: ScryptService;
10+
711
protected getKey(key: string): string {
812
return key.toLowerCase();
913
}
14+
15+
getExchange(exchange: string): ExchangeService | ScryptService {
16+
return exchange === ExchangeName.SCRYPT ? this.scryptService : this.get(exchange);
17+
}
1018
}

src/integration/exchange/services/exchange-tx.service.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { ExchangeName } from '../enums/exchange.enum';
1919
import { ExchangeTxMapper } from '../mappers/exchange-tx.mapper';
2020
import { ExchangeTxRepository } from '../repositories/exchange-tx.repository';
2121
import { ExchangeRegistryService } from './exchange-registry.service';
22+
import { ScryptService } from './scrypt.service';
2223

2324
@Injectable()
2425
export class ExchangeTxService {
@@ -121,7 +122,20 @@ export class ExchangeTxService {
121122

122123
private async getTransactionsFor(sync: ExchangeSync, since: Date): Promise<ExchangeTxDto[]> {
123124
try {
124-
const exchangeService = this.registryService.get(sync.exchange);
125+
const exchangeService = this.registryService.getExchange(sync.exchange);
126+
127+
// Scrypt special case
128+
if (exchangeService instanceof ScryptService) {
129+
const [transactions, trades] = await Promise.all([
130+
exchangeService.getAllTransactions(since),
131+
exchangeService.getTrades(since),
132+
]);
133+
134+
return [
135+
...ExchangeTxMapper.mapScryptTransactions(transactions, sync.exchange),
136+
...ExchangeTxMapper.mapScryptTrades(trades, sync.exchange),
137+
];
138+
}
125139

126140
const tokens = sync.tokens ?? (await this.assetService.getAssetsUsedOn(sync.exchange));
127141

src/integration/exchange/services/mexc.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export class MexcService extends ExchangeService {
128128
? 'ok'
129129
: 'pending',
130130
updated: undefined,
131-
fee: undefined,
131+
fee: d.transactionFee ? { cost: parseFloat(d.transactionFee), currency: d.coin.split('-')[0] } : undefined,
132132
network: d.network,
133133
comment: d.memo,
134134
internal: undefined,

0 commit comments

Comments
 (0)