test basic end-to-end trade completion

add methods to take offer and indicate payment to haveno daemon
add getNewDepositSubaddress() to haveno daemon
automatically fund haveno wallets and await unlocked funds
run tests sequentially
update protobuf definition
This commit is contained in:
woodser 2021-09-19 14:00:22 -04:00
parent e35f8c5e95
commit a268b26784
7 changed files with 649 additions and 58 deletions

View File

@ -431,6 +431,8 @@ message TxInfo {
service Wallets { service Wallets {
rpc GetBalances (GetBalancesRequest) returns (GetBalancesReply) { rpc GetBalances (GetBalancesRequest) returns (GetBalancesReply) {
} }
rpc GetNewDepositSubaddress (GetNewDepositSubaddressRequest) returns (GetNewDepositSubaddressReply) {
}
rpc GetAddressBalance (GetAddressBalanceRequest) returns (GetAddressBalanceReply) { rpc GetAddressBalance (GetAddressBalanceRequest) returns (GetAddressBalanceReply) {
} }
rpc GetUnusedBsqAddress (GetUnusedBsqAddressRequest) returns (GetUnusedBsqAddressReply) { rpc GetUnusedBsqAddress (GetUnusedBsqAddressRequest) returns (GetUnusedBsqAddressReply) {
@ -469,6 +471,13 @@ message GetBalancesReply {
BalancesInfo balances = 1; BalancesInfo balances = 1;
} }
message GetNewDepositSubaddressRequest {
}
message GetNewDepositSubaddressReply {
string subaddress = 1;
}
message GetAddressBalanceRequest { message GetAddressBalanceRequest {
string address = 1; string address = 1;
} }

View File

@ -21,7 +21,7 @@
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
"build": "react-scripts build", "build": "react-scripts build",
"test": "react-scripts test", "test": "react-scripts test --runInBand",
"eject": "react-scripts eject" "eject": "react-scripts eject"
}, },
"eslintConfig": { "eslintConfig": {

View File

@ -1,23 +1,26 @@
// import haveno types // import haveno types
import {HavenoDaemon} from "./HavenoDaemon"; import {HavenoDaemon} from "./HavenoDaemon";
import {XmrBalanceInfo, OfferInfo} from './protobuf/grpc_pb'; // TODO (woodser): better names; haveno_grpc_pb, haveno_pb import {XmrBalanceInfo, OfferInfo, TradeInfo} from './protobuf/grpc_pb'; // TODO (woodser): better names; haveno_grpc_pb, haveno_pb
import {PaymentAccount} from './protobuf/pb_pb'; import {PaymentAccount} from './protobuf/pb_pb';
// import monero-javascript // import monero-javascript
const monerojs = require("monero-javascript"); // TODO (woodser): support typescript and `npm install @types/monero-javascript` in monero-javascript const monerojs = require("monero-javascript"); // TODO (woodser): support typescript and `npm install @types/monero-javascript` in monero-javascript
const MoneroDaemonRpc = monerojs.MoneroDaemonRpc; const MoneroTxConfig = monerojs.MoneroTxConfig;
const MoneroWalletRpc = monerojs.MoneroWalletRpc; const TaskLooper = monerojs.TaskLooper;
// import console because jest swallows messages in real time
const console = require('console');
// alice config // alice config
const havenoVersion = "1.6.2"; const havenoVersion = "1.6.2";
const aliceDaemonUrl = "http://localhost:8080"; const aliceDaemonUrl = "http://localhost:8080";
const aliceDaemonPassword = "apitest"; const aliceDaemonPassword = "apitest";
const alice: HavenoDaemon = new HavenoDaemon(aliceDaemonUrl, aliceDaemonPassword); const alice: HavenoDaemon = new HavenoDaemon(aliceDaemonUrl, aliceDaemonPassword);
const aliceWalletUrl = "http://127.0.0.1:51743"; // alice's internal haveno wallet for direct testing // TODO (woodser): make configurable rather than randomly generated const aliceWalletUrl = "http://127.0.0.1:57983"; // alice's internal haveno wallet for direct testing // TODO (woodser): make configurable rather than randomly generated
const aliceWalletUsername = "rpc_user"; const aliceWalletUsername = "rpc_user";
const aliceWalletPassword = "abc123"; const aliceWalletPassword = "abc123";
const aliceWallet = new MoneroWalletRpc(aliceWalletUrl, aliceWalletUsername, aliceWalletPassword);
const aliceWalletSyncPeriod = 5000; const aliceWalletSyncPeriod = 5000;
let aliceWallet: any;
// bob config // bob config
const bobDaemonUrl = "http://localhost:8081"; const bobDaemonUrl = "http://localhost:8081";
@ -27,15 +30,35 @@ const bob: HavenoDaemon = new HavenoDaemon(bobDaemonUrl, bobDaemonPassword);
// monero daemon config // monero daemon config
const moneroDaemonUrl = "http://localhost:38081" const moneroDaemonUrl = "http://localhost:38081"
const moneroDaemonUsername = "superuser"; const moneroDaemonUsername = "superuser";
const moneroDaemonPassword = "abctesting123"; const moneroDaemonPassword = "abctesting123";
const miningAddress = "59M2dSSmrKiimFavjWQ8zFGWe6ziHr9XUjhHcMVEj9ut4EdkcmcqawfgMrtEERipUJA8iNzU65eaELoFYcor1c4jK4FRj1N";
let monerod: any; let monerod: any;
// source funding wallet
const fundingWalletUrl = "http://localhost:38084";
const fundingWalletUsername = "rpc_user";
const fundingWalletPassword = "abc123";
let fundingWallet: any;
// other test config
const MAX_TIME_PEER_NOTICE = 3000;
beforeAll(async () => { beforeAll(async () => {
await monerojs.LibraryUtils.setWorkerDistPath("./node_modules/monero-javascript/src/main/js/common/MoneroWebWorker.js"); // TODO (woodser): remove this when update to monero-javascript-v0.5.6 which correctly detects node environment
// TODO (woodser): remove this when update to monero-javascript-v0.5.6 which correctly detects node environment
await monerojs.LibraryUtils.setWorkerDistPath("./node_modules/monero-javascript/src/main/js/common/MoneroWebWorker.js");
// initialize clients of wallet and daemon rpc
aliceWallet = await monerojs.connectToWalletRpc(aliceWalletUrl, aliceWalletUsername, aliceWalletPassword);
fundingWallet = await monerojs.connectToWalletRpc(fundingWalletUrl, fundingWalletUsername, fundingWalletPassword);
monerod = await monerojs.connectToDaemonRpc(moneroDaemonUrl, moneroDaemonUsername, moneroDaemonPassword); monerod = await monerojs.connectToDaemonRpc(moneroDaemonUrl, moneroDaemonUsername, moneroDaemonPassword);
// debug tools
//for (let offer of await alice.getMyOffers("BUY")) await alice.removeOffer(offer.getId()); //for (let offer of await alice.getMyOffers("BUY")) await alice.removeOffer(offer.getId());
//for (let offer of await alice.getMyOffers("SELL")) await alice.removeOffer(offer.getId()); //for (let offer of await alice.getMyOffers("SELL")) await alice.removeOffer(offer.getId());
//for (let frozenOutput of await aliceWallet.getOutputs({isFrozen: true})) await aliceWallet.thawOutput(frozenOutput.getKeyImage().getHex()); //for (let frozenOutput of await aliceWallet.getOutputs({isFrozen: true})) await aliceWallet.thawOutput(frozenOutput.getKeyImage().getHex());
//console.log((await alice.getBalances()).getUnlockedBalance() + ", " + (await alice.getBalances()).getLockedBalance());
//console.log((await bob.getBalances()).getUnlockedBalance() + ", " + (await bob.getBalances()).getLockedBalance());
}); });
test("Can get the version", async () => { test("Can get the version", async () => {
@ -119,7 +142,7 @@ test("Invalidates offers when reserved funds are spent", async () => {
} }
// offer is available to peers // offer is available to peers
await wait(3000); await wait(MAX_TIME_PEER_NOTICE);
if (!getOffer(await bob.getOffers("buy"), offer.getId())) throw new Error("Offer " + offer.getId() + " was not found in peer's offers after posting"); if (!getOffer(await bob.getOffers("buy"), offer.getId())) throw new Error("Offer " + offer.getId() + " was not found in peer's offers after posting");
// spend one of offer's reserved outputs // spend one of offer's reserved outputs
@ -149,18 +172,86 @@ test("Invalidates offers when reserved funds are spent", async () => {
await monerod.flushTxPool(tx.getHash()); await monerod.flushTxPool(tx.getHash());
}); });
jest.setTimeout(120000);
test("Can complete a trade", async () => { test("Can complete a trade", async () => {
console.log("Alice balances: " + getBalancesStr(await alice.getBalances()));
// wait for alice and bob to have unlocked balance for trade // wait for alice and bob to have unlocked balance for trade
let tradeAmount: bigint = BigInt("250000000000"); let tradeAmount: bigint = BigInt("250000000000");
await waitForUnlockedBalance(tradeAmount, alice, bob); await waitForUnlockedBalance(tradeAmount, alice, bob);
// TODO: finish this test // alice posts offer to buy xmr
console.log("Alice posting offer");
let offer: OfferInfo = await postOffer();
console.log("Alice done posting offer");
// bob sees offer
await wait(MAX_TIME_PEER_NOTICE);
// get bob's ethereum payment account
let ethPaymentAccount: PaymentAccount | undefined;
for (let paymentAccount of await bob.getPaymentAccounts()) {
if (paymentAccount.getSelectedTradeCurrency()?.getCode() === "ETH") {
ethPaymentAccount = paymentAccount;
break;
}
}
if (!ethPaymentAccount) throw new Error("Bob must have ethereum payment account to take offer");
// bob takes offer
let startTime = Date.now();
let bobBalancesBefore: XmrBalanceInfo = await bob.getBalances();
console.log("Bob taking offer");
let trade: TradeInfo = await bob.takeOffer(offer.getId(), ethPaymentAccount.getId()); // TODO (woodser): this returns before trade is fully initialized
console.log("Bob done taking offer in " + (Date.now() - startTime) + " ms");
// bob can get trade
let fetchedTrade: TradeInfo = await bob.getTrade(trade.getTradeId());
// TODO: test fetched trade
// test bob's balances after taking trade
let bobBalancesAfter: XmrBalanceInfo = await bob.getBalances();
expect(BigInt(bobBalancesAfter.getUnlockedBalance())).toBeLessThan(BigInt(bobBalancesBefore.getUnlockedBalance()));
expect(BigInt(bobBalancesAfter.getReservedOfferBalance()) + BigInt(bobBalancesAfter.getReservedTradeBalance())).toBeGreaterThan(BigInt(bobBalancesBefore.getReservedOfferBalance()) + BigInt(bobBalancesBefore.getReservedTradeBalance()));
// bob is notified of balance change
// alice notified of balance changes and that offer is taken
await wait(MAX_TIME_PEER_NOTICE);
// alice can get trade
fetchedTrade = await alice.getTrade(trade.getTradeId());
// mine until deposit txs unlock
console.log("Mining to unlock deposit txs");
await waitForUnlockedTxs(fetchedTrade.getMakerDepositTxId(), fetchedTrade.getTakerDepositTxId());
console.log("Done mining to unlock deposit txs");
// alice notified to send payment
await wait(5000);
// alice indicates payment is sent
await alice.confirmPaymentStarted(trade.getTradeId());
// bob notified payment is sent
await wait(MAX_TIME_PEER_NOTICE);
// bob confirms payment is received
await bob.confirmPaymentReceived(trade.getTradeId());
// bob notified trade is complete
fetchedTrade = await bob.getTrade(trade.getTradeId());
console.log(fetchedTrade.getState()); // TODO (woodser): this should be complete state
// test bob's balances after confirming payment
// alice notified trade is complete and of balance changes
}); });
// ------------------------------- HELPERS ------------------------------------ // ------------------------------- HELPERS ------------------------------------
async function postOffer() { async function postOffer() { // TODO (woodser): postOffer(maker, peer)
// test requires ethereum payment account // test requires ethereum payment account
let ethPaymentAccount: PaymentAccount | undefined; let ethPaymentAccount: PaymentAccount | undefined;
@ -177,7 +268,7 @@ async function postOffer() {
// post offer // post offer
// TODO: don't define variables, just document in comments // TODO: don't define variables, just document in comments
let amount: bigint = BigInt("250000000000"); let amount: bigint = BigInt("200000000000");
let minAmount: bigint = BigInt("150000000000"); let minAmount: bigint = BigInt("150000000000");
let price: number = 12.378981; // TODO: price is optional? price string gets converted to long? let price: number = 12.378981; // TODO: price is optional? price string gets converted to long?
let useMarketBasedPrice: boolean = true; let useMarketBasedPrice: boolean = true;
@ -208,8 +299,8 @@ async function postOffer() {
return offer; return offer;
} }
async function waitForUnlockedBalance(amount: bigint, ...clients: HavenoDaemon[]) { function getBalancesStr(balances: XmrBalanceInfo) {
throw new Error("waitForUnlockedFunds() not implemented"); // TODO: implement return "[unlocked balance=" + balances.getUnlockedBalance() + ", locked balance=" + balances.getLockedBalance() + ", reserved offer balance=" + balances.getReservedOfferBalance() + ", reserved trade balance: " + balances.getReservedTradeBalance() + "]";
} }
function getOffer(offers: OfferInfo[], id: string): OfferInfo | undefined { function getOffer(offers: OfferInfo[], id: string): OfferInfo | undefined {
@ -228,4 +319,71 @@ function testOffer(offer: OfferInfo) {
async function wait(durationMs: number) { async function wait(durationMs: number) {
return new Promise(function(resolve) { setTimeout(resolve, durationMs); }); return new Promise(function(resolve) { setTimeout(resolve, durationMs); });
}
async function startMining() {
try {
await monerod.startMining(miningAddress, 1);
} catch (err) {
if (err.message !== "Already mining") throw err;
}
}
async function waitForUnlockedBalance(amount: bigint, ...clients: HavenoDaemon[]) {
// fund haveno clients with insufficient balance
let miningNeeded = false;
let fundConfig = new MoneroTxConfig().setAccountIndex(0).setRelay(true);
for (let client of clients) {
let balances = await client.getBalances();
if (BigInt(balances.getUnlockedBalance()) < amount) miningNeeded = true;
let depositNeeded: BigInt = amount - BigInt(balances.getUnlockedBalance()) - BigInt(balances.getLockedBalance());
if (depositNeeded > BigInt("0")) fundConfig.addDestination(await client.getNewDepositSubaddress(), depositNeeded);
}
if (fundConfig.getDestinations()) {
try { await fundingWallet.createTx(fundConfig); }
catch (err) { throw new Error("Error funding haveno daemons: " + err.message); }
}
// done if all clients have sufficient unlocked balance
if (!miningNeeded) return;
// wait for funds to unlock
console.log("Mining for unlocked trader balances")
await startMining();
let promises: Promise<void>[] = []
for (let client of clients) {
promises.push(new Promise(async function(resolve, reject) {
let taskLooper: any = new TaskLooper(async function() {
let balances: XmrBalanceInfo = await client.getBalances();
if (BigInt(balances.getUnlockedBalance()) >= amount) {
taskLooper.stop();
resolve();
}
});
taskLooper.start(5000);
}));
}
await Promise.all(promises);
await monerod.stopMining();
console.log("Funds unlocked, done mining");
};
async function waitForUnlockedTxs(...txHashes: string[]) {
await startMining();
let promises: Promise<void>[] = []
for (let txHash of txHashes) {
promises.push(new Promise(async function(resolve, reject) {
let taskLooper: any = new TaskLooper(async function() {
let tx = await monerod.getTx(txHash);
if (tx.isConfirmed() && tx.getBlock().getHeight() <= await monerod.getHeight() - 10) {
taskLooper.stop();
resolve();
}
});
taskLooper.start(5000);
}));
}
await Promise.all(promises);
await monerod.stopMining();
} }

View File

@ -1,7 +1,7 @@
import * as grpcWeb from 'grpc-web'; import * as grpcWeb from 'grpc-web';
import {GetVersionClient, WalletsClient, OffersClient, PaymentAccountsClient} from './protobuf/GrpcServiceClientPb'; import {GetVersionClient, WalletsClient, OffersClient, PaymentAccountsClient, TradesClient} from './protobuf/GrpcServiceClientPb';
import {GetVersionRequest, GetVersionReply, GetBalancesRequest, GetBalancesReply, XmrBalanceInfo, GetOffersRequest, GetOffersReply, OfferInfo, GetPaymentAccountsRequest, GetPaymentAccountsReply, CreateCryptoCurrencyPaymentAccountRequest, CreateCryptoCurrencyPaymentAccountReply, CreateOfferRequest, CreateOfferReply, CancelOfferRequest} from './protobuf/grpc_pb'; import {GetVersionRequest, GetVersionReply, GetBalancesRequest, GetBalancesReply, XmrBalanceInfo, GetOffersRequest, GetOffersReply, OfferInfo, GetPaymentAccountsRequest, GetPaymentAccountsReply, CreateCryptoCurrencyPaymentAccountRequest, CreateCryptoCurrencyPaymentAccountReply, CreateOfferRequest, CreateOfferReply, CancelOfferRequest, TakeOfferRequest, TakeOfferReply, TradeInfo, GetTradeRequest, GetTradeReply, GetNewDepositSubaddressRequest, GetNewDepositSubaddressReply, ConfirmPaymentStartedRequest, ConfirmPaymentReceivedRequest} from './protobuf/grpc_pb';
import {PaymentAccount} from './protobuf/pb_pb'; import {PaymentAccount, AvailabilityResult} from './protobuf/pb_pb';
/** /**
* Haveno daemon client using gRPC. * Haveno daemon client using gRPC.
@ -13,8 +13,9 @@ class HavenoDaemon {
_password: string; _password: string;
_getVersionClient: GetVersionClient; _getVersionClient: GetVersionClient;
_walletsClient: WalletsClient; _walletsClient: WalletsClient;
_offersClient: OffersClient;
_paymentAccountsClient: PaymentAccountsClient; _paymentAccountsClient: PaymentAccountsClient;
_offersClient: OffersClient;
_tradesClient: TradesClient;
/** /**
* Construct a client connected to a Haveno daemon. * Construct a client connected to a Haveno daemon.
@ -27,8 +28,9 @@ class HavenoDaemon {
this._password = password; this._password = password;
this._getVersionClient = new GetVersionClient(this._url); this._getVersionClient = new GetVersionClient(this._url);
this._walletsClient = new WalletsClient(this._url); this._walletsClient = new WalletsClient(this._url);
this._offersClient = new OffersClient(this._url);
this._paymentAccountsClient = new PaymentAccountsClient(this._url); this._paymentAccountsClient = new PaymentAccountsClient(this._url);
this._offersClient = new OffersClient(this._url);
this._tradesClient = new TradesClient(this._url);
} }
/** /**
@ -38,9 +40,8 @@ class HavenoDaemon {
*/ */
async getVersion(): Promise<string> { async getVersion(): Promise<string> {
let that = this; let that = this;
let request = new GetVersionRequest();
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
that._getVersionClient.getVersion(request, {password: that._password}, function(err: grpcWeb.Error, response: GetVersionReply) { that._getVersionClient.getVersion(new GetVersionRequest(), {password: that._password}, function(err: grpcWeb.Error, response: GetVersionReply) {
if (err) reject(err); if (err) reject(err);
else resolve(response.getVersion()); else resolve(response.getVersion());
}); });
@ -54,9 +55,8 @@ class HavenoDaemon {
*/ */
async getBalances(): Promise<XmrBalanceInfo> { async getBalances(): Promise<XmrBalanceInfo> {
let that = this; let that = this;
let request = new GetBalancesRequest();
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
that._walletsClient.getBalances(request, {password: that._password}, function(err: grpcWeb.Error, response: GetBalancesReply) { that._walletsClient.getBalances(new GetBalancesRequest(), {password: that._password}, function(err: grpcWeb.Error, response: GetBalancesReply) {
if (err) reject(err); if (err) reject(err);
else resolve(response.getBalances()!.getXmr()!); else resolve(response.getBalances()!.getXmr()!);
}); });
@ -64,41 +64,16 @@ class HavenoDaemon {
} }
/** /**
* Get available offers to buy or sell XMR. * Get a new subaddress in the Haveno wallet to receive deposits.
* *
* @param {string} direction - one of "BUY" or "SELL" * @return {string} the deposit address (a subaddress in the Haveno wallet)
*
* @return {OfferInfo[]} available offers
*/ */
async getOffers(direction: string): Promise<OfferInfo[]> { async getNewDepositSubaddress(): Promise<string> {
let request = new GetOffersRequest()
.setDirection(direction)
.setCurrencyCode("XMR");
let that = this; let that = this;
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
that._offersClient.getOffers(request, {password: that._password}, function(err: grpcWeb.Error, response: GetOffersReply) { that._walletsClient.getNewDepositSubaddress(new GetNewDepositSubaddressRequest(), {password: that._password}, function(err: grpcWeb.Error, response: GetNewDepositSubaddressReply) {
if (err) reject(err); if (err) reject(err);
else resolve(response.getOffersList()); else resolve(response.getSubaddress());
});
});
}
/**
* Get user's created offers to buy or sell XMR.
*
* @param {string} direction - one of "BUY" or "SELL"
*
* @return {OfferInfo[]} the user's created offers
*/
async getMyOffers(direction: string): Promise<OfferInfo[]> {
let that = this;
let request = new GetOffersRequest()
.setDirection(direction)
.setCurrencyCode("XMR");
return new Promise(function(resolve, reject) {
that._offersClient.getMyOffers(request, {password: that._password}, function(err: grpcWeb.Error, response: GetOffersReply) {
if (err) reject(err);
else resolve(response.getOffersList());
}); });
}); });
} }
@ -145,6 +120,40 @@ class HavenoDaemon {
}); });
} }
/**
* Get available offers to buy or sell XMR.
*
* @param {string} direction - one of "BUY" or "SELL"
*
* @return {OfferInfo[]} available offers
*/
async getOffers(direction: string): Promise<OfferInfo[]> {
let that = this;
return new Promise(function(resolve, reject) {
that._offersClient.getOffers(new GetOffersRequest().setDirection(direction).setCurrencyCode("XMR"), {password: that._password}, function(err: grpcWeb.Error, response: GetOffersReply) {
if (err) reject(err);
else resolve(response.getOffersList());
});
});
}
/**
* Get user's created offers to buy or sell XMR.
*
* @param {string} direction - one of "BUY" or "SELL"
*
* @return {OfferInfo[]} the user's created offers
*/
async getMyOffers(direction: string): Promise<OfferInfo[]> {
let that = this;
return new Promise(function(resolve, reject) {
that._offersClient.getMyOffers(new GetOffersRequest().setDirection(direction).setCurrencyCode("XMR"), {password: that._password}, function(err: grpcWeb.Error, response: GetOffersReply) {
if (err) reject(err);
else resolve(response.getOffersList());
});
});
}
/** /**
* Post an offer. * Post an offer.
* *
@ -158,7 +167,7 @@ class HavenoDaemon {
* @param {number} buyerSecurityDeposit - buyer security deposit as % of trade amount * @param {number} buyerSecurityDeposit - buyer security deposit as % of trade amount
* @param {string} paymentAccountId - payment account id * @param {string} paymentAccountId - payment account id
* @param {number} triggerPrice - price to remove offer * @param {number} triggerPrice - price to remove offer
* @return {HavenoOffer[]} created offers * @return {OfferInfo} the created offer
*/ */
async postOffer(currencyCode: string, async postOffer(currencyCode: string,
direction: string, direction: string,
@ -194,12 +203,80 @@ class HavenoDaemon {
/** /**
* Remove a posted offer, releasing its reserved funds. * Remove a posted offer, releasing its reserved funds.
* *
* @param {string} id - the offer id to cancel * @param {string} offerId - the offer id to cancel
*/ */
async removeOffer(id: string): Promise<void> { async removeOffer(offerId: string): Promise<void> {
let that = this; let that = this;
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
that._offersClient.cancelOffer(new CancelOfferRequest().setId(id), {password: that._password}, function(err: grpcWeb.Error) { that._offersClient.cancelOffer(new CancelOfferRequest().setId(offerId), {password: that._password}, function(err: grpcWeb.Error) {
if (err) reject(err);
else resolve();
});
});
}
/**
* Take an offer.
*
* @param {string} offerId - id of the offer to take
* @param {string} paymentAccountId - id of the payment account
* @return {TradeInfo} the initialized trade
*/
async takeOffer(offerId: string, paymentAccountId: string): Promise<TradeInfo> {
let that = this;
let request = new TakeOfferRequest()
.setOfferId(offerId)
.setPaymentAccountId(paymentAccountId)
.setTakerFeeCurrencyCode("XMR");
return new Promise(function(resolve, reject) {
that._tradesClient.takeOffer(request, {password: that._password}, function(err: grpcWeb.Error, response: TakeOfferReply) {
if (err) reject(err);
else if (response.getFailureReason() && response.getFailureReason()!.getAvailabilityResult() !== AvailabilityResult.AVAILABLE) reject(response.getFailureReason()!.getDescription());
else resolve(response.getTrade());
});
});
}
/**
* Get a trade by id.
*
* @param {string} tradeId - the id of the trade and its offer
* @return {TradeInfo} the trade with the given id
*/
async getTrade(tradeId: string): Promise<TradeInfo> {
let that = this;
return new Promise(function(resolve, reject) {
that._tradesClient.getTrade(new GetTradeRequest().setTradeId(tradeId), {password: that._password}, function(err: grpcWeb.Error, response: GetTradeReply) {
if (err) reject(err);
else resolve(response.getTrade());
});
});
}
/**
* Confirm a payment is started.
*
* @param {string} tradeId - the id of the trade
*/
async confirmPaymentStarted(tradeId: string): Promise<void> {
let that = this;
return new Promise(function(resolve, reject) {
that._tradesClient.confirmPaymentStarted(new ConfirmPaymentStartedRequest().setTradeId(tradeId), {password: that._password}, function(err: grpcWeb.Error) {
if (err) reject(err);
else resolve();
});
});
}
/**
* Confirm a payment is received.
*
* @param {string} tradeId - the id of the trade
*/
async confirmPaymentReceived(tradeId: string): Promise<void> {
let that = this;
return new Promise(function(resolve, reject) {
that._tradesClient.confirmPaymentReceived(new ConfirmPaymentReceivedRequest().setTradeId(tradeId), {password: that._password}, function(err: grpcWeb.Error) {
if (err) reject(err); if (err) reject(err);
else resolve(); else resolve();
}); });

View File

@ -1163,6 +1163,46 @@ export class WalletsClient {
this.methodInfoGetBalances); this.methodInfoGetBalances);
} }
methodInfoGetNewDepositSubaddress = new grpcWeb.AbstractClientBase.MethodInfo(
grpc_pb.GetNewDepositSubaddressReply,
(request: grpc_pb.GetNewDepositSubaddressRequest) => {
return request.serializeBinary();
},
grpc_pb.GetNewDepositSubaddressReply.deserializeBinary
);
getNewDepositSubaddress(
request: grpc_pb.GetNewDepositSubaddressRequest,
metadata: grpcWeb.Metadata | null): Promise<grpc_pb.GetNewDepositSubaddressReply>;
getNewDepositSubaddress(
request: grpc_pb.GetNewDepositSubaddressRequest,
metadata: grpcWeb.Metadata | null,
callback: (err: grpcWeb.Error,
response: grpc_pb.GetNewDepositSubaddressReply) => void): grpcWeb.ClientReadableStream<grpc_pb.GetNewDepositSubaddressReply>;
getNewDepositSubaddress(
request: grpc_pb.GetNewDepositSubaddressRequest,
metadata: grpcWeb.Metadata | null,
callback?: (err: grpcWeb.Error,
response: grpc_pb.GetNewDepositSubaddressReply) => void) {
if (callback !== undefined) {
return this.client_.rpcCall(
this.hostname_ +
'/io.bisq.protobuffer.Wallets/GetNewDepositSubaddress',
request,
metadata || {},
this.methodInfoGetNewDepositSubaddress,
callback);
}
return this.client_.unaryCall(
this.hostname_ +
'/io.bisq.protobuffer.Wallets/GetNewDepositSubaddress',
request,
metadata || {},
this.methodInfoGetNewDepositSubaddress);
}
methodInfoGetAddressBalance = new grpcWeb.AbstractClientBase.MethodInfo( methodInfoGetAddressBalance = new grpcWeb.AbstractClientBase.MethodInfo(
grpc_pb.GetAddressBalanceReply, grpc_pb.GetAddressBalanceReply,
(request: grpc_pb.GetAddressBalanceRequest) => { (request: grpc_pb.GetAddressBalanceRequest) => {

View File

@ -1343,6 +1343,38 @@ export namespace GetBalancesReply {
} }
} }
export class GetNewDepositSubaddressRequest extends jspb.Message {
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): GetNewDepositSubaddressRequest.AsObject;
static toObject(includeInstance: boolean, msg: GetNewDepositSubaddressRequest): GetNewDepositSubaddressRequest.AsObject;
static serializeBinaryToWriter(message: GetNewDepositSubaddressRequest, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): GetNewDepositSubaddressRequest;
static deserializeBinaryFromReader(message: GetNewDepositSubaddressRequest, reader: jspb.BinaryReader): GetNewDepositSubaddressRequest;
}
export namespace GetNewDepositSubaddressRequest {
export type AsObject = {
}
}
export class GetNewDepositSubaddressReply extends jspb.Message {
getSubaddress(): string;
setSubaddress(value: string): GetNewDepositSubaddressReply;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): GetNewDepositSubaddressReply.AsObject;
static toObject(includeInstance: boolean, msg: GetNewDepositSubaddressReply): GetNewDepositSubaddressReply.AsObject;
static serializeBinaryToWriter(message: GetNewDepositSubaddressReply, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): GetNewDepositSubaddressReply;
static deserializeBinaryFromReader(message: GetNewDepositSubaddressReply, reader: jspb.BinaryReader): GetNewDepositSubaddressReply;
}
export namespace GetNewDepositSubaddressReply {
export type AsObject = {
subaddress: string,
}
}
export class GetAddressBalanceRequest extends jspb.Message { export class GetAddressBalanceRequest extends jspb.Message {
getAddress(): string; getAddress(): string;
setAddress(value: string): GetAddressBalanceRequest; setAddress(value: string): GetAddressBalanceRequest;

View File

@ -49,6 +49,8 @@ goog.exportSymbol('proto.io.bisq.protobuffer.GetMyOfferReply', null, global);
goog.exportSymbol('proto.io.bisq.protobuffer.GetMyOfferRequest', null, global); goog.exportSymbol('proto.io.bisq.protobuffer.GetMyOfferRequest', null, global);
goog.exportSymbol('proto.io.bisq.protobuffer.GetMyOffersReply', null, global); goog.exportSymbol('proto.io.bisq.protobuffer.GetMyOffersReply', null, global);
goog.exportSymbol('proto.io.bisq.protobuffer.GetMyOffersRequest', null, global); goog.exportSymbol('proto.io.bisq.protobuffer.GetMyOffersRequest', null, global);
goog.exportSymbol('proto.io.bisq.protobuffer.GetNewDepositSubaddressReply', null, global);
goog.exportSymbol('proto.io.bisq.protobuffer.GetNewDepositSubaddressRequest', null, global);
goog.exportSymbol('proto.io.bisq.protobuffer.GetOfferReply', null, global); goog.exportSymbol('proto.io.bisq.protobuffer.GetOfferReply', null, global);
goog.exportSymbol('proto.io.bisq.protobuffer.GetOfferRequest', null, global); goog.exportSymbol('proto.io.bisq.protobuffer.GetOfferRequest', null, global);
goog.exportSymbol('proto.io.bisq.protobuffer.GetOffersReply', null, global); goog.exportSymbol('proto.io.bisq.protobuffer.GetOffersReply', null, global);
@ -1262,6 +1264,48 @@ if (goog.DEBUG && !COMPILED) {
*/ */
proto.io.bisq.protobuffer.GetBalancesReply.displayName = 'proto.io.bisq.protobuffer.GetBalancesReply'; proto.io.bisq.protobuffer.GetBalancesReply.displayName = 'proto.io.bisq.protobuffer.GetBalancesReply';
} }
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.io.bisq.protobuffer.GetNewDepositSubaddressRequest = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.io.bisq.protobuffer.GetNewDepositSubaddressRequest, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.io.bisq.protobuffer.GetNewDepositSubaddressRequest.displayName = 'proto.io.bisq.protobuffer.GetNewDepositSubaddressRequest';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.io.bisq.protobuffer.GetNewDepositSubaddressReply = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.io.bisq.protobuffer.GetNewDepositSubaddressReply, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.io.bisq.protobuffer.GetNewDepositSubaddressReply.displayName = 'proto.io.bisq.protobuffer.GetNewDepositSubaddressReply';
}
/** /**
* Generated by JsPbCodeGenerator. * Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a * @param {Array=} opt_data Optional initial data array, typically from a
@ -11955,6 +11999,237 @@ proto.io.bisq.protobuffer.GetBalancesReply.prototype.hasBalances = function() {
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.io.bisq.protobuffer.GetNewDepositSubaddressRequest.prototype.toObject = function(opt_includeInstance) {
return proto.io.bisq.protobuffer.GetNewDepositSubaddressRequest.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.io.bisq.protobuffer.GetNewDepositSubaddressRequest} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.io.bisq.protobuffer.GetNewDepositSubaddressRequest.toObject = function(includeInstance, msg) {
var f, obj = {
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.io.bisq.protobuffer.GetNewDepositSubaddressRequest}
*/
proto.io.bisq.protobuffer.GetNewDepositSubaddressRequest.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.io.bisq.protobuffer.GetNewDepositSubaddressRequest;
return proto.io.bisq.protobuffer.GetNewDepositSubaddressRequest.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.io.bisq.protobuffer.GetNewDepositSubaddressRequest} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.io.bisq.protobuffer.GetNewDepositSubaddressRequest}
*/
proto.io.bisq.protobuffer.GetNewDepositSubaddressRequest.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.io.bisq.protobuffer.GetNewDepositSubaddressRequest.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.io.bisq.protobuffer.GetNewDepositSubaddressRequest.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.io.bisq.protobuffer.GetNewDepositSubaddressRequest} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.io.bisq.protobuffer.GetNewDepositSubaddressRequest.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
};
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.io.bisq.protobuffer.GetNewDepositSubaddressReply.prototype.toObject = function(opt_includeInstance) {
return proto.io.bisq.protobuffer.GetNewDepositSubaddressReply.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.io.bisq.protobuffer.GetNewDepositSubaddressReply} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.io.bisq.protobuffer.GetNewDepositSubaddressReply.toObject = function(includeInstance, msg) {
var f, obj = {
subaddress: jspb.Message.getFieldWithDefault(msg, 1, "")
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.io.bisq.protobuffer.GetNewDepositSubaddressReply}
*/
proto.io.bisq.protobuffer.GetNewDepositSubaddressReply.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.io.bisq.protobuffer.GetNewDepositSubaddressReply;
return proto.io.bisq.protobuffer.GetNewDepositSubaddressReply.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.io.bisq.protobuffer.GetNewDepositSubaddressReply} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.io.bisq.protobuffer.GetNewDepositSubaddressReply}
*/
proto.io.bisq.protobuffer.GetNewDepositSubaddressReply.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {string} */ (reader.readString());
msg.setSubaddress(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.io.bisq.protobuffer.GetNewDepositSubaddressReply.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.io.bisq.protobuffer.GetNewDepositSubaddressReply.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.io.bisq.protobuffer.GetNewDepositSubaddressReply} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.io.bisq.protobuffer.GetNewDepositSubaddressReply.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getSubaddress();
if (f.length > 0) {
writer.writeString(
1,
f
);
}
};
/**
* optional string subaddress = 1;
* @return {string}
*/
proto.io.bisq.protobuffer.GetNewDepositSubaddressReply.prototype.getSubaddress = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
};
/**
* @param {string} value
* @return {!proto.io.bisq.protobuffer.GetNewDepositSubaddressReply} returns this
*/
proto.io.bisq.protobuffer.GetNewDepositSubaddressReply.prototype.setSubaddress = function(value) {
return jspb.Message.setProto3StringField(this, 1, value);
};
if (jspb.Message.GENERATE_TO_OBJECT) { if (jspb.Message.GENERATE_TO_OBJECT) {
/** /**
* Creates an object representation of this proto. * Creates an object representation of this proto.