update payment methods

This commit is contained in:
woodser 2022-05-26 10:58:15 -04:00
parent 6fa1db708f
commit 1f5ee145ad
2 changed files with 92 additions and 48 deletions

View File

@ -30,7 +30,7 @@ import console from "console"; // import console because jest swallows messages
// ------------------------------ TEST CONFIG --------------------------------- // ------------------------------ TEST CONFIG ---------------------------------
const TestConfig = { const TestConfig = {
logLevel: 0, logLevel: 1,
moneroBinsDir: "../haveno/.localnet", moneroBinsDir: "../haveno/.localnet",
testDataDir: "./testdata", testDataDir: "./testdata",
networkType: monerojs.MoneroNetworkType.STAGENET, networkType: monerojs.MoneroNetworkType.STAGENET,
@ -308,7 +308,7 @@ test("Can manage an account", async () => {
assert(size > 0); assert(size > 0);
// delete account which shuts down server // delete account which shuts down server
await charlie.deleteAccount(); await charlie.deleteAccount(); // TODO: support deleting and restoring account without shutting down server, #310
assert(!await charlie.isConnectedToDaemon()); assert(!await charlie.isConnectedToDaemon());
await releaseHavenoProcess(charlie); await releaseHavenoProcess(charlie);
@ -769,20 +769,19 @@ test("Can get market depth", async () => {
expect(marketDepth.getSellPricesList().length).toEqual(marketDepth.getSellDepthList().length); expect(marketDepth.getSellPricesList().length).toEqual(marketDepth.getSellDepthList().length);
// test buy prices and depths // test buy prices and depths
const priceDivisor = 100000000; // TODO: offer price = price * 100000000 const buyOffers = (await alice.getOffers(assetCode, "buy")).concat(await alice.getMyOffers(assetCode, "buy")).sort(function(a, b) { return parseFloat(a.getPrice()) - parseFloat(b.getPrice()) });
const buyOffers = (await alice.getOffers(assetCode, "buy")).concat(await alice.getMyOffers(assetCode, "buy")).sort(function(a, b) { return a.getPrice() - b.getPrice() }); expect(marketDepth.getBuyPricesList()[0]).toEqual(1 / parseFloat(buyOffers[0].getPrice())); // TODO: price when posting offer is reversed. this assumes crypto counter currency
expect(marketDepth.getBuyPricesList()[0]).toEqual(1 / (buyOffers[0].getPrice() / priceDivisor)); // TODO: price when posting offer is reversed. this assumes crypto counter currency expect(marketDepth.getBuyPricesList()[1]).toEqual(1 / parseFloat(buyOffers[1].getPrice()));
expect(marketDepth.getBuyPricesList()[1]).toEqual(1 / (buyOffers[1].getPrice() / priceDivisor)); expect(marketDepth.getBuyPricesList()[2]).toEqual(1 / parseFloat(buyOffers[2].getPrice()));
expect(marketDepth.getBuyPricesList()[2]).toEqual(1 / (buyOffers[2].getPrice() / priceDivisor));
expect(marketDepth.getBuyDepthList()[0]).toEqual(0.15); expect(marketDepth.getBuyDepthList()[0]).toEqual(0.15);
expect(marketDepth.getBuyDepthList()[1]).toEqual(0.30); expect(marketDepth.getBuyDepthList()[1]).toEqual(0.30);
expect(marketDepth.getBuyDepthList()[2]).toEqual(0.65); expect(marketDepth.getBuyDepthList()[2]).toEqual(0.65);
// test sell prices and depths // test sell prices and depths
const sellOffers = (await alice.getOffers(assetCode, "sell")).concat(await alice.getMyOffers(assetCode, "sell")).sort(function(a, b) { return b.getPrice() - a.getPrice() }); const sellOffers = (await alice.getOffers(assetCode, "sell")).concat(await alice.getMyOffers(assetCode, "sell")).sort(function(a, b) { return parseFloat(b.getPrice()) - parseFloat(a.getPrice()) });
expect(marketDepth.getSellPricesList()[0]).toEqual(1 / (sellOffers[0].getPrice() / priceDivisor)); expect(marketDepth.getSellPricesList()[0]).toEqual(1 / parseFloat(sellOffers[0].getPrice()));
expect(marketDepth.getSellPricesList()[1]).toEqual(1 / (sellOffers[1].getPrice() / priceDivisor)); expect(marketDepth.getSellPricesList()[1]).toEqual(1 / parseFloat(sellOffers[1].getPrice()));
expect(marketDepth.getSellPricesList()[2]).toEqual(1 / (sellOffers[2].getPrice() / priceDivisor)); expect(marketDepth.getSellPricesList()[2]).toEqual(1 / parseFloat(sellOffers[2].getPrice()));
expect(marketDepth.getSellDepthList()[0]).toEqual(0.3); expect(marketDepth.getSellDepthList()[0]).toEqual(0.3);
expect(marketDepth.getSellDepthList()[1]).toEqual(0.6); expect(marketDepth.getSellDepthList()[1]).toEqual(0.6);
expect(marketDepth.getSellDepthList()[2]).toEqual(1); expect(marketDepth.getSellDepthList()[2]).toEqual(1);
@ -860,6 +859,8 @@ test("Can create fiat payment accounts", async () => {
const accountForm = await alice.getPaymentAccountForm(paymentMethodId); const accountForm = await alice.getPaymentAccountForm(paymentMethodId);
// edit form // edit form
accountForm.tradeCurrencies ="gbp,eur,usd";
accountForm.selectedTradeCurrency = "usd";
accountForm.accountName = "Revolut account " + GenUtils.getUUID(); accountForm.accountName = "Revolut account " + GenUtils.getUUID();
accountForm.userName = "user123"; accountForm.userName = "user123";
@ -927,28 +928,50 @@ test("Can create crypto payment accounts", async () => {
} }
}); });
test("Can prepare for trading", async () => {
// create payment accounts
if (!await hasPaymentAccount(alice, "eth")) await createPaymentAccount(alice, "eth");
if (!await hasPaymentAccount(alice, "bch")) await createPaymentAccount(alice, "bch");
if (!await hasPaymentAccount(alice, "usd")) await createPaymentAccount(alice, "usd");
if (!await hasPaymentAccount(bob, "eth")) await createPaymentAccount(bob, "eth");
if (!await hasPaymentAccount(bob, "bch")) await createPaymentAccount(bob, "bch");
if (!await hasPaymentAccount(bob, "usd")) await createPaymentAccount(bob, "usd");
// fund wallets
const tradeAmount = BigInt("250000000000");
await fundOutputs([aliceWallet, bobWallet], tradeAmount * BigInt("6"), 4);
// wait for havenod to observe funds
await wait(TestConfig.walletSyncPeriodMs);
});
test("Can post and remove offers", async () => { test("Can post and remove offers", async () => {
// wait for alice to have at least 5 outputs of 0.5 XMR // wait for alice to have unlocked balance to post offer
await fundOutputs([aliceWallet], BigInt("500000000000"), 5); await waitForUnlockedBalance(BigInt("250000000000") * BigInt("2"), alice);
// get unlocked balance before reserving funds for offer // get unlocked balance before reserving funds for offer
const unlockedBalanceBefore = BigInt((await alice.getBalances()).getUnlockedBalance()); const unlockedBalanceBefore = BigInt((await alice.getBalances()).getUnlockedBalance());
// post crypto offer // post crypto offer
let assetCode = "ETH"; let assetCode = "BCH";
let price = 1 / 17; let price = 1 / 17;
price = 1 / price; // TODO: price in crypto offer is inverted price = 1 / price; // TODO: price in crypto offer is inverted
let offer: OfferInfo = await postOffer(alice, {assetCode: assetCode, price: price}); let offer: OfferInfo = await postOffer(alice, {assetCode: assetCode, price: price});
assert.equal(offer.getState(), "AVAILABLE"); assert.equal(offer.getState(), "AVAILABLE");
assert.equal(offer.getBaseCurrencyCode(), assetCode); // TODO: base and counter currencies inverted in crypto offer assert.equal(offer.getBaseCurrencyCode(), assetCode); // TODO: base and counter currencies inverted in crypto offer
assert.equal(offer.getCounterCurrencyCode(), "XMR"); assert.equal(offer.getCounterCurrencyCode(), "XMR");
assert.equal(offer.getPrice(), price * 100000000); // TODO: price when posting crypto offer is inverted and * 100000000. assert.equal(parseFloat(offer.getPrice()), price);
// has offer // has offer
offer = await alice.getMyOffer(offer.getId()); offer = await alice.getMyOffer(offer.getId());
assert.equal(offer.getState(), "AVAILABLE"); assert.equal(offer.getState(), "AVAILABLE");
// peer sees offer
await wait(TestConfig.maxTimePeerNoticeMs);
if (!getOffer(await bob.getOffers(assetCode, TestConfig.postOffer.direction), offer.getId())) throw new Error("Offer " + offer.getId() + " was not found in peer's offers after posted");
// cancel offer // cancel offer
await alice.removeOffer(offer.getId()); await alice.removeOffer(offer.getId());
@ -965,7 +988,7 @@ test("Can post and remove offers", async () => {
assert.equal(offer.getState(), "AVAILABLE"); assert.equal(offer.getState(), "AVAILABLE");
assert.equal(offer.getBaseCurrencyCode(), "XMR"); assert.equal(offer.getBaseCurrencyCode(), "XMR");
assert.equal(offer.getCounterCurrencyCode(), "USD"); assert.equal(offer.getCounterCurrencyCode(), "USD");
assert.equal(offer.getPrice(), price * 10000); // TODO: price = price * 10000 assert.equal(parseFloat(offer.getPrice()), price);
// has offer // has offer
offer = await alice.getMyOffer(offer.getId()); offer = await alice.getMyOffer(offer.getId());
@ -997,7 +1020,7 @@ test("Can schedule offers with locked funds", async () => {
await fundOutputs([charlieWallet], outputAmt, 2, false); await fundOutputs([charlieWallet], outputAmt, 2, false);
// schedule offer // schedule offer
const assetCode = "ETH"; const assetCode = "BCH";
const direction = "BUY"; const direction = "BUY";
let offer: OfferInfo = await postOffer(charlie, {assetCode: assetCode, direction: direction, awaitUnlockedBalance: false}); let offer: OfferInfo = await postOffer(charlie, {assetCode: assetCode, direction: direction, awaitUnlockedBalance: false});
assert.equal(offer.getState(), "SCHEDULED"); assert.equal(offer.getState(), "SCHEDULED");
@ -1202,7 +1225,7 @@ test("Can resolve disputes", async () => {
// wait for alice and bob to have unlocked balance for trade // wait for alice and bob to have unlocked balance for trade
const tradeAmount = BigInt("250000000000"); const tradeAmount = BigInt("250000000000");
await fundOutputs([aliceWallet, bobWallet], tradeAmount * BigInt("6"), 4, true); await fundOutputs([aliceWallet, bobWallet], tradeAmount * BigInt("6"), 4);
// register to receive notifications // register to receive notifications
const aliceNotifications: NotificationMessage[] = []; const aliceNotifications: NotificationMessage[] = [];
@ -2074,6 +2097,13 @@ function getRandomAssetCode() {
return TestConfig.assetCodes[GenUtils.getRandomInt(0, TestConfig.assetCodes.length - 1)]; return TestConfig.assetCodes[GenUtils.getRandomInt(0, TestConfig.assetCodes.length - 1)];
} }
async function hasPaymentAccount(trader: HavenoClient, assetCode: string): Promise<boolean> {
for (const paymentAccount of await trader.getPaymentAccounts()) {
if (paymentAccount.getSelectedTradeCurrency()!.getCode() === assetCode.toUpperCase()) return true;
}
return false;
}
async function createPaymentAccount(trader: HavenoClient, assetCode: string): Promise<PaymentAccount> { async function createPaymentAccount(trader: HavenoClient, assetCode: string): Promise<PaymentAccount> {
return isCrypto(assetCode) ? createCryptoPaymentAccount(trader, assetCode) : createRevolutPaymentAccount(trader); return isCrypto(assetCode) ? createCryptoPaymentAccount(trader, assetCode) : createRevolutPaymentAccount(trader);
} }
@ -2090,6 +2120,8 @@ function getCryptoAddress(currencyCode: string): string | undefined {
async function createRevolutPaymentAccount(trader: HavenoClient): Promise<PaymentAccount> { async function createRevolutPaymentAccount(trader: HavenoClient): Promise<PaymentAccount> {
const accountForm = await trader.getPaymentAccountForm('REVOLUT'); const accountForm = await trader.getPaymentAccountForm('REVOLUT');
accountForm.tradeCurrencies ="gbp,eur,usd";
accountForm.selectedTradeCurrency = "usd";
accountForm.accountName = "Revolut account " + GenUtils.getUUID(); accountForm.accountName = "Revolut account " + GenUtils.getUUID();
accountForm.userName = "user123"; accountForm.userName = "user123";
return trader.createPaymentAccount(accountForm); return trader.createPaymentAccount(accountForm);

View File

@ -781,12 +781,16 @@ export default class HavenoClient {
* @return {number} the price of the asset per 1 XMR * @return {number} the price of the asset per 1 XMR
*/ */
async getPrice(assetCode: string): Promise<number> { async getPrice(assetCode: string): Promise<number> {
return new Promise((resolve, reject) => { try { // TODO (woodser): try...catch is necessary to preserve stack trace. use throughout client
return await new Promise((resolve, reject) => {
this._priceClient.getMarketPrice(new MarketPriceRequest().setCurrencyCode(assetCode), {password: this._password}, function(err: grpcWeb.RpcError, response: MarketPriceReply) { this._priceClient.getMarketPrice(new MarketPriceRequest().setCurrencyCode(assetCode), {password: this._password}, function(err: grpcWeb.RpcError, response: MarketPriceReply) {
if (err) reject(err); if (err) reject(err);
else resolve(response.getPrice()); else resolve(response.getPrice());
}); });
}); });
} catch (e: any) {
throw new Error(e.message);
}
} }
/** /**
@ -891,12 +895,16 @@ export default class HavenoClient {
* @return {PaymentAccount} the created payment account * @return {PaymentAccount} the created payment account
*/ */
async createPaymentAccount(paymentAccountForm: any): Promise<PaymentAccount> { async createPaymentAccount(paymentAccountForm: any): Promise<PaymentAccount> {
return new Promise((resolve, reject) => { try {
return await new Promise((resolve, reject) => {
this._paymentAccountsClient.createPaymentAccount(new CreatePaymentAccountRequest().setPaymentAccountForm(JSON.stringify(paymentAccountForm)), {password: this._password}, function(err: grpcWeb.RpcError, response: CreatePaymentAccountReply) { this._paymentAccountsClient.createPaymentAccount(new CreatePaymentAccountRequest().setPaymentAccountForm(JSON.stringify(paymentAccountForm)), {password: this._password}, function(err: grpcWeb.RpcError, response: CreatePaymentAccountReply) {
if (err) reject(err); if (err) reject(err);
else resolve(response.getPaymentAccount()!); else resolve(response.getPaymentAccount()!);
}); });
}); });
} catch (e: any) {
throw new Error(e.message); // re-catch error to preserve stack trace TODO: repeat this pattern throughout this class
}
} }
/** /**
@ -913,12 +921,16 @@ export default class HavenoClient {
.setCurrencyCode(assetCode) .setCurrencyCode(assetCode)
.setAddress(address) .setAddress(address)
.setTradeInstant(false); // not using instant trades .setTradeInstant(false); // not using instant trades
return new Promise((resolve, reject) => { try {
return await new Promise((resolve, reject) => {
this._paymentAccountsClient.createCryptoCurrencyPaymentAccount(request, {password: this._password}, function(err: grpcWeb.RpcError, response: CreateCryptoCurrencyPaymentAccountReply) { this._paymentAccountsClient.createCryptoCurrencyPaymentAccount(request, {password: this._password}, function(err: grpcWeb.RpcError, response: CreateCryptoCurrencyPaymentAccountReply) {
if (err) reject(err); if (err) reject(err);
else resolve(response.getPaymentAccount()!); else resolve(response.getPaymentAccount()!);
}); });
}); });
} catch (e: any) {
throw new Error(e.message);
}
} }
/** /**
@ -977,9 +989,9 @@ export default class HavenoClient {
* @param {bigint} amount - amount of XMR to trade * @param {bigint} amount - amount of XMR to trade
* @param {string} assetCode - asset code to trade for XMR * @param {string} assetCode - asset code to trade for XMR
* @param {string} paymentAccountId - payment account id * @param {string} paymentAccountId - payment account id
* @param {number} buyerSecurityDeposit - buyer security deposit as % of trade amount * @param {number} buyerSecurityDepositPct - buyer security deposit as % of trade amount
* @param {number} price - trade price (optional, default to market price) * @param {number} price - trade price (optional, default to market price)
* @param {number} marketPriceMargin - if using market price, % from market price to accept (optional, default 0%) * @param {number} marketPriceMarginPct - if using market price, % from market price to accept (optional, default 0%)
* @param {bigint} minAmount - minimum amount to trade (optional, default to fixed amount) * @param {bigint} minAmount - minimum amount to trade (optional, default to fixed amount)
* @param {number} triggerPrice - price to remove offer (optional) * @param {number} triggerPrice - price to remove offer (optional)
* @return {OfferInfo} the posted offer * @return {OfferInfo} the posted offer
@ -988,9 +1000,9 @@ export default class HavenoClient {
amount: bigint, amount: bigint,
assetCode: string, assetCode: string,
paymentAccountId: string, paymentAccountId: string,
buyerSecurityDeposit: number, buyerSecurityDepositPct: number,
price?: number, price?: number,
marketPriceMargin?: number, marketPriceMarginPct?: number,
triggerPrice?: number, triggerPrice?: number,
minAmount?: bigint): Promise<OfferInfo> { minAmount?: bigint): Promise<OfferInfo> {
const request = new CreateOfferRequest() const request = new CreateOfferRequest()
@ -998,11 +1010,11 @@ export default class HavenoClient {
.setAmount(amount.toString()) .setAmount(amount.toString())
.setCurrencyCode(assetCode) .setCurrencyCode(assetCode)
.setPaymentAccountId(paymentAccountId) .setPaymentAccountId(paymentAccountId)
.setBuyerSecurityDeposit(buyerSecurityDeposit) .setBuyerSecurityDepositPct(buyerSecurityDepositPct)
.setPrice(price ? price.toString() : "1.0") // TOOD (woodser): positive price required even if using market price? .setPrice(price ? price.toString() : "1.0") // TOOD (woodser): positive price required even if using market price?
.setUseMarketBasedPrice(price === undefined) // TODO (woodser): this field is redundant; remove from api .setUseMarketBasedPrice(price === undefined) // TODO (woodser): this field is redundant; remove from api
.setMinAmount(minAmount ? minAmount.toString() : amount.toString()); .setMinAmount(minAmount ? minAmount.toString() : amount.toString());
if (marketPriceMargin) request.setMarketPriceMargin(marketPriceMargin); if (marketPriceMarginPct) request.setMarketPriceMarginPct(marketPriceMarginPct);
if (triggerPrice) request.setTriggerPrice(triggerPrice.toString()); if (triggerPrice) request.setTriggerPrice(triggerPrice.toString());
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this._offersClient.createOffer(request, {password: this._password}, function(err: grpcWeb.RpcError, response: CreateOfferReply) { this._offersClient.createOffer(request, {password: this._password}, function(err: grpcWeb.RpcError, response: CreateOfferReply) {