2022-01-15 15:43:10 -05:00
|
|
|
import {HavenoUtils} from "./utils/HavenoUtils";
|
|
|
|
import {TaskLooper} from "./utils/TaskLooper";
|
2021-09-14 08:27:45 -04:00
|
|
|
import * as grpcWeb from 'grpc-web';
|
2022-02-09 01:41:00 -08:00
|
|
|
import {GetVersionClient, AccountClient, MoneroConnectionsClient, DisputeAgentsClient, NotificationsClient, WalletsClient, PriceClient, OffersClient, PaymentAccountsClient, TradesClient, ShutdownServerClient} from './protobuf/GrpcServiceClientPb';
|
2022-02-11 17:13:56 -06:00
|
|
|
import {GetVersionRequest, GetVersionReply, IsAppInitializedRequest, IsAppInitializedReply, RegisterDisputeAgentRequest, MarketPriceRequest, MarketPriceReply, MarketPricesRequest, MarketPricesReply, MarketPriceInfo, MarketDepthRequest, MarketDepthReply, MarketDepthInfo, GetBalancesRequest, GetBalancesReply, XmrBalanceInfo, GetOffersRequest, GetOffersReply, OfferInfo, GetPaymentAccountsRequest, GetPaymentAccountsReply, CreateCryptoCurrencyPaymentAccountRequest, CreateCryptoCurrencyPaymentAccountReply, CreateOfferRequest, CreateOfferReply, CancelOfferRequest, TakeOfferRequest, TakeOfferReply, TradeInfo, GetTradeRequest, GetTradeReply, GetTradesRequest, GetTradesReply, GetNewDepositSubaddressRequest, GetNewDepositSubaddressReply, ConfirmPaymentStartedRequest, ConfirmPaymentReceivedRequest, XmrTx, GetXmrTxsRequest, GetXmrTxsReply, XmrDestination, CreateXmrTxRequest, CreateXmrTxReply, RelayXmrTxRequest, RelayXmrTxReply, CreateAccountRequest, AccountExistsRequest, AccountExistsReply, DeleteAccountRequest, OpenAccountRequest, IsAccountOpenRequest, IsAccountOpenReply, CloseAccountRequest, ChangePasswordRequest, BackupAccountRequest, BackupAccountReply, RestoreAccountRequest, StopRequest, NotificationMessage, RegisterNotificationListenerRequest, SendNotificationRequest, UrlConnection, AddConnectionRequest, RemoveConnectionRequest, GetConnectionRequest, GetConnectionsRequest, SetConnectionRequest, CheckConnectionRequest, CheckConnectionsReply, CheckConnectionsRequest, StartCheckingConnectionsRequest, StopCheckingConnectionsRequest, GetBestAvailableConnectionRequest, SetAutoSwitchRequest, CheckConnectionReply, GetConnectionsReply, GetConnectionReply, GetBestAvailableConnectionReply} from './protobuf/grpc_pb';
|
2022-02-09 01:41:00 -08:00
|
|
|
import {PaymentAccount, AvailabilityResult} from './protobuf/pb_pb';
|
2021-12-08 06:22:36 -05:00
|
|
|
const console = require('console');
|
2021-09-12 09:39:21 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Haveno daemon client using gRPC.
|
|
|
|
*/
|
|
|
|
class HavenoDaemon {
|
|
|
|
|
2022-01-15 15:43:10 -05:00
|
|
|
// grpc clients
|
2022-02-09 01:41:00 -08:00
|
|
|
_appName: string|undefined;
|
2021-09-14 08:27:45 -04:00
|
|
|
_getVersionClient: GetVersionClient;
|
2022-01-08 05:21:32 -08:00
|
|
|
_disputeAgentsClient: DisputeAgentsClient;
|
2022-01-09 17:02:43 +01:00
|
|
|
_notificationsClient: NotificationsClient;
|
2022-01-24 19:37:18 +01:00
|
|
|
_moneroConnectionsClient: MoneroConnectionsClient;
|
2021-09-14 08:27:45 -04:00
|
|
|
_walletsClient: WalletsClient;
|
2022-01-24 19:37:18 +01:00
|
|
|
_priceClient: PriceClient;
|
2021-09-14 08:27:45 -04:00
|
|
|
_paymentAccountsClient: PaymentAccountsClient;
|
2021-09-19 14:00:22 -04:00
|
|
|
_offersClient: OffersClient;
|
|
|
|
_tradesClient: TradesClient;
|
2022-02-09 01:41:00 -08:00
|
|
|
_accountClient: AccountClient;
|
|
|
|
_shutdownServerClient: ShutdownServerClient;
|
2021-09-12 09:39:21 -04:00
|
|
|
|
2022-02-09 01:41:00 -08:00
|
|
|
// state variables
|
2022-01-15 15:43:10 -05:00
|
|
|
_url: string;
|
|
|
|
_password: string;
|
|
|
|
_process: any;
|
2022-02-09 01:41:00 -08:00
|
|
|
_processLogging = false;
|
2022-01-15 15:43:10 -05:00
|
|
|
_walletRpcPort: number|undefined;
|
|
|
|
_notificationListeners: ((notification: NotificationMessage) => void)[] = [];
|
2022-02-09 01:41:00 -08:00
|
|
|
_registerNotificationListenerCalled = false;
|
|
|
|
_keepAliveLooper: any;
|
2022-01-15 15:43:10 -05:00
|
|
|
_keepAlivePeriodMs: number = 60000;
|
2022-02-09 01:41:00 -08:00
|
|
|
|
|
|
|
// constants
|
|
|
|
static readonly _fullyInitializedMessage = "AppStartupState: Application fully initialized";
|
|
|
|
static readonly _loginRequiredMessage = "HavenoDaemonMain: Interactive login required";
|
|
|
|
|
2021-09-12 09:39:21 -04:00
|
|
|
/**
|
|
|
|
* Construct a client connected to a Haveno daemon.
|
|
|
|
*
|
|
|
|
* @param {string} url - Haveno daemon url
|
2021-12-08 06:22:36 -05:00
|
|
|
* @param {string} password - Haveno daemon password
|
2021-09-12 09:39:21 -04:00
|
|
|
*/
|
2021-09-14 08:27:45 -04:00
|
|
|
constructor(url: string, password: string) {
|
2021-12-08 06:22:36 -05:00
|
|
|
if (!url) throw new Error("Must provide URL of Haveno daemon");
|
|
|
|
if (!password) throw new Error("Must provide password of Haveno daemon");
|
2022-02-09 01:41:00 -08:00
|
|
|
HavenoUtils.log(2, "Creating HavenoDaemon(" + url + ", " + password + ")");
|
2021-09-12 09:39:21 -04:00
|
|
|
this._url = url;
|
|
|
|
this._password = password;
|
2021-09-14 08:27:45 -04:00
|
|
|
this._getVersionClient = new GetVersionClient(this._url);
|
2022-02-09 01:41:00 -08:00
|
|
|
this._accountClient = new AccountClient(this._url);
|
2022-01-24 19:37:18 +01:00
|
|
|
this._moneroConnectionsClient = new MoneroConnectionsClient(this._url)
|
2022-02-09 01:41:00 -08:00
|
|
|
this._disputeAgentsClient = new DisputeAgentsClient(this._url);
|
2021-09-14 08:27:45 -04:00
|
|
|
this._walletsClient = new WalletsClient(this._url);
|
2022-01-24 19:37:18 +01:00
|
|
|
this._priceClient = new PriceClient(this._url);
|
2021-09-14 08:27:45 -04:00
|
|
|
this._paymentAccountsClient = new PaymentAccountsClient(this._url);
|
2021-09-19 14:00:22 -04:00
|
|
|
this._offersClient = new OffersClient(this._url);
|
|
|
|
this._tradesClient = new TradesClient(this._url);
|
2022-01-09 17:02:43 +01:00
|
|
|
this._notificationsClient = new NotificationsClient(this._url);
|
2022-02-09 01:41:00 -08:00
|
|
|
this._shutdownServerClient = new ShutdownServerClient(this._url);
|
2021-09-12 09:39:21 -04:00
|
|
|
}
|
|
|
|
|
2021-12-08 06:22:36 -05:00
|
|
|
/**
|
|
|
|
* Start a new Haveno process.
|
|
|
|
*
|
|
|
|
* @param {string} havenoPath - path to Haveno binaries
|
|
|
|
* @param {string[]} cmd - command to start the process
|
|
|
|
* @param {string} url - Haveno daemon url (must proxy to api port)
|
2021-12-14 13:04:02 -05:00
|
|
|
* @param {boolean} enableLogging - specifies if logging is enabled or disabled at log level 3
|
2021-12-08 06:22:36 -05:00
|
|
|
* @return {HavenoDaemon} a client connected to the newly started Haveno process
|
|
|
|
*/
|
2021-12-14 13:04:02 -05:00
|
|
|
static async startProcess(havenoPath: string, cmd: string[], url: string, enableLogging: boolean): Promise<HavenoDaemon> {
|
2021-12-08 06:22:36 -05:00
|
|
|
|
|
|
|
// return promise which resolves after starting havenod
|
|
|
|
return new Promise(function(resolve, reject) {
|
2021-12-14 13:04:02 -05:00
|
|
|
HavenoUtils.log(2, "Starting Haveno process: " + cmd + " on proxy url: " + url);
|
|
|
|
|
|
|
|
// state variables
|
|
|
|
let output = "";
|
2022-02-09 01:41:00 -08:00
|
|
|
let isStarted = false;
|
2021-12-14 13:04:02 -05:00
|
|
|
let daemon: HavenoDaemon|undefined = undefined;
|
|
|
|
|
|
|
|
// start process
|
|
|
|
let childProcess = require('child_process').spawn(cmd[0], cmd.slice(1), {cwd: havenoPath});
|
|
|
|
childProcess.stdout.setEncoding('utf8');
|
|
|
|
childProcess.stderr.setEncoding('utf8');
|
|
|
|
|
2021-12-08 06:22:36 -05:00
|
|
|
// handle stdout
|
2021-12-14 13:04:02 -05:00
|
|
|
childProcess.stdout.on('data', async function(data: any) {
|
2021-12-08 06:22:36 -05:00
|
|
|
let line = data.toString();
|
2022-02-09 01:41:00 -08:00
|
|
|
if (loggingEnabled()) process.stdout.write(line);
|
2021-12-08 06:22:36 -05:00
|
|
|
output += line + '\n'; // capture output in case of error
|
|
|
|
|
2022-02-09 01:41:00 -08:00
|
|
|
// initialize daemon on success or login required message
|
|
|
|
if (!daemon && (line.indexOf(HavenoDaemon._fullyInitializedMessage) >= 0 || line.indexOf(HavenoDaemon._loginRequiredMessage) >= 0)) {
|
2021-12-08 06:22:36 -05:00
|
|
|
|
|
|
|
// get api password
|
|
|
|
let passwordIdx = cmd.indexOf("--apiPassword");
|
|
|
|
if (passwordIdx < 0) {
|
|
|
|
reject("Must provide API password to start Haveno daemon");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let password = cmd[passwordIdx + 1];
|
2021-12-14 13:04:02 -05:00
|
|
|
|
2021-12-08 06:22:36 -05:00
|
|
|
// create client connected to internal process
|
2021-12-14 13:04:02 -05:00
|
|
|
daemon = new HavenoDaemon(url, password);
|
|
|
|
daemon._process = childProcess;
|
|
|
|
daemon._processLogging = enableLogging;
|
2022-01-24 19:37:18 +01:00
|
|
|
daemon._appName = cmd[cmd.indexOf("--appName") + 1];
|
|
|
|
|
2021-12-14 13:04:02 -05:00
|
|
|
// get wallet rpc port
|
|
|
|
let walletRpcPortIdx = cmd.indexOf("--walletRpcBindPort");
|
|
|
|
if (walletRpcPortIdx >= 0) daemon._walletRpcPort = parseInt(cmd[walletRpcPortIdx + 1]);
|
2021-12-08 06:22:36 -05:00
|
|
|
|
|
|
|
// resolve promise with client connected to internal process
|
2022-02-09 01:41:00 -08:00
|
|
|
isStarted = true;
|
2021-12-08 06:22:36 -05:00
|
|
|
resolve(daemon);
|
|
|
|
}
|
2021-12-16 20:10:40 -05:00
|
|
|
|
|
|
|
// read error message
|
|
|
|
if (line.indexOf("[HavenoDaemonMain] ERROR") >= 0) {
|
2022-02-09 01:41:00 -08:00
|
|
|
if (!isStarted) await rejectStartup(new Error(line));
|
2021-12-16 20:10:40 -05:00
|
|
|
}
|
2021-12-08 06:22:36 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
// handle stderr
|
2021-12-14 13:04:02 -05:00
|
|
|
childProcess.stderr.on('data', function(data: any) {
|
2022-02-09 01:41:00 -08:00
|
|
|
if (loggingEnabled()) process.stderr.write(data);
|
2021-12-08 06:22:36 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
// handle exit
|
2021-12-16 20:10:40 -05:00
|
|
|
childProcess.on("exit", async function(code: any) {
|
2022-02-09 01:41:00 -08:00
|
|
|
if (!isStarted) await rejectStartup(new Error("Haveno process terminated with exit code " + code + (output ? ":\n\n" + output : "")));
|
2021-12-08 06:22:36 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
// handle error
|
2021-12-16 20:10:40 -05:00
|
|
|
childProcess.on("error", async function(err: any) {
|
2021-12-08 06:22:36 -05:00
|
|
|
if (err.message.indexOf("ENOENT") >= 0) reject(new Error("haveno-daemon does not exist at path '" + cmd[0] + "'"));
|
2022-02-09 01:41:00 -08:00
|
|
|
if (!isStarted) await rejectStartup(err);
|
2021-12-08 06:22:36 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
// handle uncaught exception
|
2021-12-16 20:10:40 -05:00
|
|
|
childProcess.on("uncaughtException", async function(err: any, origin: any) {
|
2021-12-08 06:22:36 -05:00
|
|
|
console.error("Uncaught exception in Haveno process: " + err.message);
|
|
|
|
console.error(origin);
|
2022-02-09 01:41:00 -08:00
|
|
|
await rejectStartup(err);
|
2021-12-08 06:22:36 -05:00
|
|
|
});
|
2021-12-14 13:04:02 -05:00
|
|
|
|
2022-02-09 01:41:00 -08:00
|
|
|
async function rejectStartup(err: any) {
|
2021-12-16 20:10:40 -05:00
|
|
|
await HavenoUtils.kill(childProcess);
|
|
|
|
reject(err);
|
|
|
|
}
|
|
|
|
|
2021-12-14 13:04:02 -05:00
|
|
|
function loggingEnabled(): boolean {
|
|
|
|
return (daemon && daemon._processLogging) || (!daemon && enableLogging);
|
|
|
|
}
|
2021-12-08 06:22:36 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-12-16 20:10:40 -05:00
|
|
|
/**
|
|
|
|
* Return the process running the haveno daemon.
|
|
|
|
*
|
|
|
|
* @return the process running the haveno daemon
|
|
|
|
*/
|
|
|
|
getProcess() {
|
|
|
|
return this._process;
|
2021-12-08 06:22:36 -05:00
|
|
|
}
|
|
|
|
|
2021-12-14 13:04:02 -05:00
|
|
|
/**
|
|
|
|
* Enable or disable process logging.
|
|
|
|
*
|
|
|
|
* @param {boolean} enabled - specifies if logging is enabled or disabled
|
|
|
|
*/
|
|
|
|
setProcessLogging(enabled: boolean) {
|
|
|
|
if (this._process === undefined) throw new Error("HavenoDaemon instance not created from new process");
|
|
|
|
this._processLogging = enabled;
|
|
|
|
}
|
|
|
|
|
2021-12-08 06:22:36 -05:00
|
|
|
/**
|
|
|
|
* Get the URL of the Haveno daemon.
|
|
|
|
*
|
|
|
|
* @return {string} the URL of the Haveno daemon
|
|
|
|
*/
|
|
|
|
getUrl(): string {
|
|
|
|
return this._url;
|
|
|
|
}
|
|
|
|
|
2021-12-14 13:04:02 -05:00
|
|
|
/**
|
|
|
|
* Get the port of the primary wallet rpc instance if known.
|
|
|
|
*
|
|
|
|
* @return {number|undefined} the port of the primary wallet rpc instance if known
|
|
|
|
*/
|
|
|
|
getWalletRpcPort(): number|undefined {
|
|
|
|
return this._walletRpcPort;
|
|
|
|
}
|
|
|
|
|
2022-01-24 19:37:18 +01:00
|
|
|
/**
|
|
|
|
* Get the name of the Haveno application folder.
|
|
|
|
*/
|
|
|
|
getAppName(): string|undefined {
|
|
|
|
return this._appName;
|
|
|
|
}
|
2022-02-11 17:13:56 -06:00
|
|
|
|
2021-09-12 09:39:21 -04:00
|
|
|
/**
|
|
|
|
* Get the Haveno version.
|
|
|
|
*
|
2022-02-11 17:13:56 -06:00
|
|
|
* @return {string} the Haveno daemon version
|
2021-09-12 09:39:21 -04:00
|
|
|
*/
|
|
|
|
async getVersion(): Promise<string> {
|
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
2021-11-11 13:48:31 -05:00
|
|
|
that._getVersionClient.getVersion(new GetVersionRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: GetVersionReply) {
|
2021-09-12 09:39:21 -04:00
|
|
|
if (err) reject(err);
|
|
|
|
else resolve(response.getVersion());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-01-08 05:21:32 -08:00
|
|
|
/**
|
2022-02-09 01:41:00 -08:00
|
|
|
* Indicates if connected and authenticated with the Haveno daemon.
|
2022-01-08 05:21:32 -08:00
|
|
|
*
|
2022-02-09 01:41:00 -08:00
|
|
|
* @return {boolean} true if connected with the Haveno daemon, false otherwise
|
2022-01-08 05:21:32 -08:00
|
|
|
*/
|
2022-02-09 01:41:00 -08:00
|
|
|
async isConnectedToDaemon(): Promise<boolean> {
|
|
|
|
try {
|
|
|
|
await this.getVersion();
|
|
|
|
return true;
|
|
|
|
} catch (err) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Indicates if the Haveno account is created.
|
|
|
|
*
|
|
|
|
* @return {boolean} true if the account is created, false otherwise
|
|
|
|
*/
|
|
|
|
async accountExists(): Promise<boolean> {
|
2022-01-08 05:21:32 -08:00
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
2022-02-09 01:41:00 -08:00
|
|
|
that._accountClient.accountExists(new AccountExistsRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: AccountExistsReply) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve(response.getAccountExists());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Indicates if the Haveno account is open and authenticated with the correct password.
|
|
|
|
*
|
|
|
|
* @return {boolean} true if the account is open and authenticated, false otherwise
|
|
|
|
*/
|
|
|
|
async isAccountOpen(): Promise<boolean> {
|
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._accountClient.isAccountOpen(new IsAccountOpenRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: IsAccountOpenReply) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve(response.getIsAccountOpen());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create and open a new Haveno account.
|
|
|
|
*
|
|
|
|
* @param {string} password - the password to encrypt the account
|
|
|
|
*/
|
|
|
|
async createAccount(password: string): Promise<void> {
|
|
|
|
let that = this;
|
|
|
|
await new Promise(function(resolve, reject) {
|
|
|
|
that._accountClient.createAccount(new CreateAccountRequest().setPassword(password), {password: that._password}, function(err: grpcWeb.RpcError) {
|
2022-01-08 05:21:32 -08:00
|
|
|
if (err) reject(err);
|
|
|
|
else resolve();
|
|
|
|
});
|
|
|
|
});
|
2022-02-09 01:41:00 -08:00
|
|
|
return this._awaitAppInitialized(); // TODO: grpc should not return before setup is complete
|
2022-01-08 05:21:32 -08:00
|
|
|
}
|
|
|
|
|
2022-01-09 17:02:43 +01:00
|
|
|
/**
|
2022-02-09 01:41:00 -08:00
|
|
|
* Open existing Haveno account.
|
|
|
|
*
|
|
|
|
* @param {string} password - the account password
|
|
|
|
*/
|
|
|
|
async openAccount(password: string): Promise<void> {
|
|
|
|
let that = this;
|
|
|
|
await new Promise(function(resolve, reject) {
|
|
|
|
that._accountClient.openAccount(new OpenAccountRequest().setPassword(password), {password: that._password}, function(err: grpcWeb.RpcError) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
return this._awaitAppInitialized(); // TODO: grpc should not return before setup is complete
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Change the Haveno account password.
|
|
|
|
*
|
|
|
|
* @param {string} password - the new account password
|
|
|
|
*/
|
|
|
|
async changePassword(password: string): Promise<void> {
|
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._accountClient.changePassword(new ChangePasswordRequest().setPassword(password), {password: that._password}, function(err: grpcWeb.RpcError) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Close the currently open account.
|
|
|
|
*/
|
|
|
|
async closeAccount(): Promise<void> {
|
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._accountClient.closeAccount(new CloseAccountRequest(), {password: that._password}, function(err: grpcWeb.RpcError) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Permanently delete the Haveno account and shutdown the server. // TODO: possible to not shutdown server?
|
|
|
|
*/
|
|
|
|
async deleteAccount(): Promise<void> {
|
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._accountClient.deleteAccount(new DeleteAccountRequest(), {password: that._password}, async function(err: grpcWeb.RpcError) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else setTimeout(resolve, 5000);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Backup the account to the given stream. TODO: stream type?
|
|
|
|
*/
|
|
|
|
async backupAccount(stream: any): Promise<number> {
|
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
let total = 0;
|
|
|
|
let response = that._accountClient.backupAccount(new BackupAccountRequest(), {password: that._password});
|
|
|
|
response.on('data', (chunk) => {
|
|
|
|
let bytes = (chunk as BackupAccountReply).getZipBytes(); // TODO: right api?
|
|
|
|
total += bytes.length;
|
|
|
|
stream.write(bytes);
|
|
|
|
});
|
|
|
|
response.on('error', function(err) {
|
|
|
|
if(err) reject(err);
|
|
|
|
});
|
|
|
|
response.on('end', function() {
|
|
|
|
resolve(total);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Restore the account from zip bytes.
|
|
|
|
*
|
|
|
|
* Sends chunked requests if size over max grpc envelope size (41943404 bytes).
|
2022-01-09 17:02:43 +01:00
|
|
|
*
|
2022-02-09 01:41:00 -08:00
|
|
|
* @param {Uint8Array} zipBytes - the bytes of the zipped account to restore
|
|
|
|
*/
|
|
|
|
async restoreAccount(zipBytes: Uint8Array): Promise<void> {
|
|
|
|
if (zipBytes.length === 0) throw new Error("Zip bytes must not be empty")
|
|
|
|
let totalLength = zipBytes.byteLength;
|
|
|
|
let offset = 0;
|
|
|
|
let chunkSize = 4000000; // the max frame size is 4194304 but leave room for http headers
|
|
|
|
let hasMore = true;
|
|
|
|
while (true) {
|
|
|
|
if (zipBytes.byteLength <= offset + 1) return;
|
|
|
|
if (zipBytes.byteLength <= offset + chunkSize) {
|
|
|
|
chunkSize = zipBytes.byteLength - offset - 1;
|
|
|
|
hasMore = false;
|
|
|
|
}
|
|
|
|
let subArray = zipBytes.subarray(offset, offset + chunkSize);
|
|
|
|
await this._restoreAccountChunk(subArray, offset, totalLength, hasMore);
|
|
|
|
offset += chunkSize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a listener to receive notifications from the Haveno daemon.
|
|
|
|
*
|
2022-01-09 17:02:43 +01:00
|
|
|
* @param {HavenoDaemonListener} listener - the notification listener to add
|
|
|
|
*/
|
|
|
|
async addNotificationListener(listener: (notification: NotificationMessage) => void): Promise<void> {
|
|
|
|
this._notificationListeners.push(listener);
|
2022-02-09 01:41:00 -08:00
|
|
|
return this._registerNotificationListenerOnce();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove a notification listener.
|
|
|
|
*
|
|
|
|
* @param {HavenoDaemonListener} listener - the notification listener to remove
|
|
|
|
*/
|
|
|
|
async removeNotificationListener(listener: (notification: NotificationMessage) => void): Promise<void> {
|
|
|
|
let idx = this._notificationListeners.indexOf(listener);
|
|
|
|
if (idx > -1) this._notificationListeners.splice(idx, 1);
|
|
|
|
else throw new Error("Notification listener is not registered");
|
2022-01-09 17:02:43 +01:00
|
|
|
}
|
|
|
|
|
2022-02-09 01:41:00 -08:00
|
|
|
/**
|
|
|
|
* Indicates if connected to the Monero network based on last connection check.
|
|
|
|
*
|
|
|
|
* @return {boolean} true if connected to the Monero network, false otherwise
|
|
|
|
*/
|
|
|
|
async isConnectedToMonero(): Promise<boolean> {
|
|
|
|
let connection = await this.getMoneroConnection();
|
|
|
|
return connection !== undefined &&
|
|
|
|
connection.getOnlineStatus()! === UrlConnection.OnlineStatus.ONLINE &&
|
|
|
|
connection.getAuthenticationStatus()! !== UrlConnection.AuthenticationStatus.NOT_AUTHENTICATED;
|
|
|
|
}
|
|
|
|
|
2021-10-22 13:51:57 -04:00
|
|
|
/**
|
2022-01-24 19:37:18 +01:00
|
|
|
* Add a Monero daemon connection.
|
|
|
|
*
|
2022-02-09 01:41:00 -08:00
|
|
|
* @param {string | UrlConnection} connection - daemon url or connection to add
|
2021-10-22 13:51:57 -04:00
|
|
|
*/
|
2022-02-09 01:41:00 -08:00
|
|
|
async addMoneroConnection(connection: string | UrlConnection): Promise<void> {
|
2021-10-22 13:51:57 -04:00
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
2022-02-09 01:41:00 -08:00
|
|
|
that._moneroConnectionsClient.addConnection(new AddConnectionRequest().setConnection(typeof connection === "string" ? new UrlConnection().setUrl(connection) : connection), {password: that._password}, function(err: grpcWeb.RpcError) {
|
2021-10-22 13:51:57 -04:00
|
|
|
if (err) reject(err);
|
2022-01-24 19:37:18 +01:00
|
|
|
else resolve();
|
2021-10-22 13:51:57 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2021-11-19 23:29:44 +01:00
|
|
|
|
|
|
|
/**
|
2022-01-24 19:37:18 +01:00
|
|
|
* Remove a Monero daemon connection.
|
|
|
|
*
|
2022-02-09 01:41:00 -08:00
|
|
|
* @param {string} url - url of the daemon connection to remove
|
2021-11-19 23:29:44 +01:00
|
|
|
*/
|
2022-02-09 01:41:00 -08:00
|
|
|
async removeMoneroConnection(url: string): Promise<void> {
|
2021-11-19 23:29:44 +01:00
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
2022-02-09 01:41:00 -08:00
|
|
|
that._moneroConnectionsClient.removeConnection(new RemoveConnectionRequest().setUrl(url), {password: that._password}, function(err: grpcWeb.RpcError) {
|
2021-11-19 23:29:44 +01:00
|
|
|
if (err) reject(err);
|
2022-01-24 19:37:18 +01:00
|
|
|
else resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the current Monero daemon connection.
|
|
|
|
*
|
2022-02-09 01:41:00 -08:00
|
|
|
* @return {UrlConnection | undefined} the current daemon connection, undefined if no current connection
|
2022-01-24 19:37:18 +01:00
|
|
|
*/
|
2022-02-09 01:41:00 -08:00
|
|
|
async getMoneroConnection(): Promise<UrlConnection | undefined> {
|
2022-01-24 19:37:18 +01:00
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._moneroConnectionsClient.getConnection(new GetConnectionRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: GetConnectionReply) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve(response.getConnection());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get all Monero daemon connections.
|
|
|
|
*
|
2022-02-09 01:41:00 -08:00
|
|
|
* @return {UrlConnection[]} all daemon connections
|
2022-01-24 19:37:18 +01:00
|
|
|
*/
|
2022-02-09 01:41:00 -08:00
|
|
|
async getMoneroConnections(): Promise<UrlConnection[]> {
|
2022-01-24 19:37:18 +01:00
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._moneroConnectionsClient.getConnections(new GetConnectionsRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: GetConnectionsReply) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve(response.getConnectionsList());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the current Monero daemon connection.
|
2022-02-09 01:41:00 -08:00
|
|
|
*
|
2022-01-24 19:37:18 +01:00
|
|
|
* Add the connection if not previously seen.
|
|
|
|
* If the connection is provided as string, connect to the URI with any previously set credentials and priority.
|
2022-02-09 01:41:00 -08:00
|
|
|
* If the connection is provided as UrlConnection, overwrite any previously set credentials and priority.
|
2022-01-24 19:37:18 +01:00
|
|
|
* If undefined connection provided, disconnect the client.
|
|
|
|
*
|
2022-02-09 01:41:00 -08:00
|
|
|
* @param {string | UrlConnection} connection - connection to set as current
|
2022-01-24 19:37:18 +01:00
|
|
|
*/
|
2022-02-09 01:41:00 -08:00
|
|
|
async setMoneroConnection(connection?: string | UrlConnection): Promise<void> {
|
2022-01-24 19:37:18 +01:00
|
|
|
let that = this;
|
|
|
|
let request = new SetConnectionRequest();
|
2022-02-09 01:41:00 -08:00
|
|
|
if (typeof connection === "string") request.setUrl(connection);
|
2022-01-24 19:37:18 +01:00
|
|
|
else request.setConnection(connection);
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._moneroConnectionsClient.setConnection(request, {password: that._password}, function(err: grpcWeb.RpcError) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check the current Monero daemon connection.
|
|
|
|
*
|
|
|
|
* If disconnected and auto switch enabled, switch to the best available connection and return its status.
|
|
|
|
*
|
2022-02-09 01:41:00 -08:00
|
|
|
* @return {UrlConnection | undefined} the current daemon connection status, undefined if no current connection
|
2022-01-24 19:37:18 +01:00
|
|
|
*/
|
2022-02-09 01:41:00 -08:00
|
|
|
async checkMoneroConnection(): Promise<UrlConnection | undefined> {
|
2022-01-24 19:37:18 +01:00
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._moneroConnectionsClient.checkConnection(new CheckConnectionRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: CheckConnectionReply) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve(response.getConnection());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check all Monero daemon connections.
|
|
|
|
*
|
2022-02-09 01:41:00 -08:00
|
|
|
* @return {UrlConnection[]} status of all managed connections.
|
2022-01-24 19:37:18 +01:00
|
|
|
*/
|
2022-02-09 01:41:00 -08:00
|
|
|
async checkMoneroConnections(): Promise<UrlConnection[]> {
|
2022-01-24 19:37:18 +01:00
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._moneroConnectionsClient.checkConnections(new CheckConnectionsRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: CheckConnectionsReply) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve(response.getConnectionsList());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check the connection and start checking the connection periodically.
|
|
|
|
*
|
|
|
|
* @param {number} refreshPeriod - time between checks in milliseconds (default 15000 ms or 15 seconds)
|
|
|
|
*/
|
|
|
|
async startCheckingConnection(refreshPeriod: number): Promise<void> {
|
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._moneroConnectionsClient.startCheckingConnections(new StartCheckingConnectionsRequest().setRefreshPeriod(refreshPeriod), {password: that._password}, function(err: grpcWeb.RpcError) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stop checking the connection status periodically.
|
|
|
|
*/
|
|
|
|
async stopCheckingConnection(): Promise<void> {
|
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._moneroConnectionsClient.stopCheckingConnections(new StopCheckingConnectionsRequest(), {password: that._password}, function(err: grpcWeb.RpcError) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the best available connection in order of priority then response time.
|
|
|
|
*
|
2022-02-09 01:41:00 -08:00
|
|
|
* @return {UrlConnection | undefined} the best available connection in order of priority then response time, undefined if no connections available
|
2022-01-24 19:37:18 +01:00
|
|
|
*/
|
2022-02-09 01:41:00 -08:00
|
|
|
async getBestAvailableConnection(): Promise<UrlConnection | undefined> {
|
2022-01-24 19:37:18 +01:00
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._moneroConnectionsClient.getBestAvailableConnection(new GetBestAvailableConnectionRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: GetBestAvailableConnectionReply) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve(response.getConnection());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2022-02-11 17:13:56 -06:00
|
|
|
|
2022-01-24 19:37:18 +01:00
|
|
|
/**
|
|
|
|
* Automatically switch to the best available connection if current connection is disconnected after being checked.
|
|
|
|
*
|
|
|
|
* @param {boolean} autoSwitch - whether auto switch is enabled or disabled
|
|
|
|
*/
|
|
|
|
async setAutoSwitch(autoSwitch: boolean): Promise<void> {
|
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._moneroConnectionsClient.setAutoSwitch(new SetAutoSwitchRequest().setAutoSwitch(autoSwitch), {password: that._password}, function(err: grpcWeb.RpcError) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve();
|
2021-11-19 23:29:44 +01:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2022-02-09 01:41:00 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Register as a dispute agent.
|
|
|
|
*
|
|
|
|
* @param {string} disputeAgentType - type of dispute agent to register, e.g. mediator, refundagent
|
|
|
|
* @param {string} registrationKey - registration key
|
|
|
|
*/
|
|
|
|
async registerDisputeAgent(disputeAgentType: string, registrationKey: string): Promise<void> {
|
|
|
|
let that = this;
|
|
|
|
let request = new RegisterDisputeAgentRequest()
|
|
|
|
.setDisputeAgentType(disputeAgentType)
|
|
|
|
.setRegistrationKey(registrationKey);
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._disputeAgentsClient.registerDisputeAgent(request, {password: that._password}, function(err: grpcWeb.RpcError) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-09-12 09:39:21 -04:00
|
|
|
/**
|
|
|
|
* Get the user's balances.
|
|
|
|
*
|
2021-09-14 08:27:45 -04:00
|
|
|
* @return {XmrBalanceInfo} the user's balances
|
2021-09-12 09:39:21 -04:00
|
|
|
*/
|
2021-09-14 08:27:45 -04:00
|
|
|
async getBalances(): Promise<XmrBalanceInfo> {
|
2021-09-12 09:39:21 -04:00
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
2021-11-11 13:48:31 -05:00
|
|
|
that._walletsClient.getBalances(new GetBalancesRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: GetBalancesReply) {
|
2021-09-12 09:39:21 -04:00
|
|
|
if (err) reject(err);
|
2021-09-15 08:06:50 -04:00
|
|
|
else resolve(response.getBalances()!.getXmr()!);
|
2021-09-12 09:39:21 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-12-30 22:03:00 +02:00
|
|
|
* Get a new subaddress in the Monero wallet to receive deposits.
|
2021-09-12 09:39:21 -04:00
|
|
|
*
|
2021-09-19 14:00:22 -04:00
|
|
|
* @return {string} the deposit address (a subaddress in the Haveno wallet)
|
2021-09-12 09:39:21 -04:00
|
|
|
*/
|
2021-09-19 14:00:22 -04:00
|
|
|
async getNewDepositSubaddress(): Promise<string> {
|
2021-09-12 09:39:21 -04:00
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
2021-11-11 13:48:31 -05:00
|
|
|
that._walletsClient.getNewDepositSubaddress(new GetNewDepositSubaddressRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: GetNewDepositSubaddressReply) {
|
2021-09-12 09:39:21 -04:00
|
|
|
if (err) reject(err);
|
2021-09-19 14:00:22 -04:00
|
|
|
else resolve(response.getSubaddress());
|
2021-09-14 08:27:45 -04:00
|
|
|
});
|
|
|
|
});
|
2021-09-12 09:39:21 -04:00
|
|
|
}
|
2021-12-30 22:03:00 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get all transactions in the Monero wallet.
|
|
|
|
*
|
|
|
|
* @return {XmrTx[]} the transactions
|
|
|
|
*/
|
|
|
|
async getXmrTxs(): Promise<XmrTx[]> {
|
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._walletsClient.getXmrTxs(new GetXmrTxsRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: GetXmrTxsReply) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve(response.getTxsList());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2021-09-14 08:27:45 -04:00
|
|
|
|
2021-12-30 22:03:00 +02:00
|
|
|
/**
|
|
|
|
* Get a transaction by hash in the Monero wallet.
|
|
|
|
*
|
|
|
|
* @param {String} txHash - hash of the transaction to get
|
|
|
|
* @return {XmrTx} the transaction with the hash
|
|
|
|
*/
|
|
|
|
async getXmrTx(txHash: string): Promise<XmrTx> {
|
|
|
|
let txs = await this.getXmrTxs(); // TODO (woodser): implement getXmrTx(hash) grpc call
|
|
|
|
for (let tx of txs) {
|
|
|
|
if (tx.getHash() === txHash) return tx;
|
|
|
|
}
|
|
|
|
throw new Error("No transaction with hash " + txHash);
|
|
|
|
}
|
2022-02-11 17:13:56 -06:00
|
|
|
|
2021-12-30 22:03:00 +02:00
|
|
|
/**
|
|
|
|
* Create but do not relay a transaction to send funds from the Monero wallet.
|
|
|
|
*
|
|
|
|
* @return {XmrTx} the created transaction
|
|
|
|
*/
|
|
|
|
async createXmrTx(destinations: XmrDestination[]): Promise<XmrTx> {
|
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._walletsClient.createXmrTx(new CreateXmrTxRequest().setDestinationsList(destinations), {password: that._password}, function(err: grpcWeb.RpcError, response: CreateXmrTxReply) {
|
|
|
|
if (err) reject(err);
|
2022-01-26 18:44:47 -08:00
|
|
|
else resolve(response.getTx()!);
|
2021-12-30 22:03:00 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2022-02-11 17:13:56 -06:00
|
|
|
|
2021-12-30 22:03:00 +02:00
|
|
|
/**
|
|
|
|
* Relay a previously created transaction to send funds from the Monero wallet.
|
|
|
|
*
|
|
|
|
* @return {string} the hash of the relayed transaction
|
|
|
|
*/
|
|
|
|
async relayXmrTx(metadata: string): Promise<string> {
|
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._walletsClient.relayXmrTx(new RelayXmrTxRequest().setMetadata(metadata), {password: that._password}, function(err: grpcWeb.RpcError, response: RelayXmrTxReply) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve(response.getHash());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2022-02-11 17:13:56 -06:00
|
|
|
|
2022-01-24 19:37:18 +01:00
|
|
|
/**
|
|
|
|
* Get the current market price per 1 XMR in the given currency.
|
|
|
|
*
|
|
|
|
* @param {string} currencyCode - currency code (fiat or crypto) to get the price of
|
|
|
|
* @return {number} the current market price per 1 XMR in the given currency
|
|
|
|
*/
|
|
|
|
async getPrice(currencyCode: string): Promise<number> {
|
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._priceClient.getMarketPrice(new MarketPriceRequest().setCurrencyCode(currencyCode), {password: that._password}, function(err: grpcWeb.RpcError, response: MarketPriceReply) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve(response.getPrice());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2022-02-11 17:13:56 -06:00
|
|
|
|
2022-01-24 19:37:18 +01:00
|
|
|
/**
|
2022-02-11 17:13:56 -06:00
|
|
|
* Get the current market prices of all currencies.
|
2022-01-24 19:37:18 +01:00
|
|
|
*
|
|
|
|
* @return {MarketPrice[]} price per 1 XMR in all supported currencies (fiat & crypto)
|
|
|
|
*/
|
|
|
|
async getPrices(): Promise<MarketPriceInfo[]> {
|
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._priceClient.getMarketPrices(new MarketPricesRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: MarketPricesReply) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve(response.getMarketPriceList());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2022-02-11 17:13:56 -06:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the market depth of a currency.
|
|
|
|
*
|
|
|
|
* @return {MarketDepthInfo} market depth of the given currency
|
|
|
|
*/
|
|
|
|
async getMarketDepth(currencyCode: string): Promise<MarketDepthInfo> {
|
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._priceClient.getMarketDepth(new MarketDepthRequest().setCurrencyCode(currencyCode), {password: that._password}, function(err: grpcWeb.RpcError, response: MarketDepthReply) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve(response.getMarketDepth());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-09-14 08:27:45 -04:00
|
|
|
/**
|
|
|
|
* Get payment accounts.
|
|
|
|
*
|
|
|
|
* @return {PaymentAccount[]} the payment accounts
|
|
|
|
*/
|
|
|
|
async getPaymentAccounts(): Promise<PaymentAccount[]> {
|
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
2021-11-11 13:48:31 -05:00
|
|
|
that._paymentAccountsClient.getPaymentAccounts(new GetPaymentAccountsRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: GetPaymentAccountsReply) {
|
2021-09-14 08:27:45 -04:00
|
|
|
if (err) reject(err);
|
2021-09-15 08:06:50 -04:00
|
|
|
else resolve(response.getPaymentAccountsList());
|
2021-09-14 08:27:45 -04:00
|
|
|
});
|
|
|
|
});
|
2021-09-12 09:39:21 -04:00
|
|
|
}
|
2021-09-14 08:30:22 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a crypto payment account.
|
|
|
|
*
|
|
|
|
* @param {string} accountName - description of the account
|
|
|
|
* @param {string} currencyCode - currency code of the account
|
|
|
|
* @param {string} address - payment address of the account
|
|
|
|
* @return {PaymentAccount} the created payment account
|
|
|
|
*/
|
|
|
|
async createCryptoPaymentAccount(accountName: string,
|
|
|
|
currencyCode: string,
|
2021-10-22 13:51:57 -04:00
|
|
|
address: string): Promise<PaymentAccount> {
|
2021-09-14 08:30:22 -04:00
|
|
|
let that = this;
|
|
|
|
let request = new CreateCryptoCurrencyPaymentAccountRequest()
|
2021-09-15 08:06:50 -04:00
|
|
|
.setAccountName(accountName)
|
|
|
|
.setCurrencyCode(currencyCode)
|
2021-09-14 08:30:22 -04:00
|
|
|
.setAddress(address)
|
2021-11-10 17:47:30 -05:00
|
|
|
.setTradeInstant(false); // not using instant trades
|
2021-09-14 08:30:22 -04:00
|
|
|
return new Promise(function(resolve, reject) {
|
2021-11-11 13:48:31 -05:00
|
|
|
that._paymentAccountsClient.createCryptoCurrencyPaymentAccount(request, {password: that._password}, function(err: grpcWeb.RpcError, response: CreateCryptoCurrencyPaymentAccountReply) {
|
2021-09-14 08:30:22 -04:00
|
|
|
if (err) reject(err);
|
2021-09-15 08:06:50 -04:00
|
|
|
else resolve(response.getPaymentAccount()!);
|
2021-09-14 08:30:22 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-09-19 14:00:22 -04:00
|
|
|
/**
|
|
|
|
* Get available offers to buy or sell XMR.
|
|
|
|
*
|
2022-02-11 17:13:56 -06:00
|
|
|
* @param {string|undefined} direction - "buy" or "sell" (default all)
|
|
|
|
* @return {OfferInfo[]} the available offers
|
2021-09-19 14:00:22 -04:00
|
|
|
*/
|
2022-02-11 17:13:56 -06:00
|
|
|
async getOffers(direction?: string): Promise<OfferInfo[]> {
|
|
|
|
if (!direction) return (await this.getOffers("buy")).concat(await this.getOffers("sell")); // TODO: implement in backend
|
2021-09-19 14:00:22 -04:00
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
2021-11-11 13:48:31 -05:00
|
|
|
that._offersClient.getOffers(new GetOffersRequest().setDirection(direction).setCurrencyCode("XMR"), {password: that._password}, function(err: grpcWeb.RpcError, response: GetOffersReply) {
|
2021-09-19 14:00:22 -04:00
|
|
|
if (err) reject(err);
|
|
|
|
else resolve(response.getOffersList());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-02-11 17:13:56 -06:00
|
|
|
* Get the user's posted offers to buy or sell XMR.
|
2021-09-19 14:00:22 -04:00
|
|
|
*
|
2022-02-11 17:13:56 -06:00
|
|
|
* @param {string|undefined} direction - "buy" or "sell" (default all)
|
2021-09-19 14:00:22 -04:00
|
|
|
* @return {OfferInfo[]} the user's created offers
|
|
|
|
*/
|
2022-02-11 17:13:56 -06:00
|
|
|
async getMyOffers(direction?: string): Promise<OfferInfo[]> {
|
|
|
|
if (!direction) return (await this.getMyOffers("buy")).concat(await this.getMyOffers("sell")); // TODO: implement in backend
|
2021-09-19 14:00:22 -04:00
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
2021-11-11 13:48:31 -05:00
|
|
|
that._offersClient.getMyOffers(new GetOffersRequest().setDirection(direction).setCurrencyCode("XMR"), {password: that._password}, function(err: grpcWeb.RpcError, response: GetOffersReply) {
|
2021-09-19 14:00:22 -04:00
|
|
|
if (err) reject(err);
|
|
|
|
else resolve(response.getOffersList());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-12-14 13:04:02 -05:00
|
|
|
/**
|
|
|
|
* Get my offer by id.
|
|
|
|
*
|
|
|
|
* @param {string} offerId - id of the user's created offer
|
|
|
|
* @return {OfferInfo} the user's created offer
|
|
|
|
*/
|
|
|
|
async getMyOffer(offerId: string): Promise<OfferInfo> {
|
|
|
|
// TODO: implement this call on the backend
|
|
|
|
let offers = await this.getMyOffers("buy");
|
|
|
|
for (let offer of offers) if (offer.getId() === offerId) return offer;
|
|
|
|
offers = await this.getMyOffers("sell");
|
|
|
|
for (let offer of offers) if (offer.getId() === offerId) return offer;
|
|
|
|
throw new Error("No offer with id: " + offerId);
|
|
|
|
}
|
|
|
|
|
2021-09-14 08:30:22 -04:00
|
|
|
/**
|
|
|
|
* Post an offer.
|
|
|
|
*
|
|
|
|
* @param {string} currencyCode - currency code of traded pair
|
|
|
|
* @param {string} direction - one of "BUY" or "SELL"
|
|
|
|
* @param {number} price - trade price
|
2022-02-11 17:13:56 -06:00
|
|
|
* @param {bool} useMarketBasedPrice - base trade on market price // TODO: this field redundant with price
|
2021-09-14 08:30:22 -04:00
|
|
|
* @param {number} marketPriceMargin - % from market price to tolerate
|
|
|
|
* @param {bigint} amount - amount to trade
|
|
|
|
* @param {bigint} minAmount - minimum amount to trade
|
|
|
|
* @param {number} buyerSecurityDeposit - buyer security deposit as % of trade amount
|
|
|
|
* @param {string} paymentAccountId - payment account id
|
|
|
|
* @param {number} triggerPrice - price to remove offer
|
2021-12-08 06:22:36 -05:00
|
|
|
* @return {OfferInfo} the posted offer
|
2021-09-14 08:30:22 -04:00
|
|
|
*/
|
|
|
|
async postOffer(currencyCode: string,
|
|
|
|
direction: string,
|
|
|
|
price: number,
|
|
|
|
useMarketBasedPrice: boolean,
|
|
|
|
marketPriceMargin: number,
|
|
|
|
amount: bigint,
|
|
|
|
minAmount: bigint,
|
|
|
|
buyerSecurityDeposit: number,
|
|
|
|
paymentAccountId: string,
|
|
|
|
triggerPrice?: number): Promise<OfferInfo> {
|
|
|
|
let that = this;
|
|
|
|
let request = new CreateOfferRequest()
|
2021-09-15 08:06:50 -04:00
|
|
|
.setCurrencyCode(currencyCode)
|
2021-09-14 08:30:22 -04:00
|
|
|
.setDirection(direction)
|
2021-09-15 08:06:50 -04:00
|
|
|
.setUseMarketBasedPrice(useMarketBasedPrice)
|
2022-02-11 17:13:56 -06:00
|
|
|
.setPrice(useMarketBasedPrice ? "1.0" : price.toString()) // TODO: positive price required even if using market price
|
2021-09-15 08:06:50 -04:00
|
|
|
.setMarketPriceMargin(marketPriceMargin)
|
2021-09-14 08:30:22 -04:00
|
|
|
.setAmount(amount.toString())
|
2021-09-15 08:06:50 -04:00
|
|
|
.setMinAmount(minAmount.toString())
|
|
|
|
.setBuyerSecurityDeposit(buyerSecurityDeposit)
|
2021-10-15 13:17:52 -04:00
|
|
|
.setPaymentAccountId(paymentAccountId);
|
2021-09-15 08:06:50 -04:00
|
|
|
if (triggerPrice) request.setTriggerPrice(BigInt(triggerPrice.toString()).toString());
|
2021-09-14 08:30:22 -04:00
|
|
|
return new Promise(function(resolve, reject) {
|
2021-11-11 13:48:31 -05:00
|
|
|
that._offersClient.createOffer(request, {password: that._password}, function(err: grpcWeb.RpcError, response: CreateOfferReply) {
|
2021-09-14 08:30:22 -04:00
|
|
|
if (err) reject(err);
|
2021-09-15 08:06:50 -04:00
|
|
|
else resolve(response.getOffer()!);
|
2021-09-14 08:30:22 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-09-17 09:33:58 -04:00
|
|
|
* Remove a posted offer, releasing its reserved funds.
|
2021-09-14 08:30:22 -04:00
|
|
|
*
|
2021-09-19 14:00:22 -04:00
|
|
|
* @param {string} offerId - the offer id to cancel
|
|
|
|
*/
|
|
|
|
async removeOffer(offerId: string): Promise<void> {
|
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
2021-11-11 13:48:31 -05:00
|
|
|
that._offersClient.cancelOffer(new CancelOfferRequest().setId(offerId), {password: that._password}, function(err: grpcWeb.RpcError) {
|
2021-09-19 14:00:22 -04:00
|
|
|
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()
|
2022-01-15 15:43:10 -05:00
|
|
|
.setOfferId(offerId)
|
|
|
|
.setPaymentAccountId(paymentAccountId);
|
2021-09-19 14:00:22 -04:00
|
|
|
return new Promise(function(resolve, reject) {
|
2021-11-11 13:48:31 -05:00
|
|
|
that._tradesClient.takeOffer(request, {password: that._password}, function(err: grpcWeb.RpcError, response: TakeOfferReply) {
|
2021-09-19 14:00:22 -04:00
|
|
|
if (err) reject(err);
|
|
|
|
else if (response.getFailureReason() && response.getFailureReason()!.getAvailabilityResult() !== AvailabilityResult.AVAILABLE) reject(response.getFailureReason()!.getDescription());
|
2021-11-11 13:48:31 -05:00
|
|
|
else resolve(response.getTrade()!);
|
2021-09-19 14:00:22 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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) {
|
2021-11-11 13:48:31 -05:00
|
|
|
that._tradesClient.getTrade(new GetTradeRequest().setTradeId(tradeId), {password: that._password}, function(err: grpcWeb.RpcError, response: GetTradeReply) {
|
2021-09-19 14:00:22 -04:00
|
|
|
if (err) reject(err);
|
2021-11-11 13:48:31 -05:00
|
|
|
else resolve(response.getTrade()!);
|
2021-09-19 14:00:22 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-12-14 13:04:02 -05:00
|
|
|
/**
|
|
|
|
* Get all trades.
|
|
|
|
*
|
|
|
|
* @return {TradeInfo[]} all user trades
|
|
|
|
*/
|
|
|
|
async getTrades(): Promise<TradeInfo[]> {
|
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._tradesClient.getTrades(new GetTradesRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: GetTradesReply) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve(response.getTradesList());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-09-19 14:00:22 -04:00
|
|
|
/**
|
|
|
|
* 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) {
|
2021-11-11 13:48:31 -05:00
|
|
|
that._tradesClient.confirmPaymentStarted(new ConfirmPaymentStartedRequest().setTradeId(tradeId), {password: that._password}, function(err: grpcWeb.RpcError) {
|
2021-09-19 14:00:22 -04:00
|
|
|
if (err) reject(err);
|
|
|
|
else resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Confirm a payment is received.
|
|
|
|
*
|
|
|
|
* @param {string} tradeId - the id of the trade
|
2021-09-14 08:30:22 -04:00
|
|
|
*/
|
2021-09-19 14:00:22 -04:00
|
|
|
async confirmPaymentReceived(tradeId: string): Promise<void> {
|
2021-09-14 08:30:22 -04:00
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
2021-11-11 13:48:31 -05:00
|
|
|
that._tradesClient.confirmPaymentReceived(new ConfirmPaymentReceivedRequest().setTradeId(tradeId), {password: that._password}, function(err: grpcWeb.RpcError) {
|
2021-09-14 08:30:22 -04:00
|
|
|
if (err) reject(err);
|
|
|
|
else resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2022-01-09 17:02:43 +01:00
|
|
|
|
2022-02-09 01:41:00 -08:00
|
|
|
/**
|
|
|
|
* Shutdown the Haveno daemon server and stop the process if applicable.
|
|
|
|
*/
|
|
|
|
async shutdownServer() {
|
|
|
|
if (this._keepAliveLooper) this._keepAliveLooper.stop();
|
|
|
|
let that = this;
|
|
|
|
await new Promise(function(resolve, reject) {
|
|
|
|
that._shutdownServerClient.stop(new StopRequest(), {password: that._password}, function(err: grpcWeb.RpcError) { // process receives 'exit' event
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
if (this._process) return HavenoUtils.kill(this._process);
|
|
|
|
}
|
2022-02-11 17:13:56 -06:00
|
|
|
|
2022-01-09 17:02:43 +01:00
|
|
|
// ------------------------------- HELPERS ----------------------------------
|
|
|
|
|
2022-02-09 01:41:00 -08:00
|
|
|
/**
|
|
|
|
* Wait for the application to be fully initialized with an account and a
|
|
|
|
* connection to the Haveno network.
|
|
|
|
*
|
|
|
|
* TODO:
|
|
|
|
*
|
|
|
|
* Currently when the application starts, the account is first initialized with createAccount()
|
|
|
|
* or openAccount() which return immediately. A notification is sent after all setup is complete and
|
|
|
|
* the application is connected to the Haveno network.
|
|
|
|
*
|
|
|
|
* Ideally when the application starts, the system checks the Haveno network connection, supporting
|
|
|
|
* havenod.isHavenoConnectionInitialized() and havenod.awaitHavenoConnectionInitialized().
|
|
|
|
* Independently, gRPC createAccount() and openAccount() return after all account setup and reading from disk.
|
|
|
|
*/
|
|
|
|
async _awaitAppInitialized(): Promise<void> {
|
|
|
|
let that = this;
|
|
|
|
return new Promise(async function(resolve) {
|
|
|
|
let isResolved = false;
|
|
|
|
let listener = async function(notification: NotificationMessage) {
|
|
|
|
if (notification.getType() === NotificationMessage.NotificationType.APP_INITIALIZED) await resolveOnce();
|
|
|
|
}
|
|
|
|
await that.addNotificationListener(listener);
|
|
|
|
if (await that._isAppInitialized()) await resolveOnce();
|
|
|
|
async function resolveOnce() {
|
|
|
|
if (isResolved) return;
|
|
|
|
isResolved = true;
|
|
|
|
await that.removeNotificationListener(listener);
|
|
|
|
resolve();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async _isAppInitialized(): Promise<boolean> {
|
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._accountClient.isAppInitialized(new IsAppInitializedRequest(), {password: that._password}, function(err: grpcWeb.RpcError, response: IsAppInitializedReply) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve(response.getIsAppInitialized());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-01-09 17:02:43 +01:00
|
|
|
/**
|
|
|
|
* Register a listener to receive notifications.
|
|
|
|
* Due to the nature of grpc streaming, this method returns a promise
|
|
|
|
* which may be resolved before the listener is actually registered.
|
|
|
|
*/
|
2022-02-09 01:41:00 -08:00
|
|
|
async _registerNotificationListenerOnce(): Promise<void> {
|
|
|
|
if (this._registerNotificationListenerCalled) return;
|
|
|
|
else this._registerNotificationListenerCalled = true;
|
2022-01-09 17:02:43 +01:00
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve) {
|
2022-02-09 01:41:00 -08:00
|
|
|
|
2022-01-15 15:43:10 -05:00
|
|
|
// send request to register client listener
|
2022-01-09 17:02:43 +01:00
|
|
|
that._notificationsClient.registerNotificationListener(new RegisterNotificationListenerRequest(), {password: that._password})
|
2022-02-09 01:41:00 -08:00
|
|
|
.on('data', (data) => {
|
2022-01-09 17:02:43 +01:00
|
|
|
if (data instanceof NotificationMessage) {
|
|
|
|
for (let listener of that._notificationListeners) listener(data);
|
|
|
|
}
|
|
|
|
});
|
2022-02-09 01:41:00 -08:00
|
|
|
|
2022-01-15 15:43:10 -05:00
|
|
|
// periodically send keep alive requests // TODO (woodser): better way to keep notification stream alive?
|
|
|
|
let firstRequest = true;
|
2022-02-09 01:41:00 -08:00
|
|
|
that._keepAliveLooper = new TaskLooper(async function() {
|
2022-01-15 15:43:10 -05:00
|
|
|
if (firstRequest) {
|
|
|
|
firstRequest = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
await that._sendNotification(new NotificationMessage()
|
|
|
|
.setType(NotificationMessage.NotificationType.KEEP_ALIVE)
|
|
|
|
.setTimestamp(Date.now()));
|
|
|
|
});
|
2022-02-09 01:41:00 -08:00
|
|
|
that._keepAliveLooper.start(that._keepAlivePeriodMs);
|
|
|
|
|
|
|
|
setTimeout(resolve, 1000); // TODO: call returns before listener registered
|
2022-01-09 17:02:43 +01:00
|
|
|
});
|
|
|
|
}
|
2022-02-09 01:41:00 -08:00
|
|
|
|
2022-01-09 17:02:43 +01:00
|
|
|
/**
|
|
|
|
* Send a notification.
|
2022-02-09 01:41:00 -08:00
|
|
|
*
|
2022-01-09 17:02:43 +01:00
|
|
|
* @param {NotificationMessage} notification - notification to send
|
|
|
|
*/
|
|
|
|
async _sendNotification(notification: NotificationMessage): Promise<void> {
|
|
|
|
let that = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._notificationsClient.sendNotification(new SendNotificationRequest().setNotification(notification), {password: that._password}, function(err: grpcWeb.RpcError) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2022-02-09 01:41:00 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Restore an account chunk from zip bytes.
|
|
|
|
*/
|
|
|
|
async _restoreAccountChunk(zipBytes: Uint8Array, offset: number, totalLength: number, hasMore: boolean): Promise<void> {
|
|
|
|
let that = this;
|
|
|
|
let request = new RestoreAccountRequest()
|
|
|
|
.setZipBytes(zipBytes)
|
|
|
|
.setOffset(offset)
|
|
|
|
.setTotalLength(totalLength)
|
|
|
|
.setHasMore(hasMore);
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
that._accountClient.restoreAccount(request, {password: that._password}, function(err: grpcWeb.RpcError) {
|
|
|
|
if (err) reject(err);
|
|
|
|
else resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2021-09-12 09:39:21 -04:00
|
|
|
}
|
|
|
|
|
2021-11-19 23:29:44 +01:00
|
|
|
export {HavenoDaemon};
|