Skip to content

Commit 0c72ad3

Browse files
committed
Merge branch 'master' of https://github.com/ExchangeUnion/xud into feature/buy-sell-all-grpc
2 parents 8155d93 + bd0210e commit 0c72ad3

File tree

8 files changed

+400
-204
lines changed

8 files changed

+400
-204
lines changed

lib/db/DB.ts

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { promises as fs } from 'fs';
21
import { derivePairId } from '../utils/utils';
32
import { ModelCtor, Sequelize } from 'sequelize';
43
import { XuNetwork } from '../constants/enums';
@@ -137,8 +136,6 @@ class DB {
137136
* @param initDb whether to intialize a new database with default values if no database exists
138137
*/
139138
public init = async (network = XuNetwork.SimNet, initDb = false): Promise<void> => {
140-
const shouldInitDb = initDb && await this.isNewDb();
141-
142139
try {
143140
await this.sequelize.authenticate();
144141
this.logger.info(`connected to database ${this.storage ? this.storage : 'in memory'}`);
@@ -177,41 +174,29 @@ class DB {
177174
await Node.bulkCreate(newNodes);
178175
}
179176
}
180-
}
181-
182-
if (shouldInitDb) {
183177
// initialize database with the default currencies for the configured network
184178
const currencies = defaultCurrencies(network);
185179
if (currencies) {
186-
await Currency.bulkCreate(currencies);
180+
const existingCurrencies = await Models.Currency(this.sequelize).findAll();
181+
const newCurrencies = currencies.filter(currency => (!existingCurrencies.find(n => (n.id === currency.id))));
182+
183+
if (newCurrencies.length > 0) {
184+
await Currency.bulkCreate(newCurrencies);
185+
}
187186
}
188187

189188
// initialize database with the default trading pairs for the configured network
190189
const pairs = defaultPairs(network);
191190
if (pairs) {
192-
await Pair.bulkCreate(pairs);
193-
}
194-
}
195-
}
191+
const existingPairs = await Models.Pair(this.sequelize).findAll();
192+
const newPairs = pairs.filter(pair => (!existingPairs.find(n => (n.baseCurrency === pair.baseCurrency &&
193+
n.quoteCurrency === pair.quoteCurrency))));
196194

197-
/**
198-
* Checks whether the database is new, in other words whether we are not
199-
* loading a preexisting database from disk.
200-
*/
201-
private isNewDb = async () => {
202-
if (this.storage && this.storage !== ':memory:') {
203-
// check if database file exists
204-
try {
205-
await fs.access(this.storage);
206-
return false;
207-
} catch (err) {
208-
if (err.code !== 'ENOENT') {
209-
// we ignore errors due to file not existing, otherwise throw
210-
throw err;
195+
if (newPairs.length > 0) {
196+
await Pair.bulkCreate(newPairs);
211197
}
212198
}
213199
}
214-
return true;
215200
}
216201

217202
public close = () => {

lib/lndclient/LndClient.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import assert from 'assert';
2+
import crypto from 'crypto';
23
import { promises as fs, watch } from 'fs';
34
import grpc, { ChannelCredentials, ClientReadableStream } from 'grpc';
45
import path from 'path';
@@ -242,6 +243,10 @@ class LndClient extends SwapClient {
242243

243244
private unaryCall = <T, U>(methodName: Exclude<keyof LightningClient, ClientMethods>, params: T): Promise<U> => {
244245
return new Promise((resolve, reject) => {
246+
if (this.hasNoInvoiceSupport()) {
247+
reject(errors.NO_HOLD_INVOICE_SUPPORT);
248+
return;
249+
}
245250
if (!this.isOperational()) {
246251
reject(errors.DISABLED);
247252
return;
@@ -337,7 +342,9 @@ class LndClient extends SwapClient {
337342
let version: string | undefined;
338343
let alias: string | undefined;
339344
let status = 'Ready';
340-
if (!this.isOperational()) {
345+
if (this.hasNoInvoiceSupport()) {
346+
status = errors.NO_HOLD_INVOICE_SUPPORT(this.currency).message;
347+
} else if (!this.isOperational()) {
341348
status = errors.DISABLED(this.currency).message;
342349
} else if (this.isDisconnected()) {
343350
status = errors.UNAVAILABLE(this.currency, this.status).message;
@@ -493,6 +500,18 @@ class LndClient extends SwapClient {
493500
}
494501

495502
this.invoices = new InvoicesClient(this.uri, this.credentials);
503+
try {
504+
const randomHash = crypto.randomBytes(32).toString('hex');
505+
this.logger.debug(`checking hold invoice support with hash: ${randomHash}`);
506+
507+
await this.addInvoice({ rHash: randomHash, units: 1 });
508+
await this.removeInvoice(randomHash);
509+
} catch (err) {
510+
const errStr = typeof(err) === 'string' ? err : JSON.stringify(err);
511+
512+
this.logger.error(`could not add hold invoice, error: ${errStr}`);
513+
this.setStatus(ClientStatus.NoHoldInvoiceSupport);
514+
}
496515

497516
if (this.walletUnlocker) {
498517
// WalletUnlocker service is disabled when the main Lightning service is available

lib/lndclient/errors.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const errorCodes = {
66
DISABLED: codesPrefix.concat('.1'),
77
UNAVAILABLE: codesPrefix.concat('.2'),
88
NO_ACTIVE_CHANNELS: codesPrefix.concat('.3'),
9+
NO_HOLD_INVOICE_SUPPORT: codesPrefix.concat('.4'),
910
};
1011

1112
const errors = {
@@ -21,6 +22,10 @@ const errors = {
2122
message: `lnd-${currency} has no active channels`,
2223
code: errorCodes.NO_ACTIVE_CHANNELS,
2324
}),
25+
NO_HOLD_INVOICE_SUPPORT: (currency: string) => ({
26+
message: `lnd-${currency} has no hold invoice support`,
27+
code: errorCodes.NO_HOLD_INVOICE_SUPPORT,
28+
}),
2429
};
2530

2631
export { errorCodes };

lib/service/Service.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -626,8 +626,10 @@ class Service {
626626
const currency = pairId.split('/')[0];
627627
calculatedQuantity = (await this.getBalance({ currency })).get(currency)?.channelBalance || 0;
628628
} else {
629-
// TODO
630-
calculatedQuantity = 0;
629+
const currency = pairId.split('/')[1];
630+
631+
const balance = (await this.getBalance({ currency })).get(currency)?.channelBalance || 0;
632+
calculatedQuantity = this.calculateBuyMaxMarketQuantity(pairId, price, balance);
631633
}
632634

633635
this.logger.debug(`max flag is true to place order, calculated quantity from balance is ${calculatedQuantity}`);
@@ -664,6 +666,34 @@ class Service {
664666
await this.orderBook.placeMarketOrder(placeOrderRequest);
665667
}
666668

669+
private calculateBuyMaxMarketQuantity(pairId: string, price: number, balance: number) {
670+
let result = 0;
671+
let currentBalance = balance;
672+
673+
this.listOrders({ pairId, owner: Owner.Both, limit: 0, includeAliases: false }).forEach((orderArrays, _) => {
674+
for (const order of orderArrays.sellArray) {
675+
if (order.quantity && order.price) {
676+
if (!price) {
677+
// market buy max calculation
678+
const maxBuyableFromThisPrice = currentBalance / order.price;
679+
const calculatedQuantity = (maxBuyableFromThisPrice > order.quantity) ? order.quantity : maxBuyableFromThisPrice;
680+
result += calculatedQuantity;
681+
currentBalance -= order.price * calculatedQuantity;
682+
683+
if (currentBalance === 0) {
684+
// we filled our buy quantity with this order
685+
break;
686+
}
687+
} else {
688+
// TODO
689+
}
690+
}
691+
}
692+
});
693+
694+
return result;
695+
}
696+
667697
/** Removes a currency. */
668698
public removeCurrency = async (args: { currency: string }) => {
669699
argChecks.VALID_CURRENCY(args);

lib/swaps/SwapClient.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ enum ClientStatus {
2323
Unlocked,
2424
/** The client could not be initialized due to faulty configuration. */
2525
Misconfigured,
26+
/** The server is reachable but hold invoices are not supported. */
27+
NoHoldInvoiceSupport,
2628
}
2729

2830
type ChannelBalance = {
@@ -216,6 +218,7 @@ abstract class SwapClient extends EventEmitter {
216218
case ClientStatus.Disconnected:
217219
case ClientStatus.WaitingUnlock:
218220
case ClientStatus.OutOfSync:
221+
case ClientStatus.NoHoldInvoiceSupport:
219222
// these statuses can only be set on an operational, initalized client
220223
validStatusTransition = this.isOperational();
221224
break;
@@ -359,7 +362,7 @@ abstract class SwapClient extends EventEmitter {
359362
* Returns `true` if the client is enabled and configured properly.
360363
*/
361364
public isOperational(): boolean {
362-
return !this.isDisabled() && !this.isMisconfigured() && !this.isNotInitialized();
365+
return !this.isDisabled() && !this.isMisconfigured() && !this.isNotInitialized() && !this.hasNoInvoiceSupport();
363366
}
364367
public isDisconnected(): boolean {
365368
return this.status === ClientStatus.Disconnected;
@@ -373,6 +376,9 @@ abstract class SwapClient extends EventEmitter {
373376
public isOutOfSync(): boolean {
374377
return this.status === ClientStatus.OutOfSync;
375378
}
379+
public hasNoInvoiceSupport(): boolean {
380+
return this.status === ClientStatus.NoHoldInvoiceSupport;
381+
}
376382

377383
/** Ends all connections, subscriptions, and timers for for this client. */
378384
public close() {

test/integration/Service.spec.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import chai, { expect } from 'chai';
22
import chaiAsPromised from 'chai-as-promised';
3+
import sinon from 'sinon';
34
import { OrderSide, Owner, SwapClientType } from '../../lib/constants/enums';
45
import p2pErrors from '../../lib/p2p/errors';
56
import Service from '../../lib/service/Service';
67
import Xud from '../../lib/Xud';
78
import { getTempDir } from '../utils';
9+
import { ServiceOrderSidesArrays } from '../../lib/service/types';
810

911
chai.use(chaiAsPromised);
1012

@@ -186,4 +188,63 @@ describe('API Service', () => {
186188
});
187189
await expect(shutdownPromise).to.be.fulfilled;
188190
});
191+
192+
describe('Max Quantity Calculation', () => {
193+
before(async () => {
194+
const map = new Map<string, ServiceOrderSidesArrays>();
195+
map.set('BTC/DAI', {
196+
buyArray: [],
197+
sellArray: [
198+
{ quantity: 0.01, price: 20000, pairId: 'BTC/DAI', id: 'test_1', createdAt: 1, side: OrderSide.Sell,
199+
isOwnOrder: false, nodeIdentifier: { nodePubKey: 'some_key' } },
200+
{ quantity: 0.01, price: 50000, pairId: 'BTC/DAI', id: 'test_2', createdAt: 1, side: OrderSide.Sell,
201+
isOwnOrder: false, nodeIdentifier: { nodePubKey: 'some_key2' } },
202+
{ quantity: 0.05, price: 100000, pairId: 'BTC/DAI', id: 'test_2', createdAt: 1, side: OrderSide.Sell,
203+
isOwnOrder: false, nodeIdentifier: { nodePubKey: 'some_key2' } },
204+
],
205+
});
206+
207+
sinon.createSandbox().stub(service, 'listOrders').returns(map);
208+
});
209+
210+
it('should return `0` for 0 balance mkt', async () => {
211+
const number = service['calculateBuyMaxMarketQuantity']('BTC/DAI', 0, 0);
212+
await expect(number).to.equal(0);
213+
});
214+
215+
it('should return `0.005` for 100 balance mkt', async () => {
216+
const number = service['calculateBuyMaxMarketQuantity']('BTC/DAI', 0, 100);
217+
await expect(number).to.equal(0.005);
218+
});
219+
220+
it('should return `0.01` for 200 balance mkt', async () => {
221+
const number = service['calculateBuyMaxMarketQuantity']('BTC/DAI', 0, 200);
222+
await expect(number).to.equal(0.01);
223+
});
224+
225+
it('should return `0.016` for 500 balance mkt', async () => {
226+
const number = service['calculateBuyMaxMarketQuantity']('BTC/DAI', 0, 500);
227+
await expect(number).to.equal(0.016);
228+
});
229+
230+
it('should return `0.02` for 700 balance mkt', async () => {
231+
const number = service['calculateBuyMaxMarketQuantity']('BTC/DAI', 0, 700);
232+
await expect(number).to.equal(0.02);
233+
});
234+
235+
it('should return `0.021` for 800 balance mkt', async () => {
236+
const number = service['calculateBuyMaxMarketQuantity']('BTC/DAI', 0, 800);
237+
await expect(number).to.equal(0.021);
238+
});
239+
240+
it('should return `0.07` for 5700 balance mkt', async () => {
241+
const number = service['calculateBuyMaxMarketQuantity']('BTC/DAI', 0, 5700);
242+
await expect(number).to.equal(0.07);
243+
});
244+
245+
it('should return `0.07` for 10000 balance mkt', async () => {
246+
const number = service['calculateBuyMaxMarketQuantity']('BTC/DAI', 0, 10000);
247+
await expect(number).to.equal(0.07);
248+
});
249+
});
189250
});

0 commit comments

Comments
 (0)