2021-11-12 10:26:22 -05:00
// --------------------------------- IMPORTS ----------------------------------
2021-09-17 09:33:58 -04:00
// import haveno types
2021-09-14 08:27:45 -04:00
import { HavenoDaemon } from "./HavenoDaemon" ;
2021-12-08 06:22:36 -05:00
import { HavenoUtils } from "./HavenoUtils" ;
import * as grpcWeb from 'grpc-web' ;
2021-11-19 23:29:44 +01:00
import { XmrBalanceInfo , OfferInfo , TradeInfo , MarketPriceInfo } from './protobuf/grpc_pb' ; // TODO (woodser): better names; haveno_grpc_pb, haveno_pb
2021-11-19 17:25:49 -05:00
import { PaymentAccount , Offer } from './protobuf/pb_pb' ;
2021-09-12 09:39:21 -04:00
2021-09-17 09:33:58 -04:00
// import monero-javascript
const monerojs = require ( "monero-javascript" ) ; // TODO (woodser): support typescript and `npm install @types/monero-javascript` in monero-javascript
2021-10-22 13:51:57 -04:00
const GenUtils = monerojs . GenUtils ;
2021-09-19 14:00:22 -04:00
const MoneroTxConfig = monerojs . MoneroTxConfig ;
const TaskLooper = monerojs . TaskLooper ;
2021-12-08 06:22:36 -05:00
// other required imports
const console = require ( 'console' ) ; // import console because jest swallows messages in real time
const assert = require ( "assert" ) ;
2021-12-14 13:04:02 -05:00
const net = require ( 'net' ) ;
2021-09-12 09:39:21 -04:00
2021-11-12 10:26:22 -05:00
// --------------------------- TEST CONFIGURATION -----------------------------
2021-12-14 13:04:02 -05:00
// logging options
HavenoUtils . setLogLevel ( 1 ) ; // set log level (gets more verbose increasing from 0)
const LOG_PROCESS_OUTPUT = false ; // enable or disable logging process output
2021-12-08 06:22:36 -05:00
// path to directory with haveno binaries
const HAVENO_PATH = "../haveno" ;
2021-11-12 10:26:22 -05:00
// wallet to fund alice and bob during tests
2021-12-08 06:22:36 -05:00
const FUNDING_WALLET_URL = "http://localhost:38084" ;
const FUNDING_WALLET_USERNAME = "rpc_user" ;
const FUNDING_WALLET_PASSWORD = "abc123" ;
const DEFAULT_FUNDING_WALLET_PATH = "test_funding_wallet" ;
const MINIMUM_FUNDING = BigInt ( "5000000000000" ) ;
2021-11-12 10:26:22 -05:00
let fundingWallet : any ;
2021-09-17 09:33:58 -04:00
// alice config
2021-12-08 06:22:36 -05:00
const HAVENO_VERSION = "1.6.2" ;
const ALICE_DAEMON_URL = "http://localhost:8080" ;
const ALICE_DAEMON_PASSWORD = "apitest" ;
const ALICE_WALLET_URL = "http://127.0.0.1:38091" ; // alice's internal haveno wallet for direct testing
const ALICE_WALLET_USERNAME = "rpc_user" ;
const ALICE_WALLET_PASSWORD = "abc123" ;
let alice : HavenoDaemon ;
2021-09-19 14:00:22 -04:00
let aliceWallet : any ;
2021-09-17 09:33:58 -04:00
// bob config
2021-12-08 06:22:36 -05:00
const BOB_DAEMON_URL = "http://localhost:8081" ;
const BOB_DAEMON_PASSWORD = "apitest" ;
2021-12-14 13:04:02 -05:00
let bob : HavenoDaemon ;
2021-09-17 09:33:58 -04:00
// monero daemon config
2021-12-08 06:22:36 -05:00
const MONERO_DAEMON_URL = "http://localhost:38081"
const MONERO_DAEMON_USERNAME = "superuser" ;
const MONERO_DAEMON_PASSWORD = "abctesting123" ;
2021-09-17 09:33:58 -04:00
let monerod : any ;
2021-09-19 14:00:22 -04:00
// other test config
2021-11-19 17:25:49 -05:00
const MAX_FEE = BigInt ( "75000000000" ) ;
2021-10-15 13:17:52 -04:00
const WALLET_SYNC_PERIOD = 5000 ;
2021-09-19 14:00:22 -04:00
const MAX_TIME_PEER_NOTICE = 3000 ;
2021-10-22 13:51:57 -04:00
const TEST_CRYPTO_ACCOUNTS = [ // TODO (woodser): test other cryptos, fiat
{
2021-11-19 23:29:44 +01:00
currencyCode : "ETH" ,
2021-10-22 13:51:57 -04:00
address : "0xdBdAb835Acd6fC84cF5F9aDD3c0B5a1E25fbd99f"
} ,
{
2021-11-19 23:29:44 +01:00
currencyCode : "BTC" ,
2021-10-22 13:51:57 -04:00
address : "bcrt1q6j90vywv8x7eyevcnn2tn2wrlg3vsjlsvt46qz"
}
] ;
2021-09-19 14:00:22 -04:00
2021-12-08 06:22:36 -05:00
// map proxied ports to havenod api and p2p ports
const PROXY_PORTS = new Map < string , string [ ] > ( [
[ "8080" , [ "9999" , "5555" ] ] ,
[ "8081" , [ "10000" , "6666" ] ] ,
[ "8082" , [ "10001" , "7777" ] ] ,
[ "8083" , [ "10002" , "7778" ] ] ,
[ "8084" , [ "10003" , "7779" ] ] ,
[ "8085" , [ "10004" , "7780" ] ] ,
[ "8086" , [ "10005" , "7781" ] ] ,
] ) ;
// track started haveno processes
const HAVENO_PROCESSES : HavenoDaemon [ ] = [ ] ;
2021-12-14 13:04:02 -05:00
const HAVENO_PROCESS_PORTS : string [ ] = [ ] ;
2021-12-08 06:22:36 -05:00
2021-11-12 10:26:22 -05:00
// ----------------------------------- TESTS ----------------------------------
2021-09-17 09:33:58 -04:00
beforeAll ( async ( ) = > {
2021-09-19 14:00:22 -04:00
2021-12-08 06:22:36 -05:00
// initialize clients
alice = new HavenoDaemon ( ALICE_DAEMON_URL , ALICE_DAEMON_PASSWORD ) ;
bob = new HavenoDaemon ( BOB_DAEMON_URL , BOB_DAEMON_PASSWORD ) ;
monerod = await monerojs . connectToDaemonRpc ( MONERO_DAEMON_URL , MONERO_DAEMON_USERNAME , MONERO_DAEMON_PASSWORD ) ;
aliceWallet = await monerojs . connectToWalletRpc ( ALICE_WALLET_URL , ALICE_WALLET_USERNAME , ALICE_WALLET_PASSWORD ) ;
2021-09-19 14:00:22 -04:00
2021-11-18 15:45:25 -05:00
// initialize funding wallet
await initFundingWallet ( ) ;
2021-09-19 14:00:22 -04:00
// debug tools
2021-09-17 09:33:58 -04:00
//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());
2021-09-19 14:00:22 -04:00
//console.log((await alice.getBalances()).getUnlockedBalance() + ", " + (await alice.getBalances()).getLockedBalance());
//console.log((await bob.getBalances()).getUnlockedBalance() + ", " + (await bob.getBalances()).getLockedBalance());
2021-09-17 09:33:58 -04:00
} ) ;
2021-09-12 09:39:21 -04:00
2021-12-14 13:04:02 -05:00
jest . setTimeout ( 400000 ) ;
2021-09-12 09:39:21 -04:00
test ( "Can get the version" , async ( ) = > {
2021-09-17 09:33:58 -04:00
let version = await alice . getVersion ( ) ;
2021-12-08 06:22:36 -05:00
expect ( version ) . toEqual ( HAVENO_VERSION ) ;
2021-09-12 09:39:21 -04:00
} ) ;
2021-10-22 13:51:57 -04:00
test ( "Can get market prices" , async ( ) = > {
2021-11-19 23:29:44 +01:00
// get all market prices
let prices : MarketPriceInfo [ ] = await alice . getPrices ( ) ;
expect ( prices . length ) . toBeGreaterThan ( 1 ) ;
for ( let price of prices ) {
expect ( price . getCurrencyCode ( ) . length ) . toBeGreaterThan ( 0 ) ;
expect ( price . getPrice ( ) ) . toBeGreaterThanOrEqual ( 0 ) ;
}
// get market prices of specific currencies
2021-10-22 13:51:57 -04:00
for ( let testAccount of TEST_CRYPTO_ACCOUNTS ) {
let price = await alice . getPrice ( testAccount . currencyCode ) ;
expect ( price ) . toBeGreaterThan ( 0 ) ;
}
2021-11-19 23:29:44 +01:00
// test that prices are reasonable
let usd = await alice . getPrice ( "USD" ) ;
expect ( usd ) . toBeGreaterThan ( 50 ) ;
expect ( usd ) . toBeLessThan ( 5000 ) ;
let doge = await alice . getPrice ( "DOGE" ) ;
expect ( doge ) . toBeGreaterThan ( 200 )
expect ( doge ) . toBeLessThan ( 20000 ) ;
let btc = await alice . getPrice ( "BTC" ) ;
expect ( btc ) . toBeGreaterThan ( 0.0004 )
expect ( btc ) . toBeLessThan ( 0.4 ) ;
// test invalid currency
await expect ( async ( ) = > { await alice . getPrice ( "INVALID_CURRENCY" ) } )
. rejects
. toThrow ( 'Currency not found: INVALID_CURRENCY' ) ;
2021-10-22 13:51:57 -04:00
} ) ;
2021-09-17 09:33:58 -04:00
test ( "Can get balances" , async ( ) = > {
let balances : XmrBalanceInfo = await alice . getBalances ( ) ;
2021-11-11 13:48:31 -05:00
expect ( BigInt ( balances . getUnlockedBalance ( ) ) ) . toBeGreaterThanOrEqual ( 0 ) ;
expect ( BigInt ( balances . getLockedBalance ( ) ) ) . toBeGreaterThanOrEqual ( 0 ) ;
expect ( BigInt ( balances . getReservedOfferBalance ( ) ) ) . toBeGreaterThanOrEqual ( 0 ) ;
expect ( BigInt ( balances . getReservedTradeBalance ( ) ) ) . toBeGreaterThanOrEqual ( 0 ) ;
2021-09-12 09:39:21 -04:00
} ) ;
2021-09-14 08:27:45 -04:00
test ( "Can get offers" , async ( ) = > {
2021-09-17 09:33:58 -04:00
let offers : OfferInfo [ ] = await alice . getOffers ( "BUY" ) ;
2021-09-12 09:39:21 -04:00
for ( let offer of offers ) {
testOffer ( offer ) ;
}
} ) ;
2021-09-17 09:33:58 -04:00
test ( "Can get my offers" , async ( ) = > {
let offers : OfferInfo [ ] = await alice . getMyOffers ( "SELL" ) ;
2021-09-14 08:27:45 -04:00
for ( let offer of offers ) {
testOffer ( offer ) ;
}
} ) ;
test ( "Can get payment accounts" , async ( ) = > {
2021-09-17 09:33:58 -04:00
let paymentAccounts : PaymentAccount [ ] = await alice . getPaymentAccounts ( ) ;
2021-09-14 08:27:45 -04:00
for ( let paymentAccount of paymentAccounts ) {
2021-11-16 08:06:20 -05:00
if ( paymentAccount . getPaymentAccountPayload ( ) ! . getCryptoCurrencyAccountPayload ( ) ) { // TODO (woodser): test non-crypto
testCryptoPaymentAccount ( paymentAccount ) ;
}
2021-09-14 08:27:45 -04:00
}
} ) ;
2021-10-22 13:51:57 -04:00
test ( "Can create crypto payment accounts" , async ( ) = > {
// test each stagenet crypto account
for ( let testAccount of TEST_CRYPTO_ACCOUNTS ) {
// create payment account
let name = testAccount . currencyCode + " " + testAccount . address . substr ( 0 , 8 ) + "... " + GenUtils . getUUID ( ) ;
let paymentAccount : PaymentAccount = await alice . createCryptoPaymentAccount (
name ,
testAccount . currencyCode ,
testAccount . address ) ;
testCryptoPaymentAccount ( paymentAccount ) ;
testCryptoPaymentAccountEquals ( paymentAccount , testAccount , name ) ;
// fetch and test payment account
let fetchedAccount : PaymentAccount | undefined ;
for ( let account of await alice . getPaymentAccounts ( ) ) {
if ( paymentAccount . getId ( ) === account . getId ( ) ) {
fetchedAccount = account ;
break ;
}
2021-10-15 13:17:52 -04:00
}
2021-10-22 13:51:57 -04:00
if ( ! fetchedAccount ) throw new Error ( "Payment account not found after being added" ) ;
testCryptoPaymentAccount ( paymentAccount ) ;
testCryptoPaymentAccountEquals ( fetchedAccount , testAccount , name ) ;
// wait before creating next account
await GenUtils . waitFor ( 1000 ) ;
// TODO (woodser): test rejecting account with invalid currency code
// TODO (woodser): test rejecting account with invalid address
// TODO (woodser): test rejecting account with duplicate name
}
function testCryptoPaymentAccountEquals ( paymentAccount : PaymentAccount , testAccount : any , name : string ) {
expect ( paymentAccount . getAccountName ( ) ) . toEqual ( name ) ;
expect ( paymentAccount . getPaymentAccountPayload ( ) ! . getCryptoCurrencyAccountPayload ( ) ! . getAddress ( ) ) . toEqual ( testAccount . address ) ;
expect ( paymentAccount . getSelectedTradeCurrency ( ) ! . getCode ( ) ) . toEqual ( testAccount . currencyCode . toUpperCase ( ) ) ;
2021-10-15 13:17:52 -04:00
}
2021-09-14 08:30:22 -04:00
} ) ;
test ( "Can post and remove an offer" , async ( ) = > {
2021-10-15 13:17:52 -04:00
2021-11-12 10:26:22 -05:00
// wait for alice to have unlocked balance to post offer
2021-10-15 13:17:52 -04:00
let tradeAmount : bigint = BigInt ( "250000000000" ) ;
2021-11-19 17:25:49 -05:00
await waitForUnlockedBalance ( tradeAmount * BigInt ( "2" ) , alice ) ;
2021-09-17 09:33:58 -04:00
// get unlocked balance before reserving funds for offer
let unlockedBalanceBefore : bigint = BigInt ( ( await alice . getBalances ( ) ) . getUnlockedBalance ( ) ) ;
// post offer
2021-12-08 06:22:36 -05:00
let offer : OfferInfo = await postOffer ( alice , "buy" , BigInt ( "200000000000" ) , undefined ) ;
2021-12-14 13:04:02 -05:00
assert . equal ( offer . getState ( ) , "AVAILABLE" ) ;
// has offer
offer = await alice . getMyOffer ( offer . getId ( ) ) ;
assert . equal ( offer . getState ( ) , "AVAILABLE" ) ;
2021-09-17 09:33:58 -04:00
// cancel offer
await alice . removeOffer ( offer . getId ( ) ) ;
// offer is removed from my offers
if ( getOffer ( await alice . getMyOffers ( "buy" ) , offer . getId ( ) ) ) throw new Error ( "Offer " + offer . getId ( ) + " was found in my offers after removal" ) ;
// reserved balance released
expect ( unlockedBalanceBefore ) . toEqual ( BigInt ( ( await alice . getBalances ( ) ) . getUnlockedBalance ( ) ) ) ;
} ) ;
test ( "Invalidates offers when reserved funds are spent" , async ( ) = > {
2021-12-14 13:04:02 -05:00
let err ;
let tx ;
try {
// wait for alice to have unlocked balance for trade
let tradeAmount : bigint = BigInt ( "250000000000" ) ;
await waitForUnlockedBalance ( tradeAmount * BigInt ( "2" ) , alice ) ;
2021-09-17 09:33:58 -04:00
2021-12-14 13:04:02 -05:00
// get frozen key images before posting offer
let frozenKeyImagesBefore = [ ] ;
for ( let frozenOutput of await aliceWallet . getOutputs ( { isFrozen : true } ) ) frozenKeyImagesBefore . push ( frozenOutput . getKeyImage ( ) . getHex ( ) ) ;
// post offer
await wait ( 1000 ) ;
let offer : OfferInfo = await postOffer ( alice , "buy" , tradeAmount , undefined ) ;
// get key images reserved by offer
let reservedKeyImages = [ ] ;
let frozenKeyImagesAfter = [ ] ;
for ( let frozenOutput of await aliceWallet . getOutputs ( { isFrozen : true } ) ) frozenKeyImagesAfter . push ( frozenOutput . getKeyImage ( ) . getHex ( ) ) ;
for ( let frozenKeyImageAfter of frozenKeyImagesAfter ) {
if ( ! frozenKeyImagesBefore . includes ( frozenKeyImageAfter ) ) reservedKeyImages . push ( frozenKeyImageAfter ) ;
}
// offer is available to peers
await wait ( WALLET_SYNC_PERIOD * 2 ) ;
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
if ( ! reservedKeyImages . length ) throw new Error ( "No reserved key images detected" ) ;
await aliceWallet . thawOutput ( reservedKeyImages [ 0 ] ) ;
tx = await aliceWallet . sweepOutput ( { keyImage : reservedKeyImages [ 0 ] , address : await aliceWallet . getPrimaryAddress ( ) , relay : false } ) ;
await monerod . submitTxHex ( tx . getFullHex ( ) , true ) ;
// wait for spend to be seen
await wait ( WALLET_SYNC_PERIOD * 2 ) ; // TODO (woodser): need place for common test utilities
// offer is removed from peer offers
if ( getOffer ( await bob . getOffers ( "buy" ) , offer . getId ( ) ) ) throw new Error ( "Offer " + offer . getId ( ) + " was found in peer's offers after reserved funds spent" ) ;
// offer is removed from my offers
if ( getOffer ( await alice . getMyOffers ( "buy" ) , offer . getId ( ) ) ) throw new Error ( "Offer " + offer . getId ( ) + " was found in my offers after reserved funds spent" ) ;
// offer is automatically cancelled
try {
await alice . removeOffer ( offer . getId ( ) ) ;
throw new Error ( "cannot remove invalidated offer" ) ;
} catch ( err ) {
if ( err . message === "cannot remove invalidated offer" ) throw new Error ( err . message ) ;
}
} catch ( err2 ) {
err = err2 ;
2021-09-17 09:33:58 -04:00
}
2021-12-14 13:04:02 -05:00
// flush tx from pool
if ( tx ) await monerod . flushTxPool ( tx . getHash ( ) ) ;
if ( err ) throw err ;
} ) ;
// TODO (woodser): test arbitrator state too
// TODO (woodser): test breaking protocol after depositing to multisig (e.g. don't send payment account payload by deleting it)
test ( "Handles unexpected errors during trade initialization" , async ( ) = > {
let traders : HavenoDaemon [ ] = [ ] ;
let err : any ;
2021-09-17 09:33:58 -04:00
try {
2021-12-14 13:04:02 -05:00
// start and fund 3 trader processes
let tradeAmount : bigint = BigInt ( "250000000000" ) ;
console . log ( "Starting trader processes" ) ;
traders = await startTraderProcesses ( 3 , LOG_PROCESS_OUTPUT ) ;
await traders [ 0 ] . getBalances ( ) ;
await waitForUnlockedBalance ( tradeAmount * BigInt ( "2" ) , traders [ 0 ] , traders [ 1 ] , traders [ 2 ] ) ;
// trader 0 posts offer
console . log ( "Posting offer" ) ;
let offer = await postOffer ( traders [ 0 ] , "buy" , tradeAmount , undefined ) ;
offer = await traders [ 0 ] . getMyOffer ( offer . getId ( ) ) ;
assert . equal ( offer . getState ( ) , "AVAILABLE" ) ;
// wait for offer for offer to be seen
await wait ( WALLET_SYNC_PERIOD * 2 ) ;
// trader 1 spends trade funds after initializing trade
let paymentAccount = await createCryptoPaymentAccount ( traders [ 1 ] ) ;
wait ( 3000 ) . then ( async function ( ) {
try {
let traderWallet = await monerojs . connectToWalletRpc ( "http://localhost:" + traders [ 1 ] . getWalletRpcPort ( ) , "rpc_user" , "abc123" ) ; // TODO: don't hardcode here, protect wallet rpc based on account password
for ( let frozenOutput of await traderWallet . getOutputs ( { isFrozen : true } ) ) await traderWallet . thawOutput ( frozenOutput . getKeyImage ( ) . getHex ( ) ) ;
console . log ( "Sweeping trade funds" ) ;
await traderWallet . sweepUnlocked ( { address : await traderWallet . getPrimaryAddress ( ) , relay : true } ) ;
} catch ( err ) {
console . log ( "Caught error sweeping funds!" ) ;
console . log ( err ) ;
}
} ) ;
// trader 1 tries to take offer
try {
console . log ( "Trader 1 taking offer" ) ;
await traders [ 1 ] . takeOffer ( offer . getId ( ) , paymentAccount . getId ( ) ) ;
throw new Error ( "Should have failed taking offer because taker trade funds spent" )
} catch ( err ) {
assert ( err . message . includes ( "not enough money" ) , "Unexpected error: " + err . message ) ;
}
// TODO: test that unavailable right after taking (taker will know before maker)
// trader 0's offer remains available
await wait ( 10000 ) ; // give time for trade initialization to fail and offer to become available
offer = await traders [ 0 ] . getMyOffer ( offer . getId ( ) ) ;
if ( offer . getState ( ) !== "AVAILABLE" ) {
console . log ( "Offer is not yet available, waiting to become available after timeout..." ) ; // there is no error notice if peer stops responding
await wait ( 25000 ) ; // give another 25 seconds to become available
offer = await traders [ 0 ] . getMyOffer ( offer . getId ( ) ) ;
assert . equal ( offer . getState ( ) , "AVAILABLE" ) ;
}
// trader 0 spends trade funds then trader 2 takes offer
wait ( 3000 ) . then ( async function ( ) {
try {
let traderWallet = await monerojs . connectToWalletRpc ( "http://localhost:" + traders [ 0 ] . getWalletRpcPort ( ) , "rpc_user" , "abc123" ) ; // TODO: don't hardcode here, protect wallet rpc based on account password
for ( let frozenOutput of await traderWallet . getOutputs ( { isFrozen : true } ) ) await traderWallet . thawOutput ( frozenOutput . getKeyImage ( ) . getHex ( ) ) ;
console . log ( "Sweeping offer funds" ) ;
await traderWallet . sweepUnlocked ( { address : await traderWallet . getPrimaryAddress ( ) , relay : true } ) ;
} catch ( err ) {
console . log ( "Caught error sweeping funds!" ) ;
console . log ( err ) ;
}
} ) ;
// trader 2 tries to take offer
paymentAccount = await createCryptoPaymentAccount ( traders [ 2 ] ) ;
try {
console . log ( "Trader 2 taking offer" )
await traders [ 2 ] . takeOffer ( offer . getId ( ) , paymentAccount . getId ( ) ) ;
throw new Error ( "Should have failed taking offer because maker trade funds spent" )
} catch ( err ) {
assert ( err . message . includes ( "not enough money" ) || err . message . includes ( "timeout reached. protocol did not complete" ) , "Unexpected error: " + err . message ) ;
}
// trader 2's balance is unreserved
let trader2Balances = await traders [ 2 ] . getBalances ( ) ;
expect ( BigInt ( trader2Balances . getReservedTradeBalance ( ) ) ) . toEqual ( BigInt ( "0" ) ) ;
expect ( BigInt ( trader2Balances . getUnlockedBalance ( ) ) ) . toBeGreaterThan ( BigInt ( "0" ) ) ;
} catch ( err2 ) {
err = err2 ;
2021-09-17 09:33:58 -04:00
}
2021-12-14 13:04:02 -05:00
// stop traders
console . log ( "Stopping haveno processes" ) ;
for ( let trader of traders ) await stopHavenoProcess ( trader ) ;
if ( err ) throw err ;
2021-09-17 09:33:58 -04:00
} ) ;
2021-12-08 06:22:36 -05:00
test ( "Cannot make or take offer with insufficient unlocked funds" , async ( ) = > {
let charlie : HavenoDaemon | undefined ;
let err : any ;
try {
// start charlie
2021-12-14 13:04:02 -05:00
charlie = await startTraderProcess ( LOG_PROCESS_OUTPUT ) ;
2021-12-08 06:22:36 -05:00
// charlie creates ethereum payment account
2021-12-14 13:04:02 -05:00
let paymentAccount = await createCryptoPaymentAccount ( charlie ) ;
2021-12-08 06:22:36 -05:00
// charlie cannot make offer with insufficient funds
try {
2021-12-14 13:04:02 -05:00
await postOffer ( charlie , "buy" , BigInt ( "200000000000" ) , paymentAccount . getId ( ) ) ;
2021-12-08 06:22:36 -05:00
throw new Error ( "Should have failed making offer with insufficient funds" )
} catch ( err ) {
let errTyped = err as grpcWeb . RpcError ;
assert . equal ( errTyped . code , 2 ) ;
assert ( errTyped . message . includes ( "not enough money" ) ) ;
}
// alice posts offer
let offers : OfferInfo [ ] = await alice . getMyOffers ( "buy" ) ; // TODO: support alice.getMyOffers() without direction
let offer : OfferInfo ;
if ( offers . length ) offer = offers [ 0 ] ;
else {
let tradeAmount : bigint = BigInt ( "250000000000" ) ;
await waitForUnlockedBalance ( tradeAmount * BigInt ( "2" ) , alice ) ;
offer = await postOffer ( alice , "buy" , tradeAmount , undefined ) ;
assert . equal ( offer . getState ( ) , "AVAILABLE" ) ;
2021-12-14 13:04:02 -05:00
await wait ( WALLET_SYNC_PERIOD * 2 ) ;
2021-12-08 06:22:36 -05:00
}
// charlie cannot take offer with insufficient funds
try {
2021-12-14 13:04:02 -05:00
await charlie . takeOffer ( offer . getId ( ) , paymentAccount . getId ( ) ) ; // TODO (woodser): this returns before trade is fully initialized
2021-12-08 06:22:36 -05:00
throw new Error ( "Should have failed taking offer with insufficient funds" )
} catch ( err ) {
let errTyped = err as grpcWeb . RpcError ;
2021-12-14 13:04:02 -05:00
assert ( errTyped . message . includes ( "not enough money" ) , "Unexpected error: " + errTyped . message ) ; // TODO (woodser): error message does not contain stacktrace
2021-12-08 06:22:36 -05:00
assert . equal ( errTyped . code , 2 ) ;
}
// charlie does not have trade
try {
await charlie . getTrade ( offer . getId ( ) ) ;
} catch ( err ) {
let errTyped = err as grpcWeb . RpcError ;
assert . equal ( errTyped . code , 3 ) ;
assert ( errTyped . message . includes ( "trade with id '" + offer . getId ( ) + "' not found" ) ) ; // TODO (woodser): error message does not contain stacktrace
}
} catch ( err2 ) {
err = err2 ;
}
// stop charlie
if ( charlie ) await stopHavenoProcess ( charlie ) ;
// TODO: how to delete trader app folder at end of test?
if ( err ) throw err ;
} ) ;
2021-11-19 17:25:49 -05:00
// TODO (woodser): test grpc notifications
2021-09-17 09:33:58 -04:00
test ( "Can complete a trade" , async ( ) = > {
2021-12-08 06:22:36 -05:00
2021-09-17 09:33:58 -04:00
// wait for alice and bob to have unlocked balance for trade
let tradeAmount : bigint = BigInt ( "250000000000" ) ;
2021-11-19 17:25:49 -05:00
await waitForUnlockedBalance ( tradeAmount * BigInt ( "2" ) , alice , bob ) ;
let aliceBalancesBefore = await alice . getBalances ( ) ;
let bobBalancesBefore : XmrBalanceInfo = await bob . getBalances ( ) ;
2021-09-19 14:00:22 -04:00
2021-10-15 13:17:52 -04:00
// alice posts offer to buy xmr
console . log ( "Alice posting offer" ) ;
2021-11-19 17:25:49 -05:00
let direction = "buy" ;
2021-12-08 06:22:36 -05:00
let offer : OfferInfo = await postOffer ( alice , direction , tradeAmount , undefined ) ;
2021-11-19 17:25:49 -05:00
expect ( offer . getState ( ) ) . toEqual ( "AVAILABLE" ) ;
2021-10-15 13:17:52 -04:00
console . log ( "Alice done posting offer" ) ;
2021-12-14 13:04:02 -05:00
// TODO (woodser): test error message taking offer before posted
2021-10-15 13:17:52 -04:00
// bob sees offer
await wait ( WALLET_SYNC_PERIOD * 2 ) ;
2021-11-19 17:25:49 -05:00
let offerBob = getOffer ( await bob . getOffers ( direction ) , offer . getId ( ) ) ;
if ( ! offerBob ) throw new Error ( "Offer " + offer . getId ( ) + " was not found in peer's offers after posting" ) ;
2021-12-14 13:04:02 -05:00
expect ( offerBob . getState ( ) ) . toEqual ( "UNKNOWN" ) ; // TODO: offer state is not known?
// cannot take offer with invalid payment id
let aliceTradesBefore = await alice . getTrades ( ) ;
let bobTradesBefore = await bob . getTrades ( ) ;
try {
await bob . takeOffer ( offer . getId ( ) , "abc" ) ;
throw new Error ( "taking offer with invalid payment account id should fail" ) ;
} catch ( err ) {
assert . equal ( err . message , "payment account with id 'abc' not found" ) ;
assert . equal ( ( await alice . getTrades ( ) ) . length , aliceTradesBefore . length , "alice should have not new trades" ) ;
assert . equal ( ( await bob . getTrades ( ) ) . length , bobTradesBefore . length , "bob should not have new trades" ) ; // TODO (woodser): also test balance unreserved
}
2021-11-19 17:25:49 -05:00
// bob creates ethereum payment account
let testAccount = TEST_CRYPTO_ACCOUNTS [ 0 ] ;
let ethPaymentAccount : PaymentAccount = await bob . createCryptoPaymentAccount (
testAccount . currencyCode + " " + testAccount . address . substr ( 0 , 8 ) + "... " + GenUtils . getUUID ( ) ,
testAccount . currencyCode ,
testAccount . address ) ;
2021-10-15 13:17:52 -04:00
2021-09-19 14:00:22 -04:00
// bob takes offer
let startTime = Date . now ( ) ;
console . log ( "Bob taking offer" ) ;
2021-12-14 13:04:02 -05:00
let trade : TradeInfo = await bob . takeOffer ( offer . getId ( ) , ethPaymentAccount . getId ( ) ) ; // TODO (woodser): this returns before trade is fully initialized
2021-11-19 17:25:49 -05:00
expect ( trade . getPhase ( ) ) . toEqual ( "DEPOSIT_PUBLISHED" ) ;
2021-09-19 14:00:22 -04:00
console . log ( "Bob done taking offer in " + ( Date . now ( ) - startTime ) + " ms" ) ;
// bob can get trade
let fetchedTrade : TradeInfo = await bob . getTrade ( trade . getTradeId ( ) ) ;
2021-11-19 17:25:49 -05:00
expect ( fetchedTrade . getPhase ( ) ) . toEqual ( "DEPOSIT_PUBLISHED" ) ;
2021-09-19 14:00:22 -04:00
// 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 ( ) ) ;
2021-11-19 17:25:49 -05:00
expect ( fetchedTrade . getPhase ( ) ) . toEqual ( "DEPOSIT_PUBLISHED" ) ;
2021-09-19 14:00:22 -04:00
// 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" ) ;
2021-11-19 17:25:49 -05:00
2021-09-19 14:00:22 -04:00
// alice notified to send payment
2021-11-19 17:25:49 -05:00
await wait ( WALLET_SYNC_PERIOD * 2 ) ;
fetchedTrade = await alice . getTrade ( trade . getTradeId ( ) ) ;
expect ( fetchedTrade . getIsDepositConfirmed ( ) ) . toBe ( true ) ;
expect ( fetchedTrade . getPhase ( ) ) . toEqual ( "DEPOSIT_CONFIRMED" ) ; // TODO (woodser): rename to DEPOSIT_UNLOCKED, have phase for when deposit txs confirm?
fetchedTrade = await bob . getTrade ( trade . getTradeId ( ) ) ;
expect ( fetchedTrade . getIsDepositConfirmed ( ) ) . toBe ( true ) ;
expect ( fetchedTrade . getPhase ( ) ) . toEqual ( "DEPOSIT_CONFIRMED" ) ;
2021-09-19 14:00:22 -04:00
// alice indicates payment is sent
await alice . confirmPaymentStarted ( trade . getTradeId ( ) ) ;
2021-11-19 17:25:49 -05:00
fetchedTrade = await alice . getTrade ( trade . getTradeId ( ) ) ;
expect ( fetchedTrade . getPhase ( ) ) . toEqual ( "FIAT_SENT" ) ; // TODO (woodser): rename to PAYMENT_SENT
2021-09-19 14:00:22 -04:00
// bob notified payment is sent
await wait ( MAX_TIME_PEER_NOTICE ) ;
2021-11-19 17:25:49 -05:00
fetchedTrade = await bob . getTrade ( trade . getTradeId ( ) ) ;
expect ( fetchedTrade . getPhase ( ) ) . toEqual ( "FIAT_SENT" ) ; // TODO (woodser): rename to PAYMENT_SENT
2021-09-19 14:00:22 -04:00
// bob confirms payment is received
await bob . confirmPaymentReceived ( trade . getTradeId ( ) ) ;
fetchedTrade = await bob . getTrade ( trade . getTradeId ( ) ) ;
2021-11-19 17:25:49 -05:00
expect ( fetchedTrade . getPhase ( ) ) . toEqual ( "PAYOUT_PUBLISHED" ) ;
2021-09-19 14:00:22 -04:00
// alice notified trade is complete and of balance changes
2021-11-19 17:25:49 -05:00
await wait ( WALLET_SYNC_PERIOD * 2 ) ;
fetchedTrade = await alice . getTrade ( trade . getTradeId ( ) ) ;
expect ( fetchedTrade . getPhase ( ) ) . toEqual ( "PAYOUT_PUBLISHED" ) ;
// test balances after payout tx
let aliceBalancesAfter = await alice . getBalances ( ) ;
bobBalancesAfter = await bob . getBalances ( ) ;
let aliceFee = BigInt ( aliceBalancesBefore . getBalance ( ) ) + tradeAmount - BigInt ( aliceBalancesAfter . getBalance ( ) ) ;
let bobFee = BigInt ( bobBalancesBefore . getBalance ( ) ) - tradeAmount - BigInt ( bobBalancesAfter . getBalance ( ) ) ;
expect ( aliceFee ) . toBeLessThanOrEqual ( MAX_FEE ) ;
expect ( aliceFee ) . toBeGreaterThan ( BigInt ( "0" ) ) ;
expect ( bobFee ) . toBeLessThanOrEqual ( MAX_FEE ) ;
expect ( bobFee ) . toBeGreaterThan ( BigInt ( "0" ) ) ;
2021-09-17 09:33:58 -04:00
} ) ;
// ------------------------------- HELPERS ------------------------------------
2021-12-08 06:22:36 -05:00
/ * *
2021-12-14 13:04:02 -05:00
* Start Haveno trader daemons as processes .
2021-12-08 06:22:36 -05:00
*
2021-12-14 13:04:02 -05:00
* @param { number } numProcesses - number of trader processes to start
* @param { boolean } enableLogging - specifies if process output should be logged
* @return { HavenoDaemon [ ] } clients connected to the started Haveno processes
* /
async function startTraderProcesses ( numProcesses : number , enableLogging : boolean ) : Promise < HavenoDaemon [ ] > {
let traderPromises : Promise < HavenoDaemon > [ ] = [ ] ;
for ( let i = 0 ; i < numProcesses ; i ++ ) traderPromises . push ( startTraderProcess ( enableLogging ) ) ;
return Promise . all ( traderPromises ) ;
}
/ * *
* Start a Haveno trader daemon as a process .
*
* @param { boolean } enableLogging - specifies if process output should be logged
2021-12-08 06:22:36 -05:00
* @return { HavenoDaemon } the client connected to the started Haveno process
* /
2021-12-14 13:04:02 -05:00
async function startTraderProcess ( enableLogging : boolean ) : Promise < HavenoDaemon > {
2021-12-08 06:22:36 -05:00
// iterate to find unused proxy port
for ( let port of Array . from ( PROXY_PORTS . keys ( ) ) ) {
if ( port === "8080" || port === "8081" ) continue ; // reserved for alice and bob
// start haveno process on unused port
2021-12-14 13:04:02 -05:00
if ( ! GenUtils . arrayContains ( HAVENO_PROCESS_PORTS , port ) ) {
HAVENO_PROCESS_PORTS . push ( port ) ;
2021-12-08 06:22:36 -05:00
let appName = "haveno-XMR_STAGENET_trader_" + GenUtils . getUUID ( ) ;
let cmd : string [ ] = [
"./haveno-daemon" ,
"--baseCurrencyNetwork" , "XMR_STAGENET" ,
"--useLocalhostForP2P" , "true" ,
"--useDevPrivilegeKeys" , "true" ,
"--nodePort" , PROXY_PORTS . get ( port ) ! [ 1 ] ,
"--appName" , appName ,
"--apiPassword" , "apitest" ,
2021-12-14 13:04:02 -05:00
"--apiPort" , PROXY_PORTS . get ( port ) ! [ 0 ] ,
"--walletRpcBindPort" , await getFreePort ( ) + ""
2021-12-08 06:22:36 -05:00
] ;
2021-12-14 13:04:02 -05:00
let havenod = await HavenoDaemon . startProcess ( HAVENO_PATH , cmd , "http://localhost:" + port , enableLogging ) ;
2021-12-08 06:22:36 -05:00
HAVENO_PROCESSES . push ( havenod ) ;
return havenod ;
}
}
throw new Error ( "No unused test ports available" ) ;
}
/ * *
2021-12-14 13:04:02 -05:00
* Get a free port .
* /
async function getFreePort ( ) : Promise < number > {
return new Promise ( function ( resolve , reject ) {
let srv = net . createServer ( ) ;
srv . listen ( 0 , function ( ) {
let port = srv . address ( ) . port ;
srv . close ( function ( ) {
resolve ( port ) ;
} )
} ) ;
} ) ;
}
/ * *
* Stop a Haveno daemon process and release its ports for reuse .
2021-12-08 06:22:36 -05:00
* /
async function stopHavenoProcess ( havenod : HavenoDaemon ) {
await havenod . stopProcess ( ) ;
GenUtils . remove ( HAVENO_PROCESSES , havenod ) ;
2021-12-14 13:04:02 -05:00
GenUtils . remove ( HAVENO_PROCESS_PORTS , new URL ( havenod . getUrl ( ) ) . port ) ;
2021-12-08 06:22:36 -05:00
}
2021-11-12 10:26:22 -05:00
/ * *
* Open or create funding wallet .
* /
async function initFundingWallet() {
// init client connected to monero-wallet-rpc
2021-12-08 06:22:36 -05:00
fundingWallet = await monerojs . connectToWalletRpc ( FUNDING_WALLET_URL , FUNDING_WALLET_USERNAME , FUNDING_WALLET_PASSWORD ) ;
2021-11-12 10:26:22 -05:00
// check if wallet is open
let walletIsOpen = false
try {
await fundingWallet . getPrimaryAddress ( ) ;
walletIsOpen = true ;
} catch ( err ) { }
// open wallet if necessary
if ( ! walletIsOpen ) {
// attempt to open funding wallet
try {
2021-12-08 06:22:36 -05:00
await fundingWallet . openWallet ( { path : DEFAULT_FUNDING_WALLET_PATH , password : FUNDING_WALLET_PASSWORD } ) ;
2021-11-12 10:26:22 -05:00
} catch ( e ) {
if ( ! ( e instanceof monerojs . MoneroRpcError ) ) throw e ;
// -1 returned when wallet does not exist or fails to open e.g. it's already open by another application
if ( e . getCode ( ) === - 1 ) {
// create wallet
2021-12-08 06:22:36 -05:00
await fundingWallet . createWallet ( { path : DEFAULT_FUNDING_WALLET_PATH , password : FUNDING_WALLET_PASSWORD } ) ;
2021-11-12 10:26:22 -05:00
} else {
throw e ;
}
}
}
}
/ * *
* Wait for unlocked balance in wallet or Haveno daemon .
* /
async function waitForUnlockedBalance ( amount : bigint , . . . wallets : any [ ] ) {
// wrap common wallet functionality for tests
class WalletWrapper {
_wallet : any ;
constructor ( wallet : any ) {
this . _wallet = wallet ;
}
async getUnlockedBalance ( ) : Promise < bigint > {
if ( this . _wallet instanceof HavenoDaemon ) return BigInt ( ( await this . _wallet . getBalances ( ) ) . getUnlockedBalance ( ) ) ;
else return BigInt ( ( await this . _wallet . getUnlockedBalance ( ) ) . toString ( ) ) ;
}
async getLockedBalance ( ) : Promise < bigint > {
if ( this . _wallet instanceof HavenoDaemon ) return BigInt ( ( await this . _wallet . getBalances ( ) ) . getLockedBalance ( ) ) ;
else return BigInt ( ( await this . _wallet . getBalance ( ) ) . toString ( ) ) - await this . getUnlockedBalance ( ) ;
}
async getDepositAddress ( ) : Promise < string > {
if ( this . _wallet instanceof HavenoDaemon ) return await this . _wallet . getNewDepositSubaddress ( ) ;
2021-12-14 13:04:02 -05:00
else return ( await this . _wallet . createSubaddress ( ) ) . getAddress ( ) ;
2021-11-12 10:26:22 -05:00
}
}
// wrap wallets
for ( let i = 0 ; i < wallets . length ; i ++ ) wallets [ i ] = new WalletWrapper ( wallets [ i ] ) ;
// fund wallets with insufficient balance
let miningNeeded = false ;
let fundConfig = new MoneroTxConfig ( ) . setAccountIndex ( 0 ) . setRelay ( true ) ;
for ( let wallet of wallets ) {
let unlockedBalance = await wallet . getUnlockedBalance ( ) ;
if ( unlockedBalance < amount ) miningNeeded = true ;
let depositNeeded : bigint = amount - unlockedBalance - await wallet . getLockedBalance ( ) ;
2021-11-19 17:25:49 -05:00
if ( depositNeeded > BigInt ( "0" ) && wallet . _wallet !== fundingWallet ) fundConfig . addDestination ( await wallet . getDepositAddress ( ) , depositNeeded * BigInt ( "10" ) ) ; // deposit 10 times more than needed
2021-11-12 10:26:22 -05:00
}
if ( fundConfig . getDestinations ( ) ) {
2021-12-08 06:22:36 -05:00
await waitForUnlockedBalance ( MINIMUM_FUNDING , fundingWallet ) ; // TODO (woodser): wait for enough to cover tx amount + fee
2021-11-12 10:26:22 -05:00
try { await fundingWallet . createTx ( fundConfig ) ; }
catch ( err ) { throw new Error ( "Error funding wallets: " + err . message ) ; }
}
// done if all wallets have sufficient unlocked balance
if ( ! miningNeeded ) return ;
// wait for funds to unlock
console . log ( "Mining for unlocked balance of " + amount ) ;
await startMining ( ) ;
2021-12-14 13:04:02 -05:00
let promises : Promise < void > [ ] = [ ] ;
2021-11-12 10:26:22 -05:00
for ( let wallet of wallets ) {
promises . push ( new Promise ( async function ( resolve , reject ) {
let taskLooper : any = new TaskLooper ( async function ( ) {
if ( await wallet . 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 ( ) ;
}
async function startMining() {
try {
2021-11-19 17:25:49 -05:00
await monerod . startMining ( await fundingWallet . getPrimaryAddress ( ) , 3 ) ;
2021-11-12 10:26:22 -05:00
} catch ( err ) {
if ( err . message !== "Already mining" ) throw err ;
}
}
async function wait ( durationMs : number ) {
return new Promise ( function ( resolve ) { setTimeout ( resolve , durationMs ) ; } ) ;
}
2021-12-14 13:04:02 -05:00
async function createCryptoPaymentAccount ( trader : HavenoDaemon ) : Promise < PaymentAccount > {
let testAccount = TEST_CRYPTO_ACCOUNTS [ 0 ] ;
let paymentAccount : PaymentAccount = await trader . createCryptoPaymentAccount (
2021-12-08 06:22:36 -05:00
testAccount . currencyCode + " " + testAccount . address . substr ( 0 , 8 ) + "... " + GenUtils . getUUID ( ) ,
testAccount . currencyCode ,
testAccount . address ) ;
2021-12-14 13:04:02 -05:00
return paymentAccount ;
}
async function postOffer ( maker : HavenoDaemon , direction : string , amount : bigint , paymentAccountId : string | undefined ) {
// create payment account if not given
if ( ! paymentAccountId ) paymentAccountId = ( await createCryptoPaymentAccount ( maker ) ) . getId ( ) ;
2021-09-14 08:30:22 -04:00
// get unlocked balance before reserving offer
2021-11-19 17:25:49 -05:00
let unlockedBalanceBefore : bigint = BigInt ( ( await maker . getBalances ( ) ) . getUnlockedBalance ( ) ) ;
2021-09-14 08:30:22 -04:00
// post offer
2021-11-19 17:25:49 -05:00
let offer : OfferInfo = await maker . postOffer ( "eth" ,
direction , // buy or sell xmr for eth
12.378981 , // price TODO: price is optional? price string gets converted to long?
true , // use market price
0.02 , // market price margin, e.g. within 2%
amount , // amount
BigInt ( "150000000000" ) , // min amount
0.15 , // buyer security deposit, e.g. 15%
2021-12-08 06:22:36 -05:00
paymentAccountId , // payment account id
2021-11-19 17:25:49 -05:00
undefined ) ; // trigger price // TODO: fails if there is a decimal, gets converted to long?
2021-09-14 08:30:22 -04:00
testOffer ( offer ) ;
// unlocked balance has decreased
2021-11-19 17:25:49 -05:00
let unlockedBalanceAfter : bigint = BigInt ( ( await maker . getBalances ( ) ) . getUnlockedBalance ( ) ) ;
2021-10-15 13:17:52 -04:00
if ( unlockedBalanceAfter === unlockedBalanceBefore ) throw new Error ( "unlocked balance did not change after posting offer" ) ;
2021-09-14 08:30:22 -04:00
// offer is included in my offers only
2021-11-19 17:25:49 -05:00
if ( ! getOffer ( await maker . getMyOffers ( direction ) , offer . getId ( ) ) ) {
console . log ( "OK, we couldn't get the offer, let's wait" ) ;
await wait ( 10000 ) ;
if ( ! getOffer ( await maker . getMyOffers ( direction ) , offer . getId ( ) ) ) throw new Error ( "Offer " + offer . getId ( ) + " was not found in my offers" ) ;
else console . log ( "The offer finally posted!" ) ;
}
if ( getOffer ( await maker . getOffers ( direction ) , offer . getId ( ) ) ) throw new Error ( "My offer " + offer . getId ( ) + " should not appear in available offers" ) ;
2021-09-14 08:30:22 -04:00
2021-09-17 09:33:58 -04:00
return offer ;
}
2021-09-14 08:30:22 -04:00
2021-09-19 14:00:22 -04:00
function getBalancesStr ( balances : XmrBalanceInfo ) {
2021-11-19 17:25:49 -05:00
return "[balance=" + balances . getBalance ( ) + ", unlocked balance=" + balances . getUnlockedBalance ( ) + ", locked balance=" + balances . getLockedBalance ( ) + ", reserved offer balance=" + balances . getReservedOfferBalance ( ) + ", reserved trade balance: " + balances . getReservedTradeBalance ( ) + "]" ;
2021-09-17 09:33:58 -04:00
}
2021-09-14 08:30:22 -04:00
function getOffer ( offers : OfferInfo [ ] , id : string ) : OfferInfo | undefined {
return offers . find ( offer = > offer . getId ( ) === id ) ;
}
2021-10-22 13:51:57 -04:00
function testCryptoPaymentAccount ( paymentAccount : PaymentAccount ) {
2021-11-11 13:48:31 -05:00
expect ( paymentAccount . getId ( ) . length ) . toBeGreaterThan ( 0 ) ;
expect ( paymentAccount . getAccountName ( ) . length ) . toBeGreaterThan ( 0 ) ;
expect ( paymentAccount . getPaymentAccountPayload ( ) ! . getCryptoCurrencyAccountPayload ( ) ! . getAddress ( ) . length ) . toBeGreaterThan ( 0 ) ;
expect ( paymentAccount . getSelectedTradeCurrency ( ) ! . getCode ( ) . length ) . toBeGreaterThan ( 0 ) ;
2021-10-22 13:51:57 -04:00
expect ( paymentAccount . getTradeCurrenciesList ( ) . length ) . toEqual ( 1 ) ;
let tradeCurrency = paymentAccount . getTradeCurrenciesList ( ) [ 0 ] ;
2021-11-11 13:48:31 -05:00
expect ( tradeCurrency . getName ( ) . length ) . toBeGreaterThan ( 0 ) ;
2021-10-22 13:51:57 -04:00
expect ( tradeCurrency . getCode ( ) ) . toEqual ( paymentAccount . getSelectedTradeCurrency ( ) ! . getCode ( ) ) ;
2021-09-14 08:27:45 -04:00
}
function testOffer ( offer : OfferInfo ) {
2021-11-11 13:48:31 -05:00
expect ( offer . getId ( ) . length ) . toBeGreaterThan ( 0 ) ;
2021-09-12 09:39:21 -04:00
// TODO: test rest of offer
2021-11-15 18:13:28 +01:00
}