Skip to content

Commit 1f5ee14

Browse files
committed
update payment methods
1 parent 6fa1db7 commit 1f5ee14

File tree

2 files changed

+92
-48
lines changed

2 files changed

+92
-48
lines changed

src/HavenoClient.test.ts

Lines changed: 59 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import console from "console"; // import console because jest swallows messages
3030
// ------------------------------ TEST CONFIG ---------------------------------
3131

3232
const TestConfig = {
33-
logLevel: 0,
33+
logLevel: 1,
3434
moneroBinsDir: "../haveno/.localnet",
3535
testDataDir: "./testdata",
3636
networkType: monerojs.MoneroNetworkType.STAGENET,
@@ -308,7 +308,7 @@ test("Can manage an account", async () => {
308308
assert(size > 0);
309309

310310
// delete account which shuts down server
311-
await charlie.deleteAccount();
311+
await charlie.deleteAccount(); // TODO: support deleting and restoring account without shutting down server, #310
312312
assert(!await charlie.isConnectedToDaemon());
313313
await releaseHavenoProcess(charlie);
314314

@@ -709,7 +709,7 @@ test("Can get market prices", async () => {
709709
const price = await alice.getPrice(assetCode);
710710
expect(price).toBeGreaterThan(0);
711711
}
712-
712+
713713
// test that prices are reasonable
714714
const usd = await alice.getPrice("USD");
715715
expect(usd).toBeGreaterThan(50);
@@ -720,7 +720,7 @@ test("Can get market prices", async () => {
720720
const btc = await alice.getPrice("BTC");
721721
expect(btc).toBeGreaterThan(0.0004)
722722
expect(btc).toBeLessThan(0.4);
723-
723+
724724
// test invalid currency
725725
await expect(async () => { await alice.getPrice("INVALID_CURRENCY") })
726726
.rejects
@@ -769,20 +769,19 @@ test("Can get market depth", async () => {
769769
expect(marketDepth.getSellPricesList().length).toEqual(marketDepth.getSellDepthList().length);
770770

771771
// test buy prices and depths
772-
const priceDivisor = 100000000; // TODO: offer price = price * 100000000
773-
const buyOffers = (await alice.getOffers(assetCode, "buy")).concat(await alice.getMyOffers(assetCode, "buy")).sort(function(a, b) { return a.getPrice() - b.getPrice() });
774-
expect(marketDepth.getBuyPricesList()[0]).toEqual(1 / (buyOffers[0].getPrice() / priceDivisor)); // TODO: price when posting offer is reversed. this assumes crypto counter currency
775-
expect(marketDepth.getBuyPricesList()[1]).toEqual(1 / (buyOffers[1].getPrice() / priceDivisor));
776-
expect(marketDepth.getBuyPricesList()[2]).toEqual(1 / (buyOffers[2].getPrice() / priceDivisor));
772+
const buyOffers = (await alice.getOffers(assetCode, "buy")).concat(await alice.getMyOffers(assetCode, "buy")).sort(function(a, b) { return parseFloat(a.getPrice()) - parseFloat(b.getPrice()) });
773+
expect(marketDepth.getBuyPricesList()[0]).toEqual(1 / parseFloat(buyOffers[0].getPrice())); // TODO: price when posting offer is reversed. this assumes crypto counter currency
774+
expect(marketDepth.getBuyPricesList()[1]).toEqual(1 / parseFloat(buyOffers[1].getPrice()));
775+
expect(marketDepth.getBuyPricesList()[2]).toEqual(1 / parseFloat(buyOffers[2].getPrice()));
777776
expect(marketDepth.getBuyDepthList()[0]).toEqual(0.15);
778777
expect(marketDepth.getBuyDepthList()[1]).toEqual(0.30);
779778
expect(marketDepth.getBuyDepthList()[2]).toEqual(0.65);
780779

781780
// test sell prices and depths
782-
const sellOffers = (await alice.getOffers(assetCode, "sell")).concat(await alice.getMyOffers(assetCode, "sell")).sort(function(a, b) { return b.getPrice() - a.getPrice() });
783-
expect(marketDepth.getSellPricesList()[0]).toEqual(1 / (sellOffers[0].getPrice() / priceDivisor));
784-
expect(marketDepth.getSellPricesList()[1]).toEqual(1 / (sellOffers[1].getPrice() / priceDivisor));
785-
expect(marketDepth.getSellPricesList()[2]).toEqual(1 / (sellOffers[2].getPrice() / priceDivisor));
781+
const sellOffers = (await alice.getOffers(assetCode, "sell")).concat(await alice.getMyOffers(assetCode, "sell")).sort(function(a, b) { return parseFloat(b.getPrice()) - parseFloat(a.getPrice()) });
782+
expect(marketDepth.getSellPricesList()[0]).toEqual(1 / parseFloat(sellOffers[0].getPrice()));
783+
expect(marketDepth.getSellPricesList()[1]).toEqual(1 / parseFloat(sellOffers[1].getPrice()));
784+
expect(marketDepth.getSellPricesList()[2]).toEqual(1 / parseFloat(sellOffers[2].getPrice()));
786785
expect(marketDepth.getSellDepthList()[0]).toEqual(0.3);
787786
expect(marketDepth.getSellDepthList()[1]).toEqual(0.6);
788787
expect(marketDepth.getSellDepthList()[2]).toEqual(1);
@@ -860,6 +859,8 @@ test("Can create fiat payment accounts", async () => {
860859
const accountForm = await alice.getPaymentAccountForm(paymentMethodId);
861860

862861
// edit form
862+
accountForm.tradeCurrencies ="gbp,eur,usd";
863+
accountForm.selectedTradeCurrency = "usd";
863864
accountForm.accountName = "Revolut account " + GenUtils.getUUID();
864865
accountForm.userName = "user123";
865866

@@ -927,28 +928,50 @@ test("Can create crypto payment accounts", async () => {
927928
}
928929
});
929930

930-
test("Can post and remove offers", async () => {
931+
test("Can prepare for trading", async () => {
932+
933+
// create payment accounts
934+
if (!await hasPaymentAccount(alice, "eth")) await createPaymentAccount(alice, "eth");
935+
if (!await hasPaymentAccount(alice, "bch")) await createPaymentAccount(alice, "bch");
936+
if (!await hasPaymentAccount(alice, "usd")) await createPaymentAccount(alice, "usd");
937+
if (!await hasPaymentAccount(bob, "eth")) await createPaymentAccount(bob, "eth");
938+
if (!await hasPaymentAccount(bob, "bch")) await createPaymentAccount(bob, "bch");
939+
if (!await hasPaymentAccount(bob, "usd")) await createPaymentAccount(bob, "usd");
940+
941+
// fund wallets
942+
const tradeAmount = BigInt("250000000000");
943+
await fundOutputs([aliceWallet, bobWallet], tradeAmount * BigInt("6"), 4);
944+
945+
// wait for havenod to observe funds
946+
await wait(TestConfig.walletSyncPeriodMs);
947+
});
931948

932-
// wait for alice to have at least 5 outputs of 0.5 XMR
933-
await fundOutputs([aliceWallet], BigInt("500000000000"), 5);
949+
test("Can post and remove offers", async () => {
950+
951+
// wait for alice to have unlocked balance to post offer
952+
await waitForUnlockedBalance(BigInt("250000000000") * BigInt("2"), alice);
934953

935954
// get unlocked balance before reserving funds for offer
936955
const unlockedBalanceBefore = BigInt((await alice.getBalances()).getUnlockedBalance());
937956

938957
// post crypto offer
939-
let assetCode = "ETH";
958+
let assetCode = "BCH";
940959
let price = 1 / 17;
941960
price = 1 / price; // TODO: price in crypto offer is inverted
942961
let offer: OfferInfo = await postOffer(alice, {assetCode: assetCode, price: price});
943962
assert.equal(offer.getState(), "AVAILABLE");
944963
assert.equal(offer.getBaseCurrencyCode(), assetCode); // TODO: base and counter currencies inverted in crypto offer
945964
assert.equal(offer.getCounterCurrencyCode(), "XMR");
946-
assert.equal(offer.getPrice(), price * 100000000); // TODO: price when posting crypto offer is inverted and * 100000000.
965+
assert.equal(parseFloat(offer.getPrice()), price);
947966

948967
// has offer
949968
offer = await alice.getMyOffer(offer.getId());
950969
assert.equal(offer.getState(), "AVAILABLE");
951970

971+
// peer sees offer
972+
await wait(TestConfig.maxTimePeerNoticeMs);
973+
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");
974+
952975
// cancel offer
953976
await alice.removeOffer(offer.getId());
954977

@@ -965,7 +988,7 @@ test("Can post and remove offers", async () => {
965988
assert.equal(offer.getState(), "AVAILABLE");
966989
assert.equal(offer.getBaseCurrencyCode(), "XMR");
967990
assert.equal(offer.getCounterCurrencyCode(), "USD");
968-
assert.equal(offer.getPrice(), price * 10000); // TODO: price = price * 10000
991+
assert.equal(parseFloat(offer.getPrice()), price);
969992

970993
// has offer
971994
offer = await alice.getMyOffer(offer.getId());
@@ -997,7 +1020,7 @@ test("Can schedule offers with locked funds", async () => {
9971020
await fundOutputs([charlieWallet], outputAmt, 2, false);
9981021

9991022
// schedule offer
1000-
const assetCode = "ETH";
1023+
const assetCode = "BCH";
10011024
const direction = "BUY";
10021025
let offer: OfferInfo = await postOffer(charlie, {assetCode: assetCode, direction: direction, awaitUnlockedBalance: false});
10031026
assert.equal(offer.getState(), "SCHEDULED");
@@ -1202,7 +1225,7 @@ test("Can resolve disputes", async () => {
12021225

12031226
// wait for alice and bob to have unlocked balance for trade
12041227
const tradeAmount = BigInt("250000000000");
1205-
await fundOutputs([aliceWallet, bobWallet], tradeAmount * BigInt("6"), 4, true);
1228+
await fundOutputs([aliceWallet, bobWallet], tradeAmount * BigInt("6"), 4);
12061229

12071230
// register to receive notifications
12081231
const aliceNotifications: NotificationMessage[] = [];
@@ -2071,25 +2094,34 @@ function testDestination(destination: XmrDestination) {
20712094
}
20722095

20732096
function getRandomAssetCode() {
2074-
return TestConfig.assetCodes[GenUtils.getRandomInt(0, TestConfig.assetCodes.length - 1)];
2097+
return TestConfig.assetCodes[GenUtils.getRandomInt(0, TestConfig.assetCodes.length - 1)];
2098+
}
2099+
2100+
async function hasPaymentAccount(trader: HavenoClient, assetCode: string): Promise<boolean> {
2101+
for (const paymentAccount of await trader.getPaymentAccounts()) {
2102+
if (paymentAccount.getSelectedTradeCurrency()!.getCode() === assetCode.toUpperCase()) return true;
2103+
}
2104+
return false;
20752105
}
20762106

20772107
async function createPaymentAccount(trader: HavenoClient, assetCode: string): Promise<PaymentAccount> {
2078-
return isCrypto(assetCode) ? createCryptoPaymentAccount(trader, assetCode) : createRevolutPaymentAccount(trader);
2108+
return isCrypto(assetCode) ? createCryptoPaymentAccount(trader, assetCode) : createRevolutPaymentAccount(trader);
20792109
}
20802110

20812111
function isCrypto(assetCode: string) {
2082-
return getCryptoAddress(assetCode) !== undefined;
2112+
return getCryptoAddress(assetCode) !== undefined;
20832113
}
20842114

20852115
function getCryptoAddress(currencyCode: string): string | undefined {
2086-
for (const cryptoAddress of TestConfig.cryptoAddresses) {
2087-
if (cryptoAddress.currencyCode === currencyCode.toUpperCase()) return cryptoAddress.address;
2088-
}
2116+
for (const cryptoAddress of TestConfig.cryptoAddresses) {
2117+
if (cryptoAddress.currencyCode === currencyCode.toUpperCase()) return cryptoAddress.address;
2118+
}
20892119
}
20902120

20912121
async function createRevolutPaymentAccount(trader: HavenoClient): Promise<PaymentAccount> {
20922122
const accountForm = await trader.getPaymentAccountForm('REVOLUT');
2123+
accountForm.tradeCurrencies ="gbp,eur,usd";
2124+
accountForm.selectedTradeCurrency = "usd";
20932125
accountForm.accountName = "Revolut account " + GenUtils.getUUID();
20942126
accountForm.userName = "user123";
20952127
return trader.createPaymentAccount(accountForm);

src/HavenoClient.ts

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -781,12 +781,16 @@ export default class HavenoClient {
781781
* @return {number} the price of the asset per 1 XMR
782782
*/
783783
async getPrice(assetCode: string): Promise<number> {
784-
return new Promise((resolve, reject) => {
785-
this._priceClient.getMarketPrice(new MarketPriceRequest().setCurrencyCode(assetCode), {password: this._password}, function(err: grpcWeb.RpcError, response: MarketPriceReply) {
786-
if (err) reject(err);
787-
else resolve(response.getPrice());
784+
try { // TODO (woodser): try...catch is necessary to preserve stack trace. use throughout client
785+
return await new Promise((resolve, reject) => {
786+
this._priceClient.getMarketPrice(new MarketPriceRequest().setCurrencyCode(assetCode), {password: this._password}, function(err: grpcWeb.RpcError, response: MarketPriceReply) {
787+
if (err) reject(err);
788+
else resolve(response.getPrice());
789+
});
788790
});
789-
});
791+
} catch (e: any) {
792+
throw new Error(e.message);
793+
}
790794
}
791795

792796
/**
@@ -891,12 +895,16 @@ export default class HavenoClient {
891895
* @return {PaymentAccount} the created payment account
892896
*/
893897
async createPaymentAccount(paymentAccountForm: any): Promise<PaymentAccount> {
894-
return new Promise((resolve, reject) => {
895-
this._paymentAccountsClient.createPaymentAccount(new CreatePaymentAccountRequest().setPaymentAccountForm(JSON.stringify(paymentAccountForm)), {password: this._password}, function(err: grpcWeb.RpcError, response: CreatePaymentAccountReply) {
896-
if (err) reject(err);
897-
else resolve(response.getPaymentAccount()!);
898+
try {
899+
return await new Promise((resolve, reject) => {
900+
this._paymentAccountsClient.createPaymentAccount(new CreatePaymentAccountRequest().setPaymentAccountForm(JSON.stringify(paymentAccountForm)), {password: this._password}, function(err: grpcWeb.RpcError, response: CreatePaymentAccountReply) {
901+
if (err) reject(err);
902+
else resolve(response.getPaymentAccount()!);
903+
});
898904
});
899-
});
905+
} catch (e: any) {
906+
throw new Error(e.message); // re-catch error to preserve stack trace TODO: repeat this pattern throughout this class
907+
}
900908
}
901909

902910
/**
@@ -913,12 +921,16 @@ export default class HavenoClient {
913921
.setCurrencyCode(assetCode)
914922
.setAddress(address)
915923
.setTradeInstant(false); // not using instant trades
916-
return new Promise((resolve, reject) => {
917-
this._paymentAccountsClient.createCryptoCurrencyPaymentAccount(request, {password: this._password}, function(err: grpcWeb.RpcError, response: CreateCryptoCurrencyPaymentAccountReply) {
918-
if (err) reject(err);
919-
else resolve(response.getPaymentAccount()!);
924+
try {
925+
return await new Promise((resolve, reject) => {
926+
this._paymentAccountsClient.createCryptoCurrencyPaymentAccount(request, {password: this._password}, function(err: grpcWeb.RpcError, response: CreateCryptoCurrencyPaymentAccountReply) {
927+
if (err) reject(err);
928+
else resolve(response.getPaymentAccount()!);
929+
});
920930
});
921-
});
931+
} catch (e: any) {
932+
throw new Error(e.message);
933+
}
922934
}
923935

924936
/**
@@ -977,9 +989,9 @@ export default class HavenoClient {
977989
* @param {bigint} amount - amount of XMR to trade
978990
* @param {string} assetCode - asset code to trade for XMR
979991
* @param {string} paymentAccountId - payment account id
980-
* @param {number} buyerSecurityDeposit - buyer security deposit as % of trade amount
992+
* @param {number} buyerSecurityDepositPct - buyer security deposit as % of trade amount
981993
* @param {number} price - trade price (optional, default to market price)
982-
* @param {number} marketPriceMargin - if using market price, % from market price to accept (optional, default 0%)
994+
* @param {number} marketPriceMarginPct - if using market price, % from market price to accept (optional, default 0%)
983995
* @param {bigint} minAmount - minimum amount to trade (optional, default to fixed amount)
984996
* @param {number} triggerPrice - price to remove offer (optional)
985997
* @return {OfferInfo} the posted offer
@@ -988,21 +1000,21 @@ export default class HavenoClient {
9881000
amount: bigint,
9891001
assetCode: string,
9901002
paymentAccountId: string,
991-
buyerSecurityDeposit: number,
1003+
buyerSecurityDepositPct: number,
9921004
price?: number,
993-
marketPriceMargin?: number,
1005+
marketPriceMarginPct?: number,
9941006
triggerPrice?: number,
9951007
minAmount?: bigint): Promise<OfferInfo> {
9961008
const request = new CreateOfferRequest()
9971009
.setDirection(direction)
9981010
.setAmount(amount.toString())
9991011
.setCurrencyCode(assetCode)
10001012
.setPaymentAccountId(paymentAccountId)
1001-
.setBuyerSecurityDeposit(buyerSecurityDeposit)
1013+
.setBuyerSecurityDepositPct(buyerSecurityDepositPct)
10021014
.setPrice(price ? price.toString() : "1.0") // TOOD (woodser): positive price required even if using market price?
10031015
.setUseMarketBasedPrice(price === undefined) // TODO (woodser): this field is redundant; remove from api
10041016
.setMinAmount(minAmount ? minAmount.toString() : amount.toString());
1005-
if (marketPriceMargin) request.setMarketPriceMargin(marketPriceMargin);
1017+
if (marketPriceMarginPct) request.setMarketPriceMarginPct(marketPriceMarginPct);
10061018
if (triggerPrice) request.setTriggerPrice(triggerPrice.toString());
10071019
return new Promise((resolve, reject) => {
10081020
this._offersClient.createOffer(request, {password: this._password}, function(err: grpcWeb.RpcError, response: CreateOfferReply) {

0 commit comments

Comments
 (0)