general rebase in order to update payment methods and desktop app
Co-authored-by: Alva Swanson <alvasw@protonmail.com> Co-authored-by: andyheko <haoen.ko@gmail.com> Co-authored-by: Bisq GitHub Admin <51445974+bisq-github-admin-3@users.noreply.github.com> Co-authored-by: BtcContributor <79100296+BtcContributor@users.noreply.github.com> Co-authored-by: cd2357 <cd2357@users.noreply.github.com> Co-authored-by: chimp1984 <chimp1984@gmx.com> Co-authored-by: Chris Beams <chris@beams.io> Co-authored-by: Christoph Atteneder <christoph.atteneder@gmail.com> Co-authored-by: Devin Bileck <603793+devinbileck@users.noreply.github.com> Co-authored-by: ghubstan <36207203+ghubstan@users.noreply.github.com> Co-authored-by: Huey <hueydane@gmail.com> Co-authored-by: Jakub Loucký <jakub.loucky@outlook.cz> Co-authored-by: jmacxx <47253594+jmacxx@users.noreply.github.com> Co-authored-by: KanoczTomas <tomas.kanocz@cnl.sk> Co-authored-by: m52go <735155+m52go@users.noreply.github.com> Co-authored-by: Marcus0x <marcus0x@xrhodium.org> Co-authored-by: MarnixCroes <93143998+MarnixCroes@users.noreply.github.com> Co-authored-by: Martin Harrigan <martinharrigan@gmail.com> Co-authored-by: MwithM <50149324+MwithM@users.noreply.github.com> Co-authored-by: sqrrm <sqrrm@users.noreply.github.com> Co-authored-by: Stan <36207203+ghubstan@users.noreply.github.com> Co-authored-by: Stephan Oeste <emzy@emzy.de> Co-authored-by: Steven Barclay <stejbac@gmail.com> Co-authored-by: WAT <shiido.it@gmail.com> Co-authored-by: wiz <j@wiz.biz> Co-authored-by: xyzmaker123 <84982606+xyzmaker123@users.noreply.github.com>
This commit is contained in:
parent
15a1fe8a36
commit
88578bed10
@ -19,7 +19,7 @@ option adjustments to compensate.
|
||||
|
||||
**Java SDK**: Version 10, 11, or 12
|
||||
|
||||
**Bitcoin-Core**: Version 0.19, 0.20, or 0.21
|
||||
**Bitcoin-Core**: Version 0.19 - 22
|
||||
|
||||
**Git Client**
|
||||
|
||||
@ -252,9 +252,9 @@ To remove a custom withdrawal transaction fee rate preference, and revert to the
|
||||
$ ./bisq-cli --password=xyz unsettxfeerate
|
||||
```
|
||||
|
||||
### Creating Test Payment Accounts
|
||||
### Creating Test Fiat Payment Accounts
|
||||
|
||||
Creating a payment account using the Api involves three steps:
|
||||
Creating a fiat payment account using the Api involves three steps:
|
||||
|
||||
1. Find the payment-method-id for the payment account type you wish to create. For example, if you want to
|
||||
create a face-to-face type payment account, find the face-to-face payment-method-id (`F2F`):
|
||||
@ -286,6 +286,21 @@ Creating a payment account using the Api involves three steps:
|
||||
$ ./bisq-cli --password=xyz --port=9998 getpaymentaccts
|
||||
```
|
||||
|
||||
### Creating Test Altcoin Payment Accounts
|
||||
|
||||
Unlike more complex fiat payment account setups, the `createcryptopaymentacct` command does not require a json form.
|
||||
|
||||
#### XMR Altcoin Payment Accounts
|
||||
|
||||
To create an XMR Altcoin payment account associated with example XMR address
|
||||
`44G4jWmSvTEfifSUZzTDnJVLPvYATmq9XhhtDqUof1BGCLceG82EQsVYG9Q9GN4bJcjbAJEc1JD1m5G7iK4UPZqACubV4Mq`:
|
||||
```
|
||||
$ ./bisq-cli --password=xyz --port=9999 createcryptopaymentacct --account-name=XMR-Account \
|
||||
--currency-code=XMR
|
||||
--address=44G4jWmSvTEfifSUZzTDnJVLPvYATmq9XhhtDqUof1BGCLceG82EQsVYG9Q9GN4bJcjbAJEc1JD1m5G7iK4UPZqACubV4Mq
|
||||
```
|
||||
|
||||
|
||||
### Creating Offers
|
||||
|
||||
The createoffer command is the Api's most complex command (so far), but CLI posix-style options are self-explanatory,
|
||||
@ -297,31 +312,29 @@ $ ./bisq-cli --password=xyz --port=9998 createoffer --help
|
||||
#### Examples
|
||||
|
||||
The `trade-simulation.sh` script described above is an easy way to figure out how to use this command.
|
||||
In a previous example, Alice created a BUY/ EUR offer to buy 0.125 BTC at a fixed price of 30,800 EUR,
|
||||
and pay the Bisq maker fee in BTC. Alice had already created an EUR face-to-face payment account with id
|
||||
In a previous example, Alice created a BUY/ EUR offer to buy 0.125 BTC at a fixed price of 30,800 EUR.
|
||||
Alice had already created an EUR face-to-face payment account with id
|
||||
`f3c1ec8b-9761-458d-b13d-9039c6892413`, and used this `createoffer` command:
|
||||
```
|
||||
$ ./bisq-cli --password=xyz --port=9998 createoffer \
|
||||
--payment-account=f3c1ec8b-9761-458d-b13d-9039c6892413 \
|
||||
--payment-account-id=f3c1ec8b-9761-458d-b13d-9039c6892413 \
|
||||
--direction=BUY \
|
||||
--currency-code=EUR \
|
||||
--amount=0.125 \
|
||||
--fixed-price=30800 \
|
||||
--security-deposit=15.0 \
|
||||
--fee-currency=BTC
|
||||
--security-deposit=15.0
|
||||
```
|
||||
|
||||
If Alice was in Japan, and wanted to create an offer to sell 0.125 BTC at 0.5% above the current market JPY price,
|
||||
putting up a 15% security deposit, the `createoffer` command to do that would be:
|
||||
```
|
||||
$ ./bisq-cli --password=xyz --port=9998 createoffer \
|
||||
--payment-account=f3c1ec8b-9761-458d-b13d-9039c6892413 \
|
||||
--payment-account-id=f3c1ec8b-9761-458d-b13d-9039c6892413 \
|
||||
--direction=SELL \
|
||||
--currency-code=JPY \
|
||||
--amount=0.125 \
|
||||
--market-price-margin=0.5 \
|
||||
--security-deposit=15.0 \
|
||||
--fee-currency=BTC
|
||||
--security-deposit=15.0
|
||||
```
|
||||
|
||||
The `trade-simulation.sh` script options that would generate the previous `createoffer` example is:
|
||||
@ -340,7 +353,7 @@ $ ./bisq-cli --password=xyz --port=9998 getmyoffers --direction=<BUY|SELL> --cur
|
||||
|
||||
To look at a specific offer you created:
|
||||
```
|
||||
$ ./bisq-cli --password=xyz --port=9998 getmyoffer --offer-id=<offer-id>
|
||||
$ ./bisq-cli --password=xyz --port=9998 getoffer --offer-id=<offer-id>
|
||||
```
|
||||
|
||||
### Browsing Available Offers
|
||||
@ -365,8 +378,116 @@ The offer will be removed from other Bisq users' offer views, and paid transacti
|
||||
|
||||
### Editing an Existing Offer
|
||||
|
||||
Editing existing offers is not yet supported. You can cancel and re-create an offer, but paid transaction fees
|
||||
for the canceled offer will be forfeited.
|
||||
Offers you create can be edited in various ways:
|
||||
|
||||
- Disable or re-enable an offer.
|
||||
- Change an offer's price model and disable (or re-enable) it.
|
||||
- Change a market price margin based offer to a fixed price offer.
|
||||
- Change a market price margin based offer's price margin.
|
||||
- Change, set, or remove a trigger price on a market price margin based offer.
|
||||
- Change a market price margin based offer's price margin and trigger price.
|
||||
- Change a market price margin based offer's price margin and remove its trigger price.
|
||||
- Change a fixed price offer to a market price margin based offer.
|
||||
- Change a fixed price offer's fixed price.
|
||||
|
||||
_Note: the API does not support editing an offer's payment account._
|
||||
|
||||
The subsections below contain examples related to specific use cases.
|
||||
|
||||
#### Enable and Disable Offer
|
||||
|
||||
Existing offers you create can be disabled (removed from offer book) and re-enabled (re-published to offer book).
|
||||
|
||||
To disable an offer:
|
||||
```
|
||||
./bisq-cli --password=xyz --port=9998 editoffer \
|
||||
--offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||
--enable=false
|
||||
```
|
||||
|
||||
To enable an offer:
|
||||
```
|
||||
./bisq-cli --password=xyz --port=9998 editoffer \
|
||||
--offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||
--enable=true
|
||||
```
|
||||
|
||||
#### Change Offer Pricing Model
|
||||
The `editoffer` command can be used to change an existing market price margin based offer to a fixed price offer,
|
||||
and vice-versa.
|
||||
|
||||
##### Change Market Price Margin Based to Fixed Price Offer
|
||||
Suppose you used `createoffer` to create a market price margin based offer as follows:
|
||||
```
|
||||
$ ./bisq-cli --password=xyz --port=9998 createoffer \
|
||||
--payment-account-id=f3c1ec8b-9761-458d-b13d-9039c6892413 \
|
||||
--direction=SELL \
|
||||
--currency-code=JPY \
|
||||
--amount=0.125 \
|
||||
--market-price-margin=0.5 \
|
||||
--security-deposit=15.0
|
||||
```
|
||||
To change the market price margin based offer to a fixed price offer:
|
||||
```
|
||||
./bisq-cli --password=xyz --port=9998 editoffer \
|
||||
--offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||
--fixed-price=3960000.5555
|
||||
```
|
||||
|
||||
##### Change Fixed Price Offer to Market Price Margin Based Offer
|
||||
Suppose you used `createoffer` to create a fixed price offer as follows:
|
||||
```
|
||||
$ ./bisq-cli --password=xyz --port=9998 createoffer \
|
||||
--payment-account-id=f3c1ec8b-9761-458d-b13d-9039c6892413 \
|
||||
--direction=SELL \
|
||||
--currency-code=JPY \
|
||||
--amount=0.125 \
|
||||
--fixed-price=3960000.0000 \
|
||||
--security-deposit=15.0
|
||||
```
|
||||
To change the fixed price offer to a market price margin based offer:
|
||||
```
|
||||
./bisq-cli --password=xyz --port=9998 editoffer \
|
||||
--offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||
--market-price-margin=0.5
|
||||
```
|
||||
Alternatively, you can also set a trigger price on the re-published, market price margin based offer.
|
||||
A trigger price on a SELL offer causes the offer to be automatically disabled when the market price
|
||||
falls below the trigger price. In the `editoffer` example below, the SELL offer will be disabled when
|
||||
the JPY market price falls below 3960000.0000.
|
||||
|
||||
```
|
||||
./bisq-cli --password=xyz --port=9998 editoffer \
|
||||
--offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||
--market-price-margin=0.5 \
|
||||
--trigger-price=3960000.0000
|
||||
```
|
||||
On a BUY offer, a trigger price causes the BUY offer to be automatically disabled when the market price
|
||||
rises above the trigger price.
|
||||
|
||||
_Note: Disabled offers never automatically re-enable; they can only be manually re-enabled via
|
||||
`editoffer --offer-id=<id> --enable=true`._
|
||||
|
||||
#### Remove Trigger Price
|
||||
To remove a trigger price on a market price margin based offer, set the trigger price to 0:
|
||||
```
|
||||
./bisq-cli --password=xyz --port=9998 editoffer \
|
||||
--offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||
--trigger-price=0
|
||||
```
|
||||
|
||||
#### Change Disabled Offer's Pricing Model and Enable It
|
||||
You can use `editoffer` to simultaneously change an offer's price details and disable or re-enable it.
|
||||
|
||||
Suppose you have a disabled, fixed price offer, and want to change it to a market price margin based offer, set
|
||||
a trigger price, and re-enable it:
|
||||
```
|
||||
./bisq-cli --password=xyz --port=9998 editoffer \
|
||||
--offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||
--market-price-margin=0.5 \
|
||||
--trigger-price=3960000.0000 \
|
||||
--enable=true
|
||||
```
|
||||
|
||||
### Taking Offers
|
||||
|
||||
@ -377,16 +498,14 @@ A CLI user browses available offers with the getoffers command. For example, th
|
||||
$ ./bisq-cli --password=xyz --port=9998 getoffers --direction=SELL --currency-code=EUR
|
||||
```
|
||||
|
||||
And takes one of the available offers with an EUR payment account ( id `fe20cdbd-22be-4b8a-a4b6-d2608ff09d6e`)
|
||||
Then takes one of the available offers with an EUR payment account ( id `fe20cdbd-22be-4b8a-a4b6-d2608ff09d6e`)
|
||||
with the `takeoffer` command:
|
||||
```
|
||||
$ ./bisq-cli --password=xyz --port=9998 takeoffer \
|
||||
--offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||
--payment-account=fe20cdbd-22be-4b8a-a4b6-d2608ff09d6e \
|
||||
--fee-currency=btc
|
||||
--payment-account-id=fe20cdbd-22be-4b8a-a4b6-d2608ff09d6e
|
||||
```
|
||||
The taken offer will be used to create a trade contract. The next section describes how to use the Api to execute
|
||||
the trade.
|
||||
The next section describes how to use the Api to execute a trade.
|
||||
|
||||
### Completing Trade Protocol
|
||||
|
||||
@ -429,19 +548,19 @@ protocol completed. There are three CLI commands that must be performed in coor
|
||||
```
|
||||
confirmpaymentstarted Buyer sends seller a message confirming payment has been sent.
|
||||
confirmpaymentreceived Seller sends buyer a message confirming payment has been received.
|
||||
keepfunds Keep trade proceeds in their Bisq wallets.
|
||||
closetrade Set trade state to CLOSED, and keep trade proceeds in user's Bisq wallet.
|
||||
OR
|
||||
withdrawfunds Send trade proceeds to an external wallet.
|
||||
withdrawfunds Set trade state to CLOSED, and send trade proceeds to an external wallet.
|
||||
```
|
||||
The last two mutually exclusive commands (`keepfunds` or `withdrawfunds`) may seem unnecessary, but they are critical
|
||||
because they inform the Bisq node that a trade’s state can be set to `CLOSED`. Please close out your trades with one
|
||||
The last two mutually exclusive commands (`closetrade` or `withdrawfunds`) may seem unnecessary, but they are critical
|
||||
because they tell the Bisq node to set a completed trade’s state `CLOSED`. Please close out your trades with one
|
||||
or the other command.
|
||||
|
||||
Each of the CLI commands above takes one argument: `--trade-id=<trade-id>`:
|
||||
```
|
||||
$ ./bisq-cli --password=xyz --port=9998 confirmpaymentstarted --trade-id=<trade-id>
|
||||
$ ./bisq-cli --password=xyz --port=9999 confirmpaymentreceived --trade-id=<trade-id>
|
||||
$ ./bisq-cli --password=xyz --port=9998 keepfunds --trade-id=<trade-id>
|
||||
$ ./bisq-cli --password=xyz --port=9998 closetrade --trade-id=<trade-id>
|
||||
$ ./bisq-cli --password=xyz --port=9999 withdrawfunds --trade-id=<trade-id> --address=<btc-address> [--memo=<"memo">]
|
||||
```
|
||||
|
||||
|
@ -4,7 +4,7 @@ The Java based API runs on Linux and OSX.
|
||||
|
||||
## Mainnet
|
||||
|
||||
To build from the source, clone the github repository found at `https://github.com/bisq-network/bisq`,
|
||||
To build from the source, clone the GitHub repository found at `https://github.com/bisq-network/bisq`,
|
||||
and build with gradle:
|
||||
|
||||
$ ./gradlew clean build
|
||||
|
@ -6,7 +6,7 @@
|
||||
#
|
||||
# Prerequisites:
|
||||
#
|
||||
# - Linux or OSX with bash, Java 10, or Java 11-12 (JDK language compatibility 10), and bitcoin-core (v0.19, v0.20, v0.21).
|
||||
# - Linux or OSX with bash, Java 11-15 (JDK language compatibility 11), and bitcoin-core (v0.19 - v22).
|
||||
#
|
||||
# - Bisq must be fully built with apitest dao setup files installed.
|
||||
# Build command: `./gradlew clean build :apitest:installDaoSetup`
|
||||
@ -134,7 +134,7 @@ sleeptraced 3
|
||||
|
||||
# Show Alice's new offer.
|
||||
printdate "ALICE: Looking at her new $DIRECTION $CURRENCY_CODE offer."
|
||||
CMD="$CLI_BASE --port=$ALICE_PORT getmyoffer --offer-id=$OFFER_ID"
|
||||
CMD="$CLI_BASE --port=$ALICE_PORT getoffer --offer-id=$OFFER_ID"
|
||||
printdate "ALICE CLI: $CMD"
|
||||
OFFER=$($CMD)
|
||||
exitoncommandalert $?
|
||||
|
@ -10,7 +10,7 @@
|
||||
#
|
||||
# Prerequisites:
|
||||
#
|
||||
# - Linux or OSX with bash, Java 10, or Java 11-12 (JDK language compatibility 10), and bitcoin-core (v0.19, v0.20, v0.21).
|
||||
# - Linux or OSX with bash, Java 11-15 (JDK language compatibility 11), and bitcoin-core (v0.19 - v22).
|
||||
#
|
||||
# - Bisq must be fully built with apitest dao setup files installed.
|
||||
# Build command: `./gradlew clean build :apitest:installDaoSetup`
|
||||
@ -94,7 +94,7 @@ while : ; do
|
||||
|
||||
# Show Alice's new offer.
|
||||
printdate "ALICE $ALICE_ROLE: Looking at her new $DIRECTION $CURRENCY_CODE offer."
|
||||
CMD="$CLI_BASE --port=$ALICE_PORT getmyoffer --offer-id=$OFFER_ID"
|
||||
CMD="$CLI_BASE --port=$ALICE_PORT getoffer --offer-id=$OFFER_ID"
|
||||
printdate "ALICE CLI: $CMD"
|
||||
OFFER=$($CMD)
|
||||
exitoncommandalert $?
|
||||
|
@ -193,7 +193,6 @@ gencreateoffercommand() {
|
||||
CMD+=" --market-price-margin=$MKT_PRICE_MARGIN"
|
||||
fi
|
||||
CMD+=" --security-deposit=15.0"
|
||||
CMD+=" --fee-currency=BTC"
|
||||
echo "$CMD"
|
||||
}
|
||||
|
||||
@ -368,7 +367,7 @@ waitfortradepaymentsent() {
|
||||
|
||||
IS_TRADE_PAYMENT_SENT=$(istradepaymentsent "$TRADE_DETAIL")
|
||||
exitoncommandalert $?
|
||||
printdate "$SELLER: Has buyer's fiat payment been initiated? $IS_TRADE_PAYMENT_SENT"
|
||||
printdate "$SELLER: Has buyer's payment been initiated? $IS_TRADE_PAYMENT_SENT"
|
||||
if [ "$IS_TRADE_PAYMENT_SENT" = "YES" ]
|
||||
then
|
||||
DONE=1
|
||||
@ -407,7 +406,7 @@ waitfortradepaymentreceived() {
|
||||
# but we do not need to simulate that in this regtest script.
|
||||
IS_TRADE_PAYMENT_SENT=$(istradepaymentreceived "$TRADE_DETAIL")
|
||||
exitoncommandalert $?
|
||||
printdate "$SELLER: Has buyer's payment been transferred to seller's fiat account? $IS_TRADE_PAYMENT_SENT"
|
||||
printdate "$SELLER: Has buyer's payment been transferred to seller's account? $IS_TRADE_PAYMENT_SENT"
|
||||
if [ "$IS_TRADE_PAYMENT_SENT" = "YES" ]
|
||||
then
|
||||
DONE=1
|
||||
@ -427,7 +426,7 @@ delayconfirmpaymentstarted() {
|
||||
PORT="$2"
|
||||
OFFER_ID="$3"
|
||||
RANDOM_WAIT=$(echo $[$RANDOM % 5 + 1])
|
||||
printdate "$PAYER: Sending fiat payment sent message to seller in $RANDOM_WAIT seconds..."
|
||||
printdate "$PAYER: Sending 'payment sent' message to seller in $RANDOM_WAIT seconds..."
|
||||
sleeptraced "$RANDOM_WAIT"
|
||||
CMD="$CLI_BASE --port=$PORT confirmpaymentstarted --trade-id=$OFFER_ID"
|
||||
printdate "$PAYER_CLI: $CMD"
|
||||
@ -446,7 +445,7 @@ delayconfirmpaymentreceived() {
|
||||
PORT="$2"
|
||||
OFFER_ID="$3"
|
||||
RANDOM_WAIT=$(echo $[$RANDOM % 5 + 1])
|
||||
printdate "$PAYEE: Sending fiat payment sent message to seller in $RANDOM_WAIT seconds..."
|
||||
printdate "$PAYEE: Sending 'payment sent' message to seller in $RANDOM_WAIT seconds..."
|
||||
sleeptraced "$RANDOM_WAIT"
|
||||
CMD="$CLI_BASE --port=$PORT confirmpaymentreceived --trade-id=$OFFER_ID"
|
||||
printdate "$PAYEE_CLI: $CMD"
|
||||
@ -457,11 +456,10 @@ delayconfirmpaymentreceived() {
|
||||
printbreak
|
||||
}
|
||||
|
||||
# This is a large function that should be broken up if it ever makes sense to not treat a trade
|
||||
# execution simulation as an atomic operation. But we are not testing api methods here, just
|
||||
# demonstrating how to use them to get through the trade protocol. It should work for any trade
|
||||
# between Bob & Alice, as long as Alice is maker, Bob is taker, and the offer to be taken is the
|
||||
# first displayed in Bob's getoffers command output.
|
||||
# This is a large function that might be split into smaller functions. But we are not testing
|
||||
# api methods here, just demonstrating how to use them to get through the V1 trade protocol with
|
||||
# the CLI. It should work for any trade between Bob & Alice, as long as Alice is maker, Bob is
|
||||
# taker, and the offer to be taken is the first displayed in Bob's getoffers command output.
|
||||
executetrade() {
|
||||
# Bob list available offers.
|
||||
printdate "BOB $BOB_ROLE: Looking at $DIRECTION $CURRENCY_CODE offers."
|
||||
@ -532,24 +530,27 @@ executetrade() {
|
||||
fi
|
||||
|
||||
# Generate some btc blocks
|
||||
printdate "Generating btc blocks after fiat transfer."
|
||||
printdate "Generating btc blocks after payment."
|
||||
genbtcblocks 2 2
|
||||
printbreak
|
||||
|
||||
# Complete the trade on the seller side.
|
||||
if [ "$DIRECTION" = "BUY" ]
|
||||
then
|
||||
printdate "BOB $BOB_ROLE: Closing trade by keeping funds in Bisq wallet."
|
||||
CMD="$CLI_BASE --port=$BOB_PORT keepfunds --trade-id=$OFFER_ID"
|
||||
printdate "BOB CLI: $CMD"
|
||||
else
|
||||
printdate "ALICE (taker): Closing trade by keeping funds in Bisq wallet."
|
||||
CMD="$CLI_BASE --port=$ALICE_PORT keepfunds --trade-id=$OFFER_ID"
|
||||
printdate "ALICE CLI: $CMD"
|
||||
fi
|
||||
# Complete the trade on both sides
|
||||
printdate "BOB $BOB_ROLE: Closing trade and keeping funds in Bisq wallet."
|
||||
CMD="$CLI_BASE --port=$BOB_PORT closetrade --trade-id=$OFFER_ID"
|
||||
printdate "BOB CLI: $CMD"
|
||||
KEEP_FUNDS_MSG=$($CMD)
|
||||
commandalert $? "Could close trade with keepfunds command."
|
||||
# Print the keepfunds command's console output.
|
||||
commandalert $? "Closed trade with closetrade command."
|
||||
# Print the closetrade command's console output.
|
||||
printdate "$KEEP_FUNDS_MSG"
|
||||
sleeptraced 3
|
||||
printbreak
|
||||
|
||||
printdate "ALICE (taker): Closing trade and keeping funds in Bisq wallet."
|
||||
CMD="$CLI_BASE --port=$ALICE_PORT closetrade --trade-id=$OFFER_ID"
|
||||
printdate "ALICE CLI: $CMD"
|
||||
KEEP_FUNDS_MSG=$($CMD)
|
||||
commandalert $? "Closed trade with closetrade command."
|
||||
# Print the closetrade command's console output.
|
||||
printdate "$KEEP_FUNDS_MSG"
|
||||
sleeptraced 3
|
||||
printbreak
|
||||
|
@ -1,13 +1,13 @@
|
||||
#! /bin/bash
|
||||
|
||||
# Runs fiat <-> btc trading scenarios using the API CLI with a local regtest bitcoin node.
|
||||
# Demonstrates a fiat <-> btc trade using the API CLI with a local regtest bitcoin node.
|
||||
#
|
||||
# A country code argument is used to create a country based face to face payment account for the simulated
|
||||
# trade, and the maker's face to face payment account's currency code is used when creating the offer.
|
||||
#
|
||||
# Prerequisites:
|
||||
#
|
||||
# - Linux or OSX with bash, Java 10, or Java 11-12 (JDK language compatibility 10), and bitcoin-core (v0.19, v0.20, or v0.21).
|
||||
# - Linux or OSX with bash, Java 11-15 (JDK language compatibility 11), and bitcoin-core (v0.19 - v22).
|
||||
#
|
||||
# - Bisq must be fully built with apitest dao setup files installed.
|
||||
# Build command: `./gradlew clean build :apitest:installDaoSetup`
|
||||
@ -26,15 +26,16 @@
|
||||
#
|
||||
# `$ apitest/scripts/trade-simulation.sh -d buy -c fr -m 3.00 -a 0.125`
|
||||
#
|
||||
# Script options: -d <direction> -c <country-code> -m <mkt-price-margin(%)> - f <fixed-price> -a <amount(btc)>
|
||||
# Script options: -d <direction> -c <country-code> -m <mkt-price-margin(%)> -f <fixed-price> -a <amount(btc)>
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# Create a buy/eur offer to buy 0.125 btc at a mkt-price-margin of 0%, using an Italy face to face payment account:
|
||||
# Create and take a buy/eur offer to buy 0.125 btc at a mkt-price-margin of 0%, using an Italy face to face
|
||||
# payment account:
|
||||
#
|
||||
# `$ apitest/scripts/trade-simulation.sh -d buy -c it -m 0.00 -a 0.125`
|
||||
#
|
||||
# Create a sell/eur offer to sell 0.125 btc at a fixed-price of 38,000 euros, using a France face to face
|
||||
# Create and take a sell/eur offer to sell 0.125 btc at a fixed-price of 38,000 euros, using a France face to face
|
||||
# payment account:
|
||||
#
|
||||
# `$ apitest/scripts/trade-simulation.sh -d sell -c fr -f 38000 -a 0.125`
|
||||
@ -53,8 +54,6 @@ printdate "Started $APP_BASE_NAME with parameters:"
|
||||
printscriptparams
|
||||
printbreak
|
||||
|
||||
registerdisputeagents
|
||||
|
||||
# Demonstrate how to create a country based, face to face account.
|
||||
showcreatepaymentacctsteps "Alice" "$ALICE_PORT"
|
||||
|
||||
@ -96,7 +95,7 @@ sleeptraced 3
|
||||
|
||||
# Show Alice's new offer.
|
||||
printdate "ALICE $ALICE_ROLE: Looking at her new $DIRECTION $CURRENCY_CODE offer."
|
||||
CMD="$CLI_BASE --port=$ALICE_PORT getmyoffer --offer-id=$OFFER_ID"
|
||||
CMD="$CLI_BASE --port=$ALICE_PORT getoffer --offer-id=$OFFER_ID"
|
||||
printdate "ALICE CLI: $CMD"
|
||||
OFFER=$($CMD)
|
||||
exitoncommandalert $?
|
||||
|
122
apitest/scripts/trade-xmr-simulation.sh
Executable file
122
apitest/scripts/trade-xmr-simulation.sh
Executable file
@ -0,0 +1,122 @@
|
||||
#! /bin/bash
|
||||
|
||||
# Runs xmr <-> btc trading scenarios using the API CLI with a local regtest bitcoin node.
|
||||
#
|
||||
# Prerequisites:
|
||||
#
|
||||
# - Linux or OSX with bash, Java 11-15 (JDK language compatibility 11), and bitcoin-core (v0.19 - v22).
|
||||
#
|
||||
# - Bisq must be fully built with apitest dao setup files installed.
|
||||
# Build command: `./gradlew clean build :apitest:installDaoSetup`
|
||||
#
|
||||
# - All supporting nodes must be run locally, in dev/dao/regtest mode:
|
||||
# bitcoind, seednode, arbdaemon, alicedaemon, bobdaemon
|
||||
#
|
||||
# These should be run using the apitest harness. From the root project dir, run:
|
||||
# `$ ./bisq-apitest --apiPassword=xyz --supportingApps=bitcoind,seednode,arbdaemon,alicedaemon,bobdaemon --shutdownAfterTests=false`
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# This script must be run from the root of the project, e.g.:
|
||||
#
|
||||
# `$ apitest/scripts/trade-xmr-simulation.sh -d buy -f 0.05 -a 0.125`
|
||||
#
|
||||
# Script options: -d <direction> -m <mkt-price-margin(%)> -f <fixed-price> -a <amount(btc)>
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# Create a buy/xmr offer to buy 0.125 btc at an xmr fixed-price of 0.05 btc, using an xmr payment account:
|
||||
#
|
||||
# `$ apitest/scripts/trade-xmr-simulation.sh -d buy -f 0.05 -a 0.125`
|
||||
#
|
||||
# Create a sell/xmr offer to sell 0.125 btc at at an xmr mkt-price-margin of 0%, using using an xmr payment account:
|
||||
#
|
||||
# `$ apitest/scripts/trade-xmr-simulation.sh -d sell -m 0.00 -a 0.125`
|
||||
|
||||
export APP_BASE_NAME=$(basename "$0")
|
||||
export APP_HOME=$(pwd -P)
|
||||
export APITEST_SCRIPTS_HOME="$APP_HOME/apitest/scripts"
|
||||
export CURRENCY_CODE="XMR"
|
||||
export ALICE_XMR_ADDRESS="44i8xZbd8ecaD6nQQrHjr1BwTp6QfGL22iWqHZKmU4QYSyr1F64XAxM4HgvQHxbny7ehfxemaA9LPDLz2wY3fxhB1bbMEco"
|
||||
export BOB_XMR_ADDRESS="48xdBkXaCosPxcWwXRZdSGc33M9tYu6k9ga56dqkNrgsjQuJX16xW2qTyWTZstJpXXj87dj5p4H3y1xAfoVjAysoAYrXh2N"
|
||||
|
||||
source "$APITEST_SCRIPTS_HOME/trade-simulation-env.sh"
|
||||
source "$APITEST_SCRIPTS_HOME/trade-simulation-utils.sh"
|
||||
|
||||
checksetup
|
||||
parsexmrscriptopts "$@"
|
||||
|
||||
printdate "Started $APP_BASE_NAME with parameters:"
|
||||
printscriptparams
|
||||
printbreak
|
||||
|
||||
registerdisputeagents
|
||||
|
||||
# Demonstrate how to create an XMR altcoin payment account.
|
||||
|
||||
printdate "Create Alice's XMR Trading Payment Account."
|
||||
# Note: Having problems passing a double quoted --account-name param to function.
|
||||
CMD="$CLI_BASE --port=$ALICE_PORT createcryptopaymentacct --account-name=Alice_XMR_Account"
|
||||
CMD+=" --currency-code=XMR --address=$ALICE_XMR_ADDRESS --trade-instant=false"
|
||||
printdate "ALICE CLI: $CMD"
|
||||
CMD_OUTPUT=$(createpaymentacct "$CMD")
|
||||
echo "$CMD_OUTPUT"
|
||||
printbreak
|
||||
export ALICE_ACCT_ID=$(getnewpaymentacctid "$CMD_OUTPUT")
|
||||
printdate "Alice's XMR payment-account-id: $ALICE_ACCT_ID"
|
||||
exitoncommandalert $?
|
||||
printbreak
|
||||
|
||||
printdate "Create Bob's XMR Trading Payment Account."
|
||||
# Note: Having problems passing a double quoted --account-name param to function.
|
||||
CMD="$CLI_BASE --port=$BOB_PORT createcryptopaymentacct --account-name=Bob_XMR_Account"
|
||||
CMD+=" --currency-code=XMR --address=$BOB_XMR_ADDRESS --trade-instant=false"
|
||||
printdate "BOB CLI: $CMD"
|
||||
CMD_OUTPUT=$(createpaymentacct "$CMD")
|
||||
echo "$CMD_OUTPUT"
|
||||
printbreak
|
||||
export BOB_ACCT_ID=$(getnewpaymentacctid "$CMD_OUTPUT")
|
||||
printdate "Bob's XMR payment-account-id: $BOB_ACCT_ID"
|
||||
exitoncommandalert $?
|
||||
printbreak
|
||||
|
||||
# Alice creates an offer.
|
||||
printdate "ALICE $ALICE_ROLE: Creating $DIRECTION $CURRENCY_CODE offer with payment acct $ALICE_ACCT_ID."
|
||||
CMD=$(gencreateoffercommand "$ALICE_PORT" "$ALICE_ACCT_ID")
|
||||
printdate "ALICE CLI: $CMD"
|
||||
OFFER_ID=$(createoffer "$CMD")
|
||||
exitoncommandalert $?
|
||||
printdate "ALICE $ALICE_ROLE: Created offer with id: $OFFER_ID."
|
||||
printbreak
|
||||
sleeptraced 3
|
||||
|
||||
# Show Alice's new offer.
|
||||
printdate "ALICE $ALICE_ROLE: Looking at her new $DIRECTION $CURRENCY_CODE offer."
|
||||
CMD="$CLI_BASE --port=$ALICE_PORT getoffer --offer-id=$OFFER_ID"
|
||||
printdate "ALICE CLI: $CMD"
|
||||
OFFER=$($CMD)
|
||||
exitoncommandalert $?
|
||||
echo "$OFFER"
|
||||
printbreak
|
||||
sleeptraced 3
|
||||
|
||||
# Generate some btc blocks.
|
||||
printdate "Generating btc blocks after publishing Alice's offer."
|
||||
genbtcblocks 3 1
|
||||
printbreak
|
||||
|
||||
# Go through the trade protocol.
|
||||
executetrade
|
||||
exitoncommandalert $?
|
||||
printbreak
|
||||
|
||||
# Get balances after trade completion.
|
||||
printdate "Bob & Alice's balances after trade:"
|
||||
printdate "ALICE CLI:"
|
||||
printbalances "$ALICE_PORT"
|
||||
printbreak
|
||||
printdate "BOB CLI:"
|
||||
printbalances "$BOB_PORT"
|
||||
printbreak
|
||||
|
||||
exit 0
|
@ -17,10 +17,15 @@
|
||||
|
||||
package bisq.apitest;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.apitest.Scaffold.EXIT_FAILURE;
|
||||
import static bisq.apitest.Scaffold.EXIT_SUCCESS;
|
||||
import static bisq.apitest.config.ApiTestRateMeterInterceptorConfig.appendCallRateMeteringConfigPathOpt;
|
||||
import static bisq.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateMeterInterceptorConfig;
|
||||
import static bisq.apitest.config.ApiTestRateMeterInterceptorConfig.hasCallRateMeteringConfigPathOpt;
|
||||
import static java.lang.System.err;
|
||||
import static java.lang.System.exit;
|
||||
|
||||
@ -32,7 +37,7 @@ import bisq.apitest.config.ApiTestConfig;
|
||||
* ApiTestMain is a placeholder for the gradle build file, which requires a valid
|
||||
* 'mainClassName' property in the :apitest subproject configuration.
|
||||
*
|
||||
* It does has some uses:
|
||||
* It has some uses:
|
||||
*
|
||||
* It can be used to print test scaffolding options: bisq-apitest --help.
|
||||
*
|
||||
@ -41,19 +46,23 @@ import bisq.apitest.config.ApiTestConfig;
|
||||
* It can be used to run the regtest environment for release testing:
|
||||
* bisq-test --shutdownAfterTests=false
|
||||
*
|
||||
* All method, scenario and end to end tests are found in the test sources folder.
|
||||
* All method, scenario and end-to-end tests are found in the test sources folder.
|
||||
*
|
||||
* Requires bitcoind v0.19, v0.20, or v0.21.
|
||||
* Requires bitcoind v0.19 - v22.
|
||||
*/
|
||||
@Slf4j
|
||||
public class ApiTestMain {
|
||||
|
||||
public static void main(String[] args) {
|
||||
new ApiTestMain().execute(args);
|
||||
if (!hasCallRateMeteringConfigPathOpt(args))
|
||||
new ApiTestMain().execute(getAppendedArgs(args));
|
||||
else
|
||||
new ApiTestMain().execute(args);
|
||||
}
|
||||
|
||||
public void execute(@SuppressWarnings("unused") String[] args) {
|
||||
public void execute(String[] args) {
|
||||
try {
|
||||
log.info("Configuring test harness with options:\n\t{}", String.join("\n\t", args));
|
||||
Scaffold scaffold = new Scaffold(args).setUp();
|
||||
ApiTestConfig config = scaffold.config;
|
||||
|
||||
@ -77,4 +86,9 @@ public class ApiTestMain {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
private static String[] getAppendedArgs(String[] args) {
|
||||
File rateMeterInterceptorConfig = getTestRateMeterInterceptorConfig();
|
||||
return appendCallRateMeteringConfigPathOpt(args, rateMeterInterceptorConfig);
|
||||
}
|
||||
}
|
||||
|
@ -160,6 +160,7 @@ public class Scaffold {
|
||||
try {
|
||||
log.info("Shutting down executor service ...");
|
||||
executor.shutdownNow();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
executor.awaitTermination(config.supportingApps.size() * 2000L, MILLISECONDS);
|
||||
|
||||
SetupTask[] orderedTasks = new SetupTask[]{
|
||||
@ -189,7 +190,7 @@ public class Scaffold {
|
||||
MILLISECONDS.sleep(1000);
|
||||
if (p.hasShutdownExceptions()) {
|
||||
// We log shutdown exceptions, but do not throw any from here
|
||||
// because all of the background instances must be shut down.
|
||||
// because all the background instances must be shut down.
|
||||
p.logExceptions(p.getShutdownExceptions(), log);
|
||||
|
||||
// We cache only the 1st shutdown exception and move on to the
|
||||
@ -221,6 +222,9 @@ public class Scaffold {
|
||||
}
|
||||
|
||||
private void installCallRateMeteringConfiguration(String dataDir) throws IOException, InterruptedException {
|
||||
if (config.callRateMeteringConfigPath.isEmpty())
|
||||
return;
|
||||
|
||||
File testRateMeteringFile = new File(config.callRateMeteringConfigPath);
|
||||
if (!testRateMeteringFile.exists())
|
||||
throw new FileNotFoundException(
|
||||
@ -289,49 +293,49 @@ public class Scaffold {
|
||||
startBisqApp(bobdesktop, executor, countdownLatch);
|
||||
}
|
||||
|
||||
private void startBisqApp(HavenoAppConfig havenoAppConfig,
|
||||
private void startBisqApp(HavenoAppConfig HavenoAppConfig,
|
||||
ExecutorService executor,
|
||||
CountDownLatch countdownLatch)
|
||||
throws IOException, InterruptedException {
|
||||
|
||||
HavenoProcess bisqProcess = createBisqProcess(havenoAppConfig);
|
||||
switch (havenoAppConfig) {
|
||||
HavenoProcess HavenoProcess = createHavenoProcess(HavenoAppConfig);
|
||||
switch (HavenoAppConfig) {
|
||||
case seednode:
|
||||
seedNodeTask = new SetupTask(bisqProcess, countdownLatch);
|
||||
seedNodeTask = new SetupTask(HavenoProcess, countdownLatch);
|
||||
seedNodeTaskFuture = executor.submit(seedNodeTask);
|
||||
break;
|
||||
case arbdaemon:
|
||||
case arbdesktop:
|
||||
arbNodeTask = new SetupTask(bisqProcess, countdownLatch);
|
||||
arbNodeTask = new SetupTask(HavenoProcess, countdownLatch);
|
||||
arbNodeTaskFuture = executor.submit(arbNodeTask);
|
||||
break;
|
||||
case alicedaemon:
|
||||
case alicedesktop:
|
||||
aliceNodeTask = new SetupTask(bisqProcess, countdownLatch);
|
||||
aliceNodeTask = new SetupTask(HavenoProcess, countdownLatch);
|
||||
aliceNodeTaskFuture = executor.submit(aliceNodeTask);
|
||||
break;
|
||||
case bobdaemon:
|
||||
case bobdesktop:
|
||||
bobNodeTask = new SetupTask(bisqProcess, countdownLatch);
|
||||
bobNodeTask = new SetupTask(HavenoProcess, countdownLatch);
|
||||
bobNodeTaskFuture = executor.submit(bobNodeTask);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown HavenoAppConfig " + havenoAppConfig.name());
|
||||
throw new IllegalStateException("Unknown HavenoAppConfig " + HavenoAppConfig.name());
|
||||
}
|
||||
log.info("Giving {} ms for {} to initialize ...", config.bisqAppInitTime, havenoAppConfig.appName);
|
||||
log.info("Giving {} ms for {} to initialize ...", config.bisqAppInitTime, HavenoAppConfig.appName);
|
||||
MILLISECONDS.sleep(config.bisqAppInitTime);
|
||||
if (bisqProcess.hasStartupExceptions()) {
|
||||
bisqProcess.logExceptions(bisqProcess.getStartupExceptions(), log);
|
||||
throw new IllegalStateException(bisqProcess.getStartupExceptions().get(0));
|
||||
if (HavenoProcess.hasStartupExceptions()) {
|
||||
HavenoProcess.logExceptions(HavenoProcess.getStartupExceptions(), log);
|
||||
throw new IllegalStateException(HavenoProcess.getStartupExceptions().get(0));
|
||||
}
|
||||
}
|
||||
|
||||
private HavenoProcess createBisqProcess(HavenoAppConfig havenoAppConfig)
|
||||
private HavenoProcess createHavenoProcess(HavenoAppConfig HavenoAppConfig)
|
||||
throws IOException, InterruptedException {
|
||||
HavenoProcess bisqProcess = new HavenoProcess(havenoAppConfig, config);
|
||||
bisqProcess.verifyAppNotRunning();
|
||||
bisqProcess.verifyAppDataDirInstalled();
|
||||
return bisqProcess;
|
||||
HavenoProcess HavenoProcess = new HavenoProcess(HavenoAppConfig, config);
|
||||
HavenoProcess.verifyAppNotRunning();
|
||||
HavenoProcess.verifyAppDataDirInstalled();
|
||||
return HavenoProcess;
|
||||
}
|
||||
|
||||
private void verifyStartupCompleted()
|
||||
|
@ -56,6 +56,8 @@ public class ApiTestConfig {
|
||||
|
||||
// Global constants
|
||||
public static final String BTC = "BTC";
|
||||
public static final String EUR = "EUR";
|
||||
public static final String USD = "USD";
|
||||
public static final String XMR = "XMR";
|
||||
public static final String ARBITRATOR = "arbitrator";
|
||||
public static final String MEDIATOR = "mediator";
|
||||
@ -149,7 +151,7 @@ public class ApiTestConfig {
|
||||
|
||||
ArgumentAcceptingOptionSpec<String> configFileOpt =
|
||||
parser.accepts(CONFIG_FILE, format("Specify configuration file. " +
|
||||
"Relative paths will be prefixed by %s location.", userDir))
|
||||
"Relative paths will be prefixed by %s location.", userDir))
|
||||
.withRequiredArg()
|
||||
.ofType(String.class)
|
||||
.defaultsTo(DEFAULT_CONFIG_FILE_NAME);
|
||||
@ -206,55 +208,55 @@ public class ApiTestConfig {
|
||||
|
||||
ArgumentAcceptingOptionSpec<Boolean> runSubprojectJarsOpt =
|
||||
parser.accepts(RUN_SUBPROJECT_JARS,
|
||||
"Run subproject build jars instead of full build jars")
|
||||
"Run subproject build jars instead of full build jars")
|
||||
.withRequiredArg()
|
||||
.ofType(Boolean.class)
|
||||
.defaultsTo(false);
|
||||
|
||||
ArgumentAcceptingOptionSpec<Long> bisqAppInitTimeOpt =
|
||||
parser.accepts(BISQ_APP_INIT_TIME,
|
||||
"Amount of time (ms) to wait on a Bisq instance's initialization")
|
||||
"Amount of time (ms) to wait on a Bisq instance's initialization")
|
||||
.withRequiredArg()
|
||||
.ofType(Long.class)
|
||||
.defaultsTo(5000L);
|
||||
|
||||
ArgumentAcceptingOptionSpec<Boolean> skipTestsOpt =
|
||||
parser.accepts(SKIP_TESTS,
|
||||
"Start apps, but skip tests")
|
||||
"Start apps, but skip tests")
|
||||
.withRequiredArg()
|
||||
.ofType(Boolean.class)
|
||||
.defaultsTo(false);
|
||||
|
||||
ArgumentAcceptingOptionSpec<Boolean> shutdownAfterTestsOpt =
|
||||
parser.accepts(SHUTDOWN_AFTER_TESTS,
|
||||
"Terminate all processes after tests")
|
||||
"Terminate all processes after tests")
|
||||
.withRequiredArg()
|
||||
.ofType(Boolean.class)
|
||||
.defaultsTo(true);
|
||||
|
||||
ArgumentAcceptingOptionSpec<String> supportingAppsOpt =
|
||||
parser.accepts(SUPPORTING_APPS,
|
||||
"Comma delimited list of supporting apps (bitcoind,seednode,arbdaemon,...")
|
||||
"Comma delimited list of supporting apps (bitcoind,seednode,arbdaemon,...")
|
||||
.withRequiredArg()
|
||||
.ofType(String.class)
|
||||
.defaultsTo("bitcoind,seednode,arbdaemon,alicedaemon,bobdaemon");
|
||||
|
||||
ArgumentAcceptingOptionSpec<String> callRateMeteringConfigPathOpt =
|
||||
parser.accepts(CALL_RATE_METERING_CONFIG_PATH,
|
||||
"Install a ratemeters.json file to configure call rate metering interceptors")
|
||||
"Install a ratemeters.json file to configure call rate metering interceptors")
|
||||
.withRequiredArg()
|
||||
.defaultsTo(EMPTY);
|
||||
|
||||
ArgumentAcceptingOptionSpec<Boolean> enableBisqDebuggingOpt =
|
||||
parser.accepts(ENABLE_BISQ_DEBUGGING,
|
||||
"Start Bisq apps with remote debug options")
|
||||
"Start Bisq apps with remote debug options")
|
||||
.withRequiredArg()
|
||||
.ofType(Boolean.class)
|
||||
.defaultsTo(false);
|
||||
|
||||
ArgumentAcceptingOptionSpec<Boolean> registerDisputeAgentsOpt =
|
||||
parser.accepts(REGISTER_DISPUTE_AGENTS,
|
||||
"Register dispute agents in arbitration daemon")
|
||||
"Register dispute agents in arbitration daemon")
|
||||
.withRequiredArg()
|
||||
.ofType(Boolean.class)
|
||||
.defaultsTo(true);
|
||||
|
@ -0,0 +1,70 @@
|
||||
package bisq.apitest.config;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.CALL_RATE_METERING_CONFIG_PATH;
|
||||
import static bisq.proto.grpc.DisputeAgentsGrpc.getRegisterDisputeAgentMethod;
|
||||
import static bisq.proto.grpc.GetVersionGrpc.getGetVersionMethod;
|
||||
import static java.lang.System.arraycopy;
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
|
||||
|
||||
import bisq.daemon.grpc.GrpcVersionService;
|
||||
import bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig;
|
||||
|
||||
public class ApiTestRateMeterInterceptorConfig {
|
||||
|
||||
public static File getTestRateMeterInterceptorConfig() {
|
||||
GrpcServiceRateMeteringConfig.Builder builder = new GrpcServiceRateMeteringConfig.Builder();
|
||||
builder.addCallRateMeter(GrpcVersionService.class.getSimpleName(),
|
||||
getGetVersionMethod().getFullMethodName(),
|
||||
1,
|
||||
SECONDS);
|
||||
// Only GrpcVersionService is @VisibleForTesting, so we need to
|
||||
// hardcode other grpcServiceClassName parameter values used in
|
||||
// builder.addCallRateMeter(...).
|
||||
builder.addCallRateMeter("GrpcDisputeAgentsService",
|
||||
getRegisterDisputeAgentMethod().getFullMethodName(),
|
||||
10, // Same as default.
|
||||
SECONDS);
|
||||
// Define rate meters for non-existent method 'disabled', to override other grpc
|
||||
// services' default rate meters -- defined in their rateMeteringInterceptor()
|
||||
// methods.
|
||||
String[] serviceClassNames = new String[]{
|
||||
"GrpcGetTradeStatisticsService",
|
||||
"GrpcHelpService",
|
||||
"GrpcOffersService",
|
||||
"GrpcPaymentAccountsService",
|
||||
"GrpcPriceService",
|
||||
"GrpcTradesService",
|
||||
"GrpcWalletsService"
|
||||
};
|
||||
for (String service : serviceClassNames) {
|
||||
builder.addCallRateMeter(service, "disabled", 1, MILLISECONDS);
|
||||
}
|
||||
File file = builder.build();
|
||||
file.deleteOnExit();
|
||||
return file;
|
||||
}
|
||||
|
||||
public static boolean hasCallRateMeteringConfigPathOpt(String[] args) {
|
||||
return stream(args).anyMatch(a -> a.contains("--" + CALL_RATE_METERING_CONFIG_PATH));
|
||||
}
|
||||
|
||||
public static String[] appendCallRateMeteringConfigPathOpt(String[] args, File rateMeterInterceptorConfig) {
|
||||
String[] rateMeteringConfigPathOpt = new String[]{
|
||||
"--" + CALL_RATE_METERING_CONFIG_PATH + "=" + rateMeterInterceptorConfig.getAbsolutePath()
|
||||
};
|
||||
if (args.length == 0) {
|
||||
return rateMeteringConfigPathOpt;
|
||||
} else {
|
||||
String[] appendedOpts = new String[args.length + 1];
|
||||
arraycopy(args, 0, appendedOpts, 0, args.length);
|
||||
arraycopy(rateMeteringConfigPathOpt, 0, appendedOpts, args.length, rateMeteringConfigPathOpt.length);
|
||||
return appendedOpts;
|
||||
}
|
||||
}
|
||||
}
|
@ -108,7 +108,7 @@ abstract class AbstractLinuxProcess implements LinuxProcess {
|
||||
File bitcoindExecutable = Paths.get(config.bitcoinPath, "bitcoind").toFile();
|
||||
if (!bitcoindExecutable.exists() || !bitcoindExecutable.canExecute())
|
||||
throw new IllegalStateException(format("'%s' cannot be found or executed.%n"
|
||||
+ "A bitcoin-core v0.19, v0.20, or v0.21 installation is required," +
|
||||
+ "A bitcoin-core v0.19 - v22 installation is required," +
|
||||
" and the 'bitcoinPath' must be configured in 'apitest.properties'",
|
||||
bitcoindExecutable.getAbsolutePath()));
|
||||
|
||||
|
@ -24,7 +24,7 @@ import java.util.List;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BASH_PATH_VALUE;
|
||||
import static java.lang.management.ManagementFactory.getRuntimeMXBean;
|
||||
@ -33,7 +33,9 @@ import static java.lang.management.ManagementFactory.getRuntimeMXBean;
|
||||
public class BashCommand {
|
||||
|
||||
private int exitStatus = -1;
|
||||
@Nullable
|
||||
private String output;
|
||||
@Nullable
|
||||
private String error;
|
||||
|
||||
private final String command;
|
||||
@ -92,6 +94,7 @@ public class BashCommand {
|
||||
}
|
||||
|
||||
// TODO return Optional<String>
|
||||
@Nullable
|
||||
public String getOutput() {
|
||||
return this.output;
|
||||
}
|
||||
@ -101,7 +104,6 @@ public class BashCommand {
|
||||
return this.error;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<String> tokenizeSystemCommand() {
|
||||
return new ArrayList<>() {{
|
||||
add(BASH_PATH_VALUE);
|
||||
|
@ -57,15 +57,13 @@ class SystemCommandExecutor {
|
||||
private ThreadedStreamHandler errorStreamHandler;
|
||||
|
||||
public SystemCommandExecutor(final List<String> cmdOptions) {
|
||||
if (log.isDebugEnabled())
|
||||
log.debug("cmd options {}", cmdOptions.toString());
|
||||
|
||||
if (cmdOptions.isEmpty())
|
||||
throw new IllegalStateException("No command params specified.");
|
||||
|
||||
if (cmdOptions.contains("sudo"))
|
||||
throw new IllegalStateException("'sudo' commands are prohibited.");
|
||||
|
||||
log.trace("System cmd options {}", cmdOptions);
|
||||
this.cmdOptions = cmdOptions;
|
||||
}
|
||||
|
||||
|
7
apitest/src/main/resources/haveno.properties
Normal file
7
apitest/src/main/resources/haveno.properties
Normal file
@ -0,0 +1,7 @@
|
||||
# Haveno core properties file loaded by Haveno instances started by the test harness.
|
||||
# Normally, it would be left empty, but it is useful for ad-hoc testing with
|
||||
# Haveno Config options not configurable in test harness-specific apitest.properties
|
||||
# file. This is where you might define Haveno options such as:
|
||||
# dumpBlockchainData=true
|
||||
# dumpDelayedPayoutTxs=true
|
||||
# dumpStatistics=true
|
@ -17,7 +17,8 @@
|
||||
|
||||
package bisq.apitest;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Duration;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@ -29,23 +30,19 @@ import javax.annotation.Nullable;
|
||||
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
|
||||
import static bisq.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateMeterInterceptorConfig;
|
||||
import static bisq.apitest.config.HavenoAppConfig.alicedaemon;
|
||||
import static bisq.apitest.config.HavenoAppConfig.arbdaemon;
|
||||
import static bisq.apitest.config.HavenoAppConfig.bobdaemon;
|
||||
import static bisq.proto.grpc.DisputeAgentsGrpc.getRegisterDisputeAgentMethod;
|
||||
import static bisq.proto.grpc.GetVersionGrpc.getGetVersionMethod;
|
||||
import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly;
|
||||
import static java.net.InetAddress.getLoopbackAddress;
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.config.ApiTestConfig;
|
||||
import bisq.apitest.method.BitcoinCliHelper;
|
||||
import bisq.cli.GrpcClient;
|
||||
import bisq.daemon.grpc.GrpcVersionService;
|
||||
import bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig;
|
||||
|
||||
/**
|
||||
* Base class for all test types: 'method', 'scenario' and 'e2e'.
|
||||
@ -90,7 +87,7 @@ public class ApiTestCase {
|
||||
throws InterruptedException, ExecutionException, IOException {
|
||||
String[] params = new String[]{
|
||||
"--supportingApps", stream(supportingApps).map(Enum::name).collect(Collectors.joining(",")),
|
||||
"--callRateMeteringConfigPath", defaultRateMeterInterceptorConfig().getAbsolutePath(),
|
||||
"--callRateMeteringConfigPath", getTestRateMeterInterceptorConfig().getAbsolutePath(),
|
||||
"--enableBisqDebugging", "false"
|
||||
};
|
||||
setUpScaffold(params);
|
||||
@ -136,11 +133,7 @@ public class ApiTestCase {
|
||||
}
|
||||
|
||||
protected static void sleep(long ms) {
|
||||
try {
|
||||
MILLISECONDS.sleep(ms);
|
||||
} catch (InterruptedException ignored) {
|
||||
// empty
|
||||
}
|
||||
sleepUninterruptibly(Duration.ofMillis(ms));
|
||||
}
|
||||
|
||||
protected final String testName(TestInfo testInfo) {
|
||||
@ -148,37 +141,4 @@ public class ApiTestCase {
|
||||
? testInfo.getTestMethod().get().getName()
|
||||
: "unknown test name";
|
||||
}
|
||||
|
||||
protected static File defaultRateMeterInterceptorConfig() {
|
||||
GrpcServiceRateMeteringConfig.Builder builder = new GrpcServiceRateMeteringConfig.Builder();
|
||||
builder.addCallRateMeter(GrpcVersionService.class.getSimpleName(),
|
||||
getGetVersionMethod().getFullMethodName(),
|
||||
1,
|
||||
SECONDS);
|
||||
// Only GrpcVersionService is @VisibleForTesting, so we need to
|
||||
// hardcode other grpcServiceClassName parameter values used in
|
||||
// builder.addCallRateMeter(...).
|
||||
builder.addCallRateMeter("GrpcDisputeAgentsService",
|
||||
getRegisterDisputeAgentMethod().getFullMethodName(),
|
||||
10, // Same as default.
|
||||
SECONDS);
|
||||
// Define rate meters for non-existent method 'disabled', to override other grpc
|
||||
// services' default rate meters -- defined in their rateMeteringInterceptor()
|
||||
// methods.
|
||||
String[] serviceClassNames = new String[]{
|
||||
"GrpcGetTradeStatisticsService",
|
||||
"GrpcHelpService",
|
||||
"GrpcOffersService",
|
||||
"GrpcPaymentAccountsService",
|
||||
"GrpcPriceService",
|
||||
"GrpcTradesService",
|
||||
"GrpcWalletsService"
|
||||
};
|
||||
for (String service : serviceClassNames) {
|
||||
builder.addCallRateMeter(service, "disabled", 1, MILLISECONDS);
|
||||
}
|
||||
File file = builder.build();
|
||||
file.deleteOnExit();
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
@ -19,17 +19,35 @@ package bisq.apitest.method;
|
||||
|
||||
import bisq.core.api.model.PaymentAccountForm;
|
||||
import bisq.core.payment.F2FAccount;
|
||||
import bisq.core.payment.NationalBankAccount;
|
||||
import bisq.core.proto.CoreProtoResolver;
|
||||
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import bisq.proto.grpc.BalancesInfo;
|
||||
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||
import static bisq.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateMeterInterceptorConfig;
|
||||
import static bisq.cli.table.builder.TableType.BTC_BALANCE_TBL;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static java.lang.String.format;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.Arrays.stream;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
@ -37,7 +55,9 @@ import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
|
||||
import bisq.apitest.ApiTestCase;
|
||||
import bisq.apitest.linux.BashCommand;
|
||||
import bisq.cli.GrpcClient;
|
||||
import bisq.cli.table.builder.TableBuilder;
|
||||
|
||||
public class MethodTest extends ApiTestCase {
|
||||
|
||||
@ -46,15 +66,6 @@ public class MethodTest extends ApiTestCase {
|
||||
private static final Function<Enum<?>[], String> toNameList = (enums) ->
|
||||
stream(enums).map(Enum::name).collect(Collectors.joining(","));
|
||||
|
||||
public static void startSupportingApps(File callRateMeteringConfigFile,
|
||||
boolean generateBtcBlock,
|
||||
Enum<?>... supportingApps) {
|
||||
startSupportingApps(callRateMeteringConfigFile,
|
||||
generateBtcBlock,
|
||||
false,
|
||||
supportingApps);
|
||||
}
|
||||
|
||||
public static void startSupportingApps(File callRateMeteringConfigFile,
|
||||
boolean generateBtcBlock,
|
||||
boolean startSupportingAppsInDebugMode,
|
||||
@ -71,19 +82,12 @@ public class MethodTest extends ApiTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public static void startSupportingApps(boolean generateBtcBlock,
|
||||
Enum<?>... supportingApps) {
|
||||
startSupportingApps(generateBtcBlock,
|
||||
false,
|
||||
supportingApps);
|
||||
}
|
||||
|
||||
public static void startSupportingApps(boolean generateBtcBlock,
|
||||
boolean startSupportingAppsInDebugMode,
|
||||
Enum<?>... supportingApps) {
|
||||
try {
|
||||
// Disable call rate metering where there is no callRateMeteringConfigFile.
|
||||
File callRateMeteringConfigFile = defaultRateMeterInterceptorConfig();
|
||||
File callRateMeteringConfigFile = getTestRateMeterInterceptorConfig();
|
||||
setUpScaffold(new String[]{
|
||||
"--supportingApps", toNameList.apply(supportingApps),
|
||||
"--callRateMeteringConfigPath", callRateMeteringConfigFile.getAbsolutePath(),
|
||||
@ -133,17 +137,94 @@ public class MethodTest extends ApiTestCase {
|
||||
return f2FAccount;
|
||||
}
|
||||
|
||||
|
||||
protected bisq.core.payment.PaymentAccount createDummyBRLAccount(GrpcClient grpcClient,
|
||||
String holderName,
|
||||
String nationalAccountId,
|
||||
String holderTaxId) {
|
||||
String nationalBankAccountJsonString = "{\n" +
|
||||
" \"_COMMENTS_\": [ \"Dummy Account\" ],\n" +
|
||||
" \"paymentMethodId\": \"NATIONAL_BANK\",\n" +
|
||||
" \"accountName\": \"Banco do Brasil\",\n" +
|
||||
" \"country\": \"BR\",\n" +
|
||||
" \"bankName\": \"Banco do Brasil\",\n" +
|
||||
" \"branchId\": \"456789-10\",\n" +
|
||||
" \"holderName\": \"" + holderName + "\",\n" +
|
||||
" \"accountNr\": \"456789-87\",\n" +
|
||||
" \"nationalAccountId\": \"" + nationalAccountId + "\",\n" +
|
||||
" \"holderTaxId\": \"" + holderTaxId + "\"\n" +
|
||||
"}\n";
|
||||
NationalBankAccount nationalBankAccount =
|
||||
(NationalBankAccount) createPaymentAccount(grpcClient, nationalBankAccountJsonString);
|
||||
return nationalBankAccount;
|
||||
}
|
||||
|
||||
protected final bisq.core.payment.PaymentAccount createPaymentAccount(GrpcClient grpcClient, String jsonString) {
|
||||
// Normally, we do asserts on the protos from the gRPC service, but in this
|
||||
// case we need a bisq.core.payment.PaymentAccount so it can be cast to its
|
||||
// sub type.
|
||||
// sub-type.
|
||||
var paymentAccount = grpcClient.createPaymentAccount(jsonString);
|
||||
return bisq.core.payment.PaymentAccount.fromProto(paymentAccount, CORE_PROTO_RESOLVER);
|
||||
}
|
||||
|
||||
// Static conveniences for test methods and test case fixture setups.
|
||||
public static final Supplier<Double> defaultBuyerSecurityDepositPct = () -> {
|
||||
var defaultPct = BigDecimal.valueOf(getDefaultBuyerSecurityDepositAsPercent());
|
||||
if (defaultPct.precision() != 2)
|
||||
throw new IllegalStateException(format(
|
||||
"Unexpected decimal precision, expected 2 but actual is %d%n."
|
||||
+ "Check for changes to Restrictions.getDefaultBuyerSecurityDepositAsPercent()",
|
||||
defaultPct.precision()));
|
||||
|
||||
return defaultPct.movePointRight(2).doubleValue();
|
||||
};
|
||||
|
||||
public static String formatBalancesTbls(BalancesInfo allBalances) {
|
||||
StringBuilder balances = new StringBuilder(BTC).append("\n");
|
||||
balances.append(new TableBuilder(BTC_BALANCE_TBL, allBalances.getBtc()).build());
|
||||
balances.append("\n");
|
||||
return balances.toString();
|
||||
}
|
||||
|
||||
protected static String encodeToHex(String s) {
|
||||
return Utilities.bytesAsHexString(s.getBytes(UTF_8));
|
||||
}
|
||||
|
||||
protected static Status.Code getStatusRuntimeExceptionStatusCode(Exception grpcException) {
|
||||
if (grpcException instanceof StatusRuntimeException)
|
||||
return ((StatusRuntimeException) grpcException).getStatus().getCode();
|
||||
else
|
||||
throw new IllegalArgumentException(
|
||||
format("Expected a io.grpc.StatusRuntimeException argument, but got a %s",
|
||||
grpcException.getClass().getName()));
|
||||
}
|
||||
|
||||
protected void verifyNoLoggedNodeExceptions() {
|
||||
var loggedExceptions = getNodeExceptionMessages();
|
||||
if (loggedExceptions != null) {
|
||||
String err = format("Exception(s) found in daemon log(s):%n%s", loggedExceptions);
|
||||
fail(err);
|
||||
}
|
||||
}
|
||||
|
||||
protected void printNodeExceptionMessages(Logger log) {
|
||||
var loggedExceptions = getNodeExceptionMessages();
|
||||
if (loggedExceptions != null)
|
||||
log.error("Exception(s) found in daemon log(s):\n{}", loggedExceptions);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected static String getNodeExceptionMessages() {
|
||||
var nodeLogsSpec = config.rootAppDataDir.getAbsolutePath() + "/bisq-BTC_REGTEST_*_dao/bisq.log";
|
||||
var grep = "grep Exception " + nodeLogsSpec;
|
||||
var bashCommand = new BashCommand(grep);
|
||||
try {
|
||||
bashCommand.run();
|
||||
} catch (IOException | InterruptedException ex) {
|
||||
fail("Bash command execution error: " + ex);
|
||||
}
|
||||
if (bashCommand.getError() == null)
|
||||
return bashCommand.getOutput();
|
||||
else
|
||||
throw new IllegalStateException("Bash command execution error: " + bashCommand.getError());
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ public class RegisterDisputeAgentsTest extends MethodTest {
|
||||
public void testRegisterArbitratorShouldThrowException() {
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||
arbClient.registerDisputeAgent(ARBITRATOR, DEV_PRIVILEGE_PRIV_KEY));
|
||||
assertEquals("INVALID_ARGUMENT: arbitrators must be registered in a Bisq UI",
|
||||
assertEquals("UNIMPLEMENTED: arbitrators must be registered in a Bisq UI",
|
||||
exception.getMessage());
|
||||
}
|
||||
|
||||
|
@ -17,13 +17,15 @@
|
||||
|
||||
package bisq.apitest.method.offer;
|
||||
|
||||
import bisq.core.monetary.Altcoin;
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import org.bitcoinj.utils.Fiat;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -32,49 +34,96 @@ import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
|
||||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
||||
import static bisq.apitest.config.ApiTestConfig.XMR;
|
||||
import static bisq.apitest.config.HavenoAppConfig.alicedaemon;
|
||||
import static bisq.apitest.config.HavenoAppConfig.arbdaemon;
|
||||
import static bisq.apitest.config.HavenoAppConfig.bobdaemon;
|
||||
import static bisq.apitest.config.HavenoAppConfig.seednode;
|
||||
import static bisq.common.util.MathUtils.roundDouble;
|
||||
import static bisq.common.util.MathUtils.scaleDownByPowerOf10;
|
||||
import static bisq.core.locale.CurrencyUtil.isCryptoCurrency;
|
||||
import static java.math.RoundingMode.HALF_UP;
|
||||
import static bisq.cli.table.builder.TableType.OFFER_TBL;
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.System.out;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.MethodTest;
|
||||
import bisq.cli.CliMain;
|
||||
import bisq.cli.table.builder.TableBuilder;
|
||||
|
||||
@Slf4j
|
||||
public abstract class AbstractOfferTest extends MethodTest {
|
||||
|
||||
protected static final int ACTIVATE_OFFER = 1;
|
||||
protected static final int DEACTIVATE_OFFER = 0;
|
||||
protected static final String NO_TRIGGER_PRICE = "0";
|
||||
|
||||
@Setter
|
||||
protected static boolean isLongRunningTest;
|
||||
|
||||
protected static PaymentAccount alicesBtcAcct;
|
||||
protected static PaymentAccount bobsBtcAcct;
|
||||
|
||||
protected static PaymentAccount alicesXmrAcct;
|
||||
protected static PaymentAccount bobsXmrAcct;
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
setUp(false);
|
||||
}
|
||||
|
||||
public static void setUp(boolean startSupportingAppsInDebugMode) {
|
||||
startSupportingApps(true,
|
||||
false,
|
||||
startSupportingAppsInDebugMode,
|
||||
bitcoind,
|
||||
seednode,
|
||||
arbdaemon,
|
||||
alicedaemon,
|
||||
bobdaemon);
|
||||
initPaymentAccounts();
|
||||
}
|
||||
|
||||
protected double getScaledOfferPrice(double offerPrice, String currencyCode) {
|
||||
int precision = isCryptoCurrency(currencyCode) ? Altcoin.SMALLEST_UNIT_EXPONENT : Fiat.SMALLEST_UNIT_EXPONENT;
|
||||
return scaleDownByPowerOf10(offerPrice, precision);
|
||||
protected static final Function<OfferInfo, String> toOfferTable = (offer) ->
|
||||
new TableBuilder(OFFER_TBL, offer).build().toString();
|
||||
|
||||
protected static final Function<List<OfferInfo>, String> toOffersTable = (offers) ->
|
||||
new TableBuilder(OFFER_TBL, offers).build().toString();
|
||||
|
||||
protected static String calcPriceAsString(double base, double delta, int precision) {
|
||||
var mathContext = new MathContext(precision);
|
||||
var priceAsBigDecimal = new BigDecimal(Double.toString(base), mathContext)
|
||||
.add(new BigDecimal(Double.toString(delta), mathContext))
|
||||
.round(mathContext);
|
||||
return format("%." + precision + "f", priceAsBigDecimal.doubleValue());
|
||||
}
|
||||
|
||||
protected final double getPercentageDifference(double price1, double price2) {
|
||||
return BigDecimal.valueOf(roundDouble((1 - (price1 / price2)), 5))
|
||||
.setScale(4, HALF_UP)
|
||||
.doubleValue();
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public static void initPaymentAccounts() {
|
||||
alicesBtcAcct = aliceClient.getPaymentAccount("BTC");
|
||||
bobsBtcAcct = bobClient.getPaymentAccount("BTC");
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public static void createXmrPaymentAccounts() {
|
||||
alicesXmrAcct = aliceClient.createCryptoCurrencyPaymentAccount("Alice's XMR Account",
|
||||
XMR,
|
||||
"44G4jWmSvTEfifSUZzTDnJVLPvYATmq9XhhtDqUof1BGCLceG82EQsVYG9Q9GN4bJcjbAJEc1JD1m5G7iK4UPZqACubV4Mq",
|
||||
false);
|
||||
log.trace("Alices XMR Account: {}", alicesXmrAcct);
|
||||
bobsXmrAcct = bobClient.createCryptoCurrencyPaymentAccount("Bob's XMR Account",
|
||||
XMR,
|
||||
"4BDRhdSBKZqAXs3PuNTbMtaXBNqFj5idC2yMVnQj8Rm61AyKY8AxLTt9vGRJ8pwcG4EtpyD8YpGqdZWCZ2VZj6yVBN2RVKs",
|
||||
false);
|
||||
log.trace("Bob's XMR Account: {}", bobsXmrAcct);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() {
|
||||
tearDownScaffold();
|
||||
}
|
||||
|
||||
protected static void runCliGetOffer(String offerId) {
|
||||
out.println("Alice's CLI 'getmyoffer' response:");
|
||||
CliMain.main(new String[]{"--password=xyz", "--port=9998", "getmyoffer", "--offer-id=" + offerId});
|
||||
out.println("Bob's CLI 'getoffer' response:");
|
||||
CliMain.main(new String[]{"--password=xyz", "--port=9999", "getoffer", "--offer-id=" + offerId});
|
||||
}
|
||||
}
|
||||
|
@ -32,9 +32,8 @@ import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@ -51,8 +50,9 @@ public class CancelOfferTest extends AbstractOfferTest {
|
||||
10000000L,
|
||||
10000000L,
|
||||
0.00,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
paymentAccountId);
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
paymentAccountId,
|
||||
NO_TRIGGER_PRICE);
|
||||
};
|
||||
|
||||
@Test
|
||||
|
@ -28,14 +28,14 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.XMR;
|
||||
import static bisq.cli.TableFormat.formatOfferTable;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static bisq.apitest.config.ApiTestConfig.EUR;
|
||||
import static bisq.apitest.config.ApiTestConfig.USD;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
import static protobuf.OfferDirection.SELL;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@ -44,35 +44,44 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testCreateAUDXMRBuyOfferUsingFixedPrice16000() {
|
||||
public void testCreateAUDBTCBuyOfferUsingFixedPrice16000() {
|
||||
PaymentAccount audAccount = createDummyF2FAccount(aliceClient, "AU");
|
||||
var newOffer = aliceClient.createFixedPricedOffer(BUY.name(),
|
||||
"aud",
|
||||
10_000_000L,
|
||||
10_000_000L,
|
||||
"36000",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
audAccount.getId());
|
||||
log.info("OFFER #1:\n{}", formatOfferTable(singletonList(newOffer), "AUD"));
|
||||
log.debug("Offer #1:\n{}", toOfferTable.apply(newOffer));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsActivated());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(360_000_000, newOffer.getPrice());
|
||||
assertEquals("36000.0000", newOffer.getPrice());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||
assertEquals("3600", newOffer.getVolume());
|
||||
assertEquals("3600", newOffer.getMinVolume());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(audAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("AUD", newOffer.getCounterCurrencyCode());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
newOffer = aliceClient.getOffer(newOfferId);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsActivated());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(360_000_000, newOffer.getPrice());
|
||||
assertEquals("36000.0000", newOffer.getPrice());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||
assertEquals("3600", newOffer.getVolume());
|
||||
assertEquals("3600", newOffer.getMinVolume());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(audAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
@ -81,75 +90,93 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testCreateUSDXMRBuyOfferUsingFixedPrice100001234() {
|
||||
public void testCreateUSDBTCBuyOfferUsingFixedPrice100001234() {
|
||||
PaymentAccount usdAccount = createDummyF2FAccount(aliceClient, "US");
|
||||
var newOffer = aliceClient.createFixedPricedOffer(BUY.name(),
|
||||
"usd",
|
||||
10_000_000L,
|
||||
10_000_000L,
|
||||
"30000.1234",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
usdAccount.getId());
|
||||
log.info("OFFER #2:\n{}", formatOfferTable(singletonList(newOffer), "USD"));
|
||||
log.debug("Offer #2:\n{}", toOfferTable.apply(newOffer));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsActivated());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(300_001_234, newOffer.getPrice());
|
||||
assertEquals("30000.1234", newOffer.getPrice());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||
assertEquals("3000", newOffer.getVolume());
|
||||
assertEquals("3000", newOffer.getMinVolume());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
||||
assertEquals(USD, newOffer.getCounterCurrencyCode());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
newOffer = aliceClient.getOffer(newOfferId);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsActivated());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(300_001_234, newOffer.getPrice());
|
||||
assertEquals("30000.1234", newOffer.getPrice());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||
assertEquals("3000", newOffer.getVolume());
|
||||
assertEquals("3000", newOffer.getMinVolume());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
||||
assertEquals(USD, newOffer.getCounterCurrencyCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testCreateEURXMRSellOfferUsingFixedPrice95001234() {
|
||||
public void testCreateEURBTCSellOfferUsingFixedPrice95001234() {
|
||||
PaymentAccount eurAccount = createDummyF2FAccount(aliceClient, "FR");
|
||||
var newOffer = aliceClient.createFixedPricedOffer(SELL.name(),
|
||||
"eur",
|
||||
10_000_000L,
|
||||
5_000_000L,
|
||||
"29500.1234",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
eurAccount.getId());
|
||||
log.info("OFFER #3:\n{}", formatOfferTable(singletonList(newOffer), "EUR"));
|
||||
log.debug("Offer #3:\n{}", toOfferTable.apply(newOffer));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsActivated());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(295_001_234, newOffer.getPrice());
|
||||
assertEquals("29500.1234", newOffer.getPrice());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(5_000_000, newOffer.getMinAmount());
|
||||
assertEquals("2950", newOffer.getVolume());
|
||||
assertEquals("1475", newOffer.getMinVolume());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(eurAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("EUR", newOffer.getCounterCurrencyCode());
|
||||
assertEquals(EUR, newOffer.getCounterCurrencyCode());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
newOffer = aliceClient.getOffer(newOfferId);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsActivated());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(295_001_234, newOffer.getPrice());
|
||||
assertEquals("29500.1234", newOffer.getPrice());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(5_000_000, newOffer.getMinAmount());
|
||||
assertEquals("2950", newOffer.getVolume());
|
||||
assertEquals("1475", newOffer.getMinVolume());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(eurAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("EUR", newOffer.getCounterCurrencyCode());
|
||||
assertEquals(EUR, newOffer.getCounterCurrencyCode());
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ import bisq.proto.grpc.OfferInfo;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
@ -31,20 +33,22 @@ import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.XMR;
|
||||
import static bisq.cli.TableFormat.formatOfferTable;
|
||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||
import static bisq.apitest.config.ApiTestConfig.USD;
|
||||
import static bisq.common.util.MathUtils.roundDouble;
|
||||
import static bisq.common.util.MathUtils.scaleDownByPowerOf10;
|
||||
import static bisq.common.util.MathUtils.scaleUpByPowerOf10;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static java.lang.Math.abs;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.math.RoundingMode.HALF_UP;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
import static protobuf.OfferDirection.SELL;
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
@ -54,77 +58,95 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
private static final double MKT_PRICE_MARGIN_ERROR_TOLERANCE = 0.0050; // 0.50%
|
||||
private static final double MKT_PRICE_MARGIN_WARNING_TOLERANCE = 0.0001; // 0.01%
|
||||
|
||||
private static final String MAKER_FEE_CURRENCY_CODE = BTC;
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testCreateUSDXMRBuyOffer5PctPriceMargin() {
|
||||
public void testCreateUSDBTCBuyOffer5PctPriceMargin() {
|
||||
PaymentAccount usdAccount = createDummyF2FAccount(aliceClient, "US");
|
||||
double priceMarginPctInput = 5.00;
|
||||
double priceMarginPctInput = 5.00d;
|
||||
var newOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
|
||||
"usd",
|
||||
10_000_000L,
|
||||
10_000_000L,
|
||||
priceMarginPctInput,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
usdAccount.getId());
|
||||
log.info("OFFER #1:\n{}", formatOfferTable(singletonList(newOffer), "usd"));
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
usdAccount.getId(),
|
||||
NO_TRIGGER_PRICE);
|
||||
log.debug("Offer #1:\n{}", toOfferTable.apply(newOffer));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsActivated());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(USD, newOffer.getCounterCurrencyCode());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
newOffer = aliceClient.getOffer(newOfferId);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsActivated());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(USD, newOffer.getCounterCurrencyCode());
|
||||
|
||||
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testCreateNZDXMRBuyOfferMinus2PctPriceMargin() {
|
||||
public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() {
|
||||
PaymentAccount nzdAccount = createDummyF2FAccount(aliceClient, "NZ");
|
||||
double priceMarginPctInput = -2.00;
|
||||
double priceMarginPctInput = -2.00d; // -2%
|
||||
var newOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
|
||||
"nzd",
|
||||
10_000_000L,
|
||||
10_000_000L,
|
||||
priceMarginPctInput,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
nzdAccount.getId());
|
||||
log.info("OFFER #2:\n{}", formatOfferTable(singletonList(newOffer), "nzd"));
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
nzdAccount.getId(),
|
||||
NO_TRIGGER_PRICE);
|
||||
log.debug("Offer #2:\n{}", toOfferTable.apply(newOffer));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsActivated());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(nzdAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("NZD", newOffer.getCounterCurrencyCode());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
newOffer = aliceClient.getOffer(newOfferId);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsActivated());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(nzdAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("NZD", newOffer.getCounterCurrencyCode());
|
||||
|
||||
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
||||
@ -132,7 +154,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testCreateGBPXMRSellOfferMinus1Point5PctPriceMargin() {
|
||||
public void testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin() {
|
||||
PaymentAccount gbpAccount = createDummyF2FAccount(aliceClient, "GB");
|
||||
double priceMarginPctInput = -1.5;
|
||||
var newOffer = aliceClient.createMarketBasedPricedOffer(SELL.name(),
|
||||
@ -140,29 +162,37 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
10_000_000L,
|
||||
5_000_000L,
|
||||
priceMarginPctInput,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
gbpAccount.getId());
|
||||
log.info("OFFER #3:\n{}", formatOfferTable(singletonList(newOffer), "gbp"));
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
gbpAccount.getId(),
|
||||
NO_TRIGGER_PRICE);
|
||||
log.debug("Offer #3:\n{}", toOfferTable.apply(newOffer));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsActivated());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(5_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(gbpAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("GBP", newOffer.getCounterCurrencyCode());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
newOffer = aliceClient.getOffer(newOfferId);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsActivated());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(5_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(gbpAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("GBP", newOffer.getCounterCurrencyCode());
|
||||
|
||||
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
||||
@ -170,7 +200,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testCreateBRLXMRSellOffer6Point55PctPriceMargin() {
|
||||
public void testCreateBRLBTCSellOffer6Point55PctPriceMargin() {
|
||||
PaymentAccount brlAccount = createDummyF2FAccount(aliceClient, "BR");
|
||||
double priceMarginPctInput = 6.55;
|
||||
var newOffer = aliceClient.createMarketBasedPricedOffer(SELL.name(),
|
||||
@ -178,53 +208,92 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
10_000_000L,
|
||||
5_000_000L,
|
||||
priceMarginPctInput,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
brlAccount.getId());
|
||||
log.info("OFFER #4:\n{}", formatOfferTable(singletonList(newOffer), "brl"));
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
brlAccount.getId(),
|
||||
NO_TRIGGER_PRICE);
|
||||
log.debug("Offer #4:\n{}", toOfferTable.apply(newOffer));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsActivated());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(5_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(brlAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("BRL", newOffer.getCounterCurrencyCode());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
newOffer = aliceClient.getOffer(newOfferId);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsActivated());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
|
||||
assertEquals(10_000_000, newOffer.getAmount());
|
||||
assertEquals(5_000_000, newOffer.getMinAmount());
|
||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(brlAccount.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||
assertEquals("BRL", newOffer.getCounterCurrencyCode());
|
||||
|
||||
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(5)
|
||||
public void testCreateUSDBTCBuyOfferWithTriggerPrice() {
|
||||
PaymentAccount usdAccount = createDummyF2FAccount(aliceClient, "US");
|
||||
double mktPriceAsDouble = aliceClient.getBtcPrice("usd");
|
||||
String triggerPrice = calcPriceAsString(mktPriceAsDouble, Double.parseDouble("1000.9999"), 4);
|
||||
var newOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
|
||||
"usd",
|
||||
10_000_000L,
|
||||
5_000_000L,
|
||||
0.0,
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
usdAccount.getId(),
|
||||
triggerPrice);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsActivated());
|
||||
|
||||
genBtcBlocksThenWait(1, 4000); // give time to add to offer book
|
||||
newOffer = aliceClient.getOffer(newOffer.getId());
|
||||
log.debug("Offer #5:\n{}", toOfferTable.apply(newOffer));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsActivated());
|
||||
assertEquals(triggerPrice, newOffer.getTriggerPrice());
|
||||
}
|
||||
|
||||
private void assertCalculatedPriceIsCorrect(OfferInfo offer, double priceMarginPctInput) {
|
||||
assertTrue(() -> {
|
||||
String counterCurrencyCode = offer.getCounterCurrencyCode();
|
||||
double mktPrice = aliceClient.getBtcPrice(counterCurrencyCode);
|
||||
double scaledOfferPrice = getScaledOfferPrice(offer.getPrice(), counterCurrencyCode);
|
||||
double priceAsDouble = Double.parseDouble(offer.getPrice());
|
||||
double expectedDiffPct = scaleDownByPowerOf10(priceMarginPctInput, 2);
|
||||
double actualDiffPct = offer.getDirection().equals(BUY.name())
|
||||
? getPercentageDifference(scaledOfferPrice, mktPrice)
|
||||
: getPercentageDifference(mktPrice, scaledOfferPrice);
|
||||
? getPercentageDifference(priceAsDouble, mktPrice)
|
||||
: getPercentageDifference(mktPrice, priceAsDouble);
|
||||
double pctDiffDelta = abs(expectedDiffPct) - abs(actualDiffPct);
|
||||
return isCalculatedPriceWithinErrorTolerance(pctDiffDelta,
|
||||
expectedDiffPct,
|
||||
actualDiffPct,
|
||||
mktPrice,
|
||||
scaledOfferPrice,
|
||||
priceAsDouble,
|
||||
offer);
|
||||
});
|
||||
}
|
||||
|
||||
private double getPercentageDifference(double price1, double price2) {
|
||||
return BigDecimal.valueOf(roundDouble((1 - (price1 / price2)), 5))
|
||||
.setScale(4, HALF_UP)
|
||||
.doubleValue();
|
||||
}
|
||||
|
||||
private boolean isCalculatedPriceWithinErrorTolerance(double delta,
|
||||
double expectedDiffPct,
|
||||
double actualDiffPct,
|
||||
@ -245,7 +314,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
actualDiffPct,
|
||||
mktPrice,
|
||||
scaledOfferPrice);
|
||||
log.warn(offer.toString());
|
||||
log.trace(offer.toString());
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -0,0 +1,265 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method.offer;
|
||||
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||
import static bisq.apitest.config.ApiTestConfig.XMR;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
import static protobuf.OfferDirection.SELL;
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class CreateXMROffersTest extends AbstractOfferTest {
|
||||
|
||||
private static final String MAKER_FEE_CURRENCY_CODE = BTC;
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
AbstractOfferTest.setUp();
|
||||
createXmrPaymentAccounts();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testCreateFixedPriceBuy1BTCFor200KXMROffer() {
|
||||
// Remember alt coin trades are BTC trades. When placing an offer, you are
|
||||
// offering to buy or sell BTC, not ETH, XMR, etc. In this test case,
|
||||
// Alice places an offer to BUY BTC.
|
||||
var newOffer = aliceClient.createFixedPricedOffer(BUY.name(),
|
||||
XMR,
|
||||
100_000_000L,
|
||||
75_000_000L,
|
||||
"0.005", // FIXED PRICE IN BTC FOR 1 XMR
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
alicesXmrAcct.getId());
|
||||
log.debug("Sell XMR (Buy BTC) offer:\n{}", toOfferTable.apply(newOffer));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsActivated());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals("0.00500000", newOffer.getPrice());
|
||||
assertEquals(100_000_000L, newOffer.getAmount());
|
||||
assertEquals(75_000_000L, newOffer.getMinAmount());
|
||||
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(alicesXmrAcct.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
||||
|
||||
genBtcBlockAndWaitForOfferPreparation();
|
||||
|
||||
newOffer = aliceClient.getOffer(newOfferId);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsActivated());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals("0.00500000", newOffer.getPrice());
|
||||
assertEquals(100_000_000L, newOffer.getAmount());
|
||||
assertEquals(75_000_000L, newOffer.getMinAmount());
|
||||
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(alicesXmrAcct.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testCreateFixedPriceSell1BTCFor200KXMROffer() {
|
||||
// Alice places an offer to SELL BTC for XMR.
|
||||
var newOffer = aliceClient.createFixedPricedOffer(SELL.name(),
|
||||
XMR,
|
||||
100_000_000L,
|
||||
50_000_000L,
|
||||
"0.005", // FIXED PRICE IN BTC (satoshis) FOR 1 XMR
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
alicesXmrAcct.getId());
|
||||
log.debug("Buy XMR (Sell BTC) offer:\n{}", toOfferTable.apply(newOffer));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsActivated());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals("0.00500000", newOffer.getPrice());
|
||||
assertEquals(100_000_000L, newOffer.getAmount());
|
||||
assertEquals(50_000_000L, newOffer.getMinAmount());
|
||||
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(alicesXmrAcct.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
||||
|
||||
genBtcBlockAndWaitForOfferPreparation();
|
||||
|
||||
newOffer = aliceClient.getOffer(newOfferId);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsActivated());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals("0.00500000", newOffer.getPrice());
|
||||
assertEquals(100_000_000L, newOffer.getAmount());
|
||||
assertEquals(50_000_000L, newOffer.getMinAmount());
|
||||
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(alicesXmrAcct.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testCreatePriceMarginBasedBuy1BTCOfferWithTriggerPrice() {
|
||||
double priceMarginPctInput = 1.00;
|
||||
double mktPriceAsDouble = aliceClient.getBtcPrice(XMR);
|
||||
String triggerPrice = calcPriceAsString(mktPriceAsDouble, Double.parseDouble("-0.001"), 8);
|
||||
var newOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
|
||||
XMR,
|
||||
100_000_000L,
|
||||
75_000_000L,
|
||||
priceMarginPctInput,
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
alicesXmrAcct.getId(),
|
||||
triggerPrice);
|
||||
log.debug("Pending Sell XMR (Buy BTC) offer:\n{}", toOfferTable.apply(newOffer));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsActivated());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
|
||||
// There is no trigger price while offer is pending.
|
||||
assertEquals(NO_TRIGGER_PRICE, newOffer.getTriggerPrice());
|
||||
|
||||
assertEquals(100_000_000L, newOffer.getAmount());
|
||||
assertEquals(75_000_000L, newOffer.getMinAmount());
|
||||
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(alicesXmrAcct.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
||||
|
||||
genBtcBlockAndWaitForOfferPreparation();
|
||||
|
||||
newOffer = aliceClient.getOffer(newOfferId);
|
||||
log.debug("Available Sell XMR (Buy BTC) offer:\n{}", toOfferTable.apply(newOffer));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsActivated());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
|
||||
// The trigger price should exist on the prepared offer.
|
||||
assertEquals(triggerPrice, newOffer.getTriggerPrice());
|
||||
|
||||
assertEquals(100_000_000L, newOffer.getAmount());
|
||||
assertEquals(75_000_000L, newOffer.getMinAmount());
|
||||
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(alicesXmrAcct.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testCreatePriceMarginBasedSell1BTCOffer() {
|
||||
// Alice places an offer to SELL BTC for XMR.
|
||||
double priceMarginPctInput = 0.50;
|
||||
var newOffer = aliceClient.createMarketBasedPricedOffer(SELL.name(),
|
||||
XMR,
|
||||
100_000_000L,
|
||||
50_000_000L,
|
||||
priceMarginPctInput,
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
alicesXmrAcct.getId(),
|
||||
NO_TRIGGER_PRICE);
|
||||
log.debug("Buy XMR (Sell BTC) offer:\n{}", toOfferTable.apply(newOffer));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsActivated());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(100_000_000L, newOffer.getAmount());
|
||||
assertEquals(50_000_000L, newOffer.getMinAmount());
|
||||
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(alicesXmrAcct.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
||||
|
||||
genBtcBlockAndWaitForOfferPreparation();
|
||||
|
||||
newOffer = aliceClient.getOffer(newOfferId);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsActivated());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
assertEquals(100_000_000L, newOffer.getAmount());
|
||||
assertEquals(50_000_000L, newOffer.getMinAmount());
|
||||
assertEquals(15_000_000, newOffer.getBuyerSecurityDeposit());
|
||||
assertEquals(alicesXmrAcct.getId(), newOffer.getPaymentAccountId());
|
||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||
assertEquals(BTC, newOffer.getCounterCurrencyCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(5)
|
||||
public void testGetAllMyXMROffers() {
|
||||
List<OfferInfo> offers = aliceClient.getMyOffersSortedByDate(XMR);
|
||||
log.debug("All of Alice's XMR offers:\n{}", toOffersTable.apply(offers));
|
||||
assertEquals(4, offers.size());
|
||||
log.debug("Alice's balances\n{}", formatBalancesTbls(aliceClient.getBalances()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(6)
|
||||
public void testGetAvailableXMROffers() {
|
||||
List<OfferInfo> offers = bobClient.getOffersSortedByDate(XMR);
|
||||
log.debug("All of Bob's available XMR offers:\n{}", toOffersTable.apply(offers));
|
||||
assertEquals(4, offers.size());
|
||||
log.debug("Bob's balances\n{}", formatBalancesTbls(bobClient.getBalances()));
|
||||
}
|
||||
|
||||
private void genBtcBlockAndWaitForOfferPreparation() {
|
||||
genBtcBlocksThenWait(1, 5000);
|
||||
}
|
||||
}
|
@ -30,11 +30,10 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static java.lang.String.format;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@ -52,7 +51,7 @@ public class ValidateCreateOfferTest extends AbstractOfferTest {
|
||||
100000000000L, // exceeds amount limit
|
||||
100000000000L,
|
||||
"10000.0000",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
usdAccount.getId()));
|
||||
assertEquals("UNKNOWN: An error occurred at task: ValidateOffer", exception.getMessage());
|
||||
}
|
||||
@ -68,7 +67,7 @@ public class ValidateCreateOfferTest extends AbstractOfferTest {
|
||||
10000000L,
|
||||
10000000L,
|
||||
"40000.0000",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
chfAccount.getId()));
|
||||
String expectedError = format("UNKNOWN: cannot create EUR offer with payment account %s", chfAccount.getId());
|
||||
assertEquals(expectedError, exception.getMessage());
|
||||
@ -85,7 +84,7 @@ public class ValidateCreateOfferTest extends AbstractOfferTest {
|
||||
10000000L,
|
||||
10000000L,
|
||||
"63000.0000",
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
audAccount.getId()));
|
||||
String expectedError = format("UNKNOWN: cannot create CAD offer with payment account %s", audAccount.getId());
|
||||
assertEquals(expectedError, exception.getMessage());
|
||||
|
@ -1,6 +1,7 @@
|
||||
package bisq.apitest.method.payment;
|
||||
|
||||
import bisq.core.api.model.PaymentAccountForm;
|
||||
import bisq.core.locale.FiatCurrency;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.locale.TradeCurrency;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
@ -17,10 +18,13 @@ import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@ -57,14 +61,23 @@ public class AbstractPaymentAccountTest extends MethodTest {
|
||||
static final String PROPERTY_NAME_BANK_ACCOUNT_NAME = "bankAccountName";
|
||||
static final String PROPERTY_NAME_BANK_ACCOUNT_NUMBER = "bankAccountNumber";
|
||||
static final String PROPERTY_NAME_BANK_ACCOUNT_TYPE = "bankAccountType";
|
||||
static final String PROPERTY_NAME_BANK_ADDRESS = "bankAddress";
|
||||
static final String PROPERTY_NAME_BANK_BRANCH = "bankBranch";
|
||||
static final String PROPERTY_NAME_BANK_BRANCH_CODE = "bankBranchCode";
|
||||
static final String PROPERTY_NAME_BANK_BRANCH_NAME = "bankBranchName";
|
||||
static final String PROPERTY_NAME_BANK_CODE = "bankCode";
|
||||
static final String PROPERTY_NAME_BANK_COUNTRY_CODE = "bankCountryCode";
|
||||
@SuppressWarnings("unused")
|
||||
static final String PROPERTY_NAME_BANK_ID = "bankId";
|
||||
static final String PROPERTY_NAME_BANK_NAME = "bankName";
|
||||
static final String PROPERTY_NAME_BANK_SWIFT_CODE = "bankSwiftCode";
|
||||
static final String PROPERTY_NAME_BRANCH_ID = "branchId";
|
||||
static final String PROPERTY_NAME_BIC = "bic";
|
||||
static final String PROPERTY_NAME_BENEFICIARY_NAME = "beneficiaryName";
|
||||
static final String PROPERTY_NAME_BENEFICIARY_ACCOUNT_NR = "beneficiaryAccountNr";
|
||||
static final String PROPERTY_NAME_BENEFICIARY_ADDRESS = "beneficiaryAddress";
|
||||
static final String PROPERTY_NAME_BENEFICIARY_CITY = "beneficiaryCity";
|
||||
static final String PROPERTY_NAME_BENEFICIARY_PHONE = "beneficiaryPhone";
|
||||
static final String PROPERTY_NAME_COUNTRY = "country";
|
||||
static final String PROPERTY_NAME_CITY = "city";
|
||||
static final String PROPERTY_NAME_CONTACT = "contact";
|
||||
@ -75,6 +88,11 @@ public class AbstractPaymentAccountTest extends MethodTest {
|
||||
static final String PROPERTY_NAME_HOLDER_NAME = "holderName";
|
||||
static final String PROPERTY_NAME_HOLDER_TAX_ID = "holderTaxId";
|
||||
static final String PROPERTY_NAME_IBAN = "iban";
|
||||
static final String PROPERTY_NAME_INTERMEDIARY_ADDRESS = "intermediaryAddress";
|
||||
static final String PROPERTY_NAME_INTERMEDIARY_BRANCH = "intermediaryBranch";
|
||||
static final String PROPERTY_NAME_INTERMEDIARY_COUNTRY_CODE = "intermediaryCountryCode";
|
||||
static final String PROPERTY_NAME_INTERMEDIARY_NAME = "intermediaryName";
|
||||
static final String PROPERTY_NAME_INTERMEDIARY_SWIFT_CODE = "intermediarySwiftCode";
|
||||
static final String PROPERTY_NAME_MOBILE_NR = "mobileNr";
|
||||
static final String PROPERTY_NAME_NATIONAL_ACCOUNT_ID = "nationalAccountId";
|
||||
static final String PROPERTY_NAME_PAY_ID = "payid";
|
||||
@ -83,7 +101,9 @@ public class AbstractPaymentAccountTest extends MethodTest {
|
||||
static final String PROPERTY_NAME_QUESTION = "question";
|
||||
static final String PROPERTY_NAME_REQUIREMENTS = "requirements";
|
||||
static final String PROPERTY_NAME_SALT = "salt";
|
||||
static final String PROPERTY_NAME_SELECTED_TRADE_CURRENCY = "selectedTradeCurrency";
|
||||
static final String PROPERTY_NAME_SORT_CODE = "sortCode";
|
||||
static final String PROPERTY_NAME_SPECIAL_INSTRUCTIONS = "specialInstructions";
|
||||
static final String PROPERTY_NAME_STATE = "state";
|
||||
static final String PROPERTY_NAME_TRADE_CURRENCIES = "tradeCurrencies";
|
||||
static final String PROPERTY_NAME_USERNAME = "userName";
|
||||
@ -110,7 +130,7 @@ public class AbstractPaymentAccountTest extends MethodTest {
|
||||
COMPLETED_FORM_MAP.clear();
|
||||
|
||||
File emptyForm = getPaymentAccountForm(aliceClient, paymentMethodId);
|
||||
// A short cut over the API:
|
||||
// A shortcut over the API:
|
||||
// File emptyForm = PAYMENT_ACCOUNT_FORM.getPaymentAccountForm(paymentMethodId);
|
||||
log.debug("{} Empty form saved to {}",
|
||||
testName(testInfo),
|
||||
@ -125,7 +145,13 @@ public class AbstractPaymentAccountTest extends MethodTest {
|
||||
PAYMENT_ACCOUNT_FORM.toJsonString(jsonForm),
|
||||
Object.class);
|
||||
assertNotNull(emptyForm);
|
||||
assertEquals(PROPERTY_VALUE_JSON_COMMENTS, emptyForm.get(PROPERTY_NAME_JSON_COMMENTS));
|
||||
|
||||
if (paymentMethodId.equals("SWIFT_ID")) {
|
||||
assertEquals(getSwiftFormComments(), emptyForm.get(PROPERTY_NAME_JSON_COMMENTS));
|
||||
} else {
|
||||
assertEquals(PROPERTY_VALUE_JSON_COMMENTS, emptyForm.get(PROPERTY_NAME_JSON_COMMENTS));
|
||||
}
|
||||
|
||||
assertEquals(paymentMethodId, emptyForm.get(PROPERTY_NAME_PAYMENT_METHOD_ID));
|
||||
assertEquals("your accountname", emptyForm.get(PROPERTY_NAME_ACCOUNT_NAME));
|
||||
for (String field : fields) {
|
||||
@ -149,6 +175,15 @@ public class AbstractPaymentAccountTest extends MethodTest {
|
||||
assertEquals(expectedCurrencyCode, paymentAccount.getSingleTradeCurrency().getCode());
|
||||
}
|
||||
|
||||
protected final void verifyAccountTradeCurrencies(Collection<FiatCurrency> expectedFiatCurrencies,
|
||||
PaymentAccount paymentAccount) {
|
||||
assertNotNull(paymentAccount.getTradeCurrencies());
|
||||
List<TradeCurrency> expectedTradeCurrencies = new ArrayList<>() {{
|
||||
addAll(expectedFiatCurrencies);
|
||||
}};
|
||||
assertArrayEquals(expectedTradeCurrencies.toArray(), paymentAccount.getTradeCurrencies().toArray());
|
||||
}
|
||||
|
||||
protected final void verifyAccountTradeCurrencies(List<TradeCurrency> expectedTradeCurrencies,
|
||||
PaymentAccount paymentAccount) {
|
||||
assertNotNull(paymentAccount.getTradeCurrencies());
|
||||
@ -164,14 +199,36 @@ public class AbstractPaymentAccountTest extends MethodTest {
|
||||
assertTrue(paymentAccount.isPresent());
|
||||
}
|
||||
|
||||
protected final String getCompletedFormAsJsonString() {
|
||||
File completedForm = fillPaymentAccountForm();
|
||||
protected final String getCompletedFormAsJsonString(List<String> comments) {
|
||||
File completedForm = fillPaymentAccountForm(comments);
|
||||
String jsonString = PAYMENT_ACCOUNT_FORM.toJsonString(completedForm);
|
||||
log.debug("Completed form: {}", jsonString);
|
||||
return jsonString;
|
||||
}
|
||||
|
||||
private File fillPaymentAccountForm() {
|
||||
protected final String getCompletedFormAsJsonString() {
|
||||
File completedForm = fillPaymentAccountForm(PROPERTY_VALUE_JSON_COMMENTS);
|
||||
String jsonString = PAYMENT_ACCOUNT_FORM.toJsonString(completedForm);
|
||||
log.debug("Completed form: {}", jsonString);
|
||||
return jsonString;
|
||||
}
|
||||
|
||||
protected final String getCommaDelimitedFiatCurrencyCodes(Collection<FiatCurrency> fiatCurrencies) {
|
||||
return fiatCurrencies.stream()
|
||||
.sorted(Comparator.comparing(TradeCurrency::getCode))
|
||||
.map(c -> c.getCurrency().getCurrencyCode())
|
||||
.collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
protected final List<String> getSwiftFormComments() {
|
||||
List<String> comments = new ArrayList<>();
|
||||
comments.addAll(PROPERTY_VALUE_JSON_COMMENTS);
|
||||
List<String> wrappedSwiftComments = Res.getWrappedAsList("payment.swift.info.account", 110);
|
||||
comments.addAll(wrappedSwiftComments);
|
||||
return comments;
|
||||
}
|
||||
|
||||
private File fillPaymentAccountForm(List<String> comments) {
|
||||
File tmpJsonForm = null;
|
||||
try {
|
||||
tmpJsonForm = File.createTempFile("temp_acct_form_",
|
||||
@ -182,7 +239,7 @@ public class AbstractPaymentAccountTest extends MethodTest {
|
||||
|
||||
writer.name(PROPERTY_NAME_JSON_COMMENTS);
|
||||
writer.beginArray();
|
||||
for (String s : PROPERTY_VALUE_JSON_COMMENTS) {
|
||||
for (String s : comments) {
|
||||
writer.value(s);
|
||||
}
|
||||
writer.endArray();
|
||||
|
@ -17,12 +17,13 @@
|
||||
|
||||
package bisq.apitest.method.payment;
|
||||
|
||||
import bisq.core.locale.FiatCurrency;
|
||||
import bisq.core.locale.TradeCurrency;
|
||||
import bisq.core.payment.AdvancedCashAccount;
|
||||
import bisq.core.payment.AliPayAccount;
|
||||
import bisq.core.payment.AustraliaPayid;
|
||||
import bisq.core.payment.AustraliaPayidAccount;
|
||||
import bisq.core.payment.CapitualAccount;
|
||||
import bisq.core.payment.CashDepositAccount;
|
||||
import bisq.core.payment.ChaseQuickPayAccount;
|
||||
import bisq.core.payment.ClearXchangeAccount;
|
||||
import bisq.core.payment.F2FAccount;
|
||||
import bisq.core.payment.FasterPaymentsAccount;
|
||||
@ -32,7 +33,9 @@ import bisq.core.payment.JapanBankAccount;
|
||||
import bisq.core.payment.MoneyBeamAccount;
|
||||
import bisq.core.payment.MoneyGramAccount;
|
||||
import bisq.core.payment.NationalBankAccount;
|
||||
import bisq.core.payment.PaxumAccount;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.PayseraAccount;
|
||||
import bisq.core.payment.PerfectMoneyAccount;
|
||||
import bisq.core.payment.PopmoneyAccount;
|
||||
import bisq.core.payment.PromptPayAccount;
|
||||
@ -41,6 +44,7 @@ import bisq.core.payment.SameBankAccount;
|
||||
import bisq.core.payment.SepaAccount;
|
||||
import bisq.core.payment.SepaInstantAccount;
|
||||
import bisq.core.payment.SpecificBanksAccount;
|
||||
import bisq.core.payment.SwiftAccount;
|
||||
import bisq.core.payment.SwishAccount;
|
||||
import bisq.core.payment.TransferwiseAccount;
|
||||
import bisq.core.payment.USPostalMoneyOrderAccount;
|
||||
@ -51,14 +55,17 @@ import bisq.core.payment.payload.BankAccountPayload;
|
||||
import bisq.core.payment.payload.CashDepositAccountPayload;
|
||||
import bisq.core.payment.payload.SameBankAccountPayload;
|
||||
import bisq.core.payment.payload.SpecificBanksAccountPayload;
|
||||
import bisq.core.payment.payload.SwiftAccountPayload;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@ -70,16 +77,23 @@ import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
||||
import static bisq.apitest.config.ApiTestConfig.EUR;
|
||||
import static bisq.apitest.config.ApiTestConfig.USD;
|
||||
import static bisq.apitest.config.HavenoAppConfig.alicedaemon;
|
||||
import static bisq.cli.TableFormat.formatPaymentAcctTbl;
|
||||
import static bisq.core.locale.CurrencyUtil.*;
|
||||
import static bisq.cli.table.builder.TableType.PAYMENT_ACCOUNT_TBL;
|
||||
import static bisq.core.locale.CurrencyUtil.getAllSortedFiatCurrencies;
|
||||
import static bisq.core.locale.CurrencyUtil.getTradeCurrency;
|
||||
import static bisq.core.payment.payload.PaymentMethod.*;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Comparator.comparing;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.table.builder.TableBuilder;
|
||||
|
||||
@SuppressWarnings({"OptionalGetWithoutIsPresent", "ConstantConditions"})
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@ -104,11 +118,18 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, ADVANCED_CASH_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Advanced Cash Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "0000 1111 2222");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, AdvancedCashAccount.SUPPORTED_CURRENCIES
|
||||
.stream()
|
||||
.map(TradeCurrency::getCode)
|
||||
.collect(Collectors.joining(",")));
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "RUB");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Advanced Cash Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
AdvancedCashAccount paymentAccount = (AdvancedCashAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountTradeCurrencies(getAllAdvancedCashCurrencies(), paymentAccount);
|
||||
verifyAccountTradeCurrencies(AdvancedCashAccount.SUPPORTED_CURRENCIES, paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
|
||||
paymentAccount.getSelectedTradeCurrency().getCode());
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||
@ -146,7 +167,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_ACCOUNT_NAME, "Credit Union Australia");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Australia Pay ID Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
AustraliaPayid paymentAccount = (AustraliaPayid) createPaymentAccount(aliceClient, jsonString);
|
||||
AustraliaPayidAccount paymentAccount = (AustraliaPayidAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("AUD", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
@ -156,6 +177,33 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateCapitualAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, CAPITUAL_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
CAPITUAL_ID,
|
||||
PROPERTY_NAME_ACCOUNT_NR);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, CAPITUAL_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Capitual Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NR, "1111 2222 3333-4");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, CapitualAccount.SUPPORTED_CURRENCIES
|
||||
.stream()
|
||||
.map(TradeCurrency::getCode)
|
||||
.collect(Collectors.joining(",")));
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "BRL");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Capitual Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
CapitualAccount paymentAccount = (CapitualAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountTradeCurrencies(CapitualAccount.SUPPORTED_CURRENCIES, paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
|
||||
paymentAccount.getSelectedTradeCurrency().getCode());
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateCashDepositAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, CASH_DEPOSIT_ID);
|
||||
@ -189,7 +237,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
CashDepositAccount paymentAccount = (CashDepositAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
|
||||
verifyAccountSingleTradeCurrency(EUR, paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||
Objects.requireNonNull(paymentAccount.getCountry()).code);
|
||||
@ -253,28 +301,6 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateChaseQuickPayAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, CHASE_QUICK_PAY_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
CHASE_QUICK_PAY_ID,
|
||||
PROPERTY_NAME_EMAIL,
|
||||
PROPERTY_NAME_HOLDER_NAME);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, CHASE_QUICK_PAY_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Quick Pay Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "johndoe@quickpay.com");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "John Doe");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
ChaseQuickPayAccount paymentAccount = (ChaseQuickPayAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateClearXChangeAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, CLEAR_X_CHANGE_ID);
|
||||
@ -290,7 +316,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
ClearXchangeAccount paymentAccount = (ClearXchangeAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
||||
verifyAccountSingleTradeCurrency(USD, paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL_OR_MOBILE_NR), paymentAccount.getEmailOrMobileNr());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
||||
@ -363,7 +389,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
HalCashAccount paymentAccount = (HalCashAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
|
||||
verifyAccountSingleTradeCurrency(EUR, paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_MOBILE_NR), paymentAccount.getMobileNr());
|
||||
print(paymentAccount);
|
||||
@ -448,7 +474,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
MoneyBeamAccount paymentAccount = (MoneyBeamAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
|
||||
verifyAccountSingleTradeCurrency(EUR, paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_ID), paymentAccount.getAccountId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||
@ -466,6 +492,11 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
PROPERTY_NAME_STATE);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, MONEY_GRAM_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Money Gram Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, MoneyGramAccount.SUPPORTED_CURRENCIES
|
||||
.stream()
|
||||
.map(TradeCurrency::getCode)
|
||||
.collect(Collectors.joining(",")));
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "INR");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_HOLDER_NAME, "John Doe");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "john@doe.info");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "US");
|
||||
@ -474,7 +505,9 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
MoneyGramAccount paymentAccount = (MoneyGramAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountTradeCurrencies(getAllMoneyGramCurrencies(), paymentAccount);
|
||||
verifyAccountTradeCurrencies(MoneyGramAccount.SUPPORTED_CURRENCIES, paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
|
||||
paymentAccount.getSelectedTradeCurrency().getCode());
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getFullName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
|
||||
@ -497,13 +530,65 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
PerfectMoneyAccount paymentAccount = (PerfectMoneyAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
||||
verifyAccountSingleTradeCurrency(USD, paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePaxumAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, PAXUM_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
PAXUM_ID,
|
||||
PROPERTY_NAME_EMAIL);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, PAXUM_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Paxum Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, PaxumAccount.SUPPORTED_CURRENCIES
|
||||
.stream()
|
||||
.map(TradeCurrency::getCode)
|
||||
.collect(Collectors.joining(",")));
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "SEK");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.net");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
PaxumAccount paymentAccount = (PaxumAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountTradeCurrencies(PaxumAccount.SUPPORTED_CURRENCIES, paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
|
||||
paymentAccount.getSelectedTradeCurrency().getCode());
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePayseraAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, PAYSERA_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
PAYSERA_ID,
|
||||
PROPERTY_NAME_EMAIL);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, PAYSERA_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Paysera Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, PayseraAccount.SUPPORTED_CURRENCIES
|
||||
.stream()
|
||||
.map(TradeCurrency::getCode)
|
||||
.collect(Collectors.joining(",")));
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "ZAR");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.net");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
PayseraAccount paymentAccount = (PayseraAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountTradeCurrencies(PayseraAccount.SUPPORTED_CURRENCIES, paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
|
||||
paymentAccount.getSelectedTradeCurrency().getCode());
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePopmoneyAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, POPMONEY_ID);
|
||||
@ -519,7 +604,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
PopmoneyAccount paymentAccount = (PopmoneyAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
||||
verifyAccountSingleTradeCurrency(USD, paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_ID), paymentAccount.getAccountId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
||||
@ -554,12 +639,19 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
PROPERTY_NAME_USERNAME);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, REVOLUT_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Revolut Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, RevolutAccount.SUPPORTED_CURRENCIES
|
||||
.stream()
|
||||
.map(TradeCurrency::getCode)
|
||||
.collect(Collectors.joining(",")));
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "QAR");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_USERNAME, "revolut123");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
RevolutAccount paymentAccount = (RevolutAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountTradeCurrencies(getAllRevolutCurrencies(), paymentAccount);
|
||||
verifyAccountTradeCurrencies(RevolutAccount.SUPPORTED_CURRENCIES, paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
|
||||
paymentAccount.getSelectedTradeCurrency().getCode());
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_USERNAME), paymentAccount.getUserName());
|
||||
print(paymentAccount);
|
||||
@ -631,7 +723,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||
Objects.requireNonNull(paymentAccount.getCountry()).code);
|
||||
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
|
||||
verifyAccountSingleTradeCurrency(EUR, paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_IBAN), paymentAccount.getIban());
|
||||
@ -662,7 +754,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||
Objects.requireNonNull(paymentAccount.getCountry()).code);
|
||||
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
|
||||
verifyAccountSingleTradeCurrency(EUR, paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_IBAN), paymentAccount.getIban());
|
||||
@ -720,6 +812,64 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateSwiftAccount(TestInfo testInfo) {
|
||||
// https://www.theswiftcodes.com
|
||||
File emptyForm = getEmptyForm(testInfo, SWIFT_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
SWIFT_ID,
|
||||
PROPERTY_NAME_BANK_SWIFT_CODE);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, SWIFT_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "IT Swift Acct w/ DE Intermediary");
|
||||
Collection<FiatCurrency> swiftCurrenciesSortedByCode = getAllSortedFiatCurrencies(comparing(TradeCurrency::getCode));
|
||||
String allFiatCodes = getCommaDelimitedFiatCurrencyCodes(swiftCurrenciesSortedByCode);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, allFiatCodes);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, EUR);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_SWIFT_CODE, "PASCITMMFIR");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_COUNTRY_CODE, "IT");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_NAME, "BANCA MONTE DEI PASCHI DI SIENA S.P.A.");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_BRANCH, "SUCC. DI FIRENZE");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BANK_ADDRESS, "Via dei Pecori, 8, 50123 Firenze FI, Italy");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BENEFICIARY_NAME, "Vito de' Medici");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BENEFICIARY_ACCOUNT_NR, "0000 1111 2222 3333");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BENEFICIARY_ADDRESS, "Via dei Pecori, 1, 50123 Firenze FI, Italy");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BENEFICIARY_CITY, "Firenze");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_BENEFICIARY_PHONE, "+39 055 222222");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SPECIAL_INSTRUCTIONS, "N/A");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_INTERMEDIARY_SWIFT_CODE, "DEUTDEFFXXX");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_INTERMEDIARY_COUNTRY_CODE, "DE");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_INTERMEDIARY_NAME, "Kosmo Krump");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_INTERMEDIARY_ADDRESS, "TAUNUSANLAGE 12, FRANKFURT AM MAIN, 60262");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_INTERMEDIARY_BRANCH, "Deutsche Bank Frankfurt F");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Swift Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString(getSwiftFormComments());
|
||||
SwiftAccount paymentAccount = (SwiftAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountTradeCurrencies(swiftCurrenciesSortedByCode, paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
|
||||
paymentAccount.getSelectedTradeCurrency().getCode());
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
SwiftAccountPayload payload = (SwiftAccountPayload) paymentAccount.getPaymentAccountPayload();
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_SWIFT_CODE), payload.getBankSwiftCode());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_COUNTRY_CODE), payload.getBankCountryCode());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_NAME), payload.getBankName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_BRANCH), payload.getBankBranch());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BANK_ADDRESS), payload.getBankAddress());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BENEFICIARY_NAME), payload.getBeneficiaryName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BENEFICIARY_ACCOUNT_NR), payload.getBeneficiaryAccountNr());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BENEFICIARY_ADDRESS), payload.getBeneficiaryAddress());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BENEFICIARY_CITY), payload.getBeneficiaryCity());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_BENEFICIARY_PHONE), payload.getBeneficiaryPhone());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SPECIAL_INSTRUCTIONS), payload.getSpecialInstructions());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_INTERMEDIARY_SWIFT_CODE), payload.getIntermediarySwiftCode());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_INTERMEDIARY_COUNTRY_CODE), payload.getIntermediaryCountryCode());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_INTERMEDIARY_NAME), payload.getIntermediaryName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_INTERMEDIARY_BRANCH), payload.getIntermediaryBranch());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_INTERMEDIARY_ADDRESS), payload.getIntermediaryAddress());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateSwishAccount(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, SWISH_ID);
|
||||
@ -751,17 +901,16 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
PROPERTY_NAME_EMAIL);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, TRANSFERWISE_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Transferwise Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, "eur");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, "NZD");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "NZD");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.info");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
TransferwiseAccount paymentAccount = (TransferwiseAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
assertEquals(1, paymentAccount.getTradeCurrencies().size());
|
||||
TradeCurrency expectedCurrency = getTradeCurrency("EUR").get();
|
||||
assertEquals(expectedCurrency, paymentAccount.getSelectedTradeCurrency());
|
||||
List<TradeCurrency> expectedTradeCurrencies = singletonList(expectedCurrency);
|
||||
verifyAccountTradeCurrencies(expectedTradeCurrencies, paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
|
||||
paymentAccount.getSelectedTradeCurrency().getCode());
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
|
||||
print(paymentAccount);
|
||||
@ -775,7 +924,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
PROPERTY_NAME_EMAIL);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, TRANSFERWISE_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Transferwise Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, "ars, cad, hrk, czk, eur, hkd, idr, jpy, chf, nzd");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, "ARS,CAD,HRK,CZK,EUR,HKD,IDR,JPY,CHF,NZD");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "CHF");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.info");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
@ -787,7 +937,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
add(getTradeCurrency("CAD").get());
|
||||
add(getTradeCurrency("HRK").get());
|
||||
add(getTradeCurrency("CZK").get());
|
||||
add(getTradeCurrency("EUR").get());
|
||||
add(getTradeCurrency(EUR).get());
|
||||
add(getTradeCurrency("HKD").get());
|
||||
add(getTradeCurrency("IDR").get());
|
||||
add(getTradeCurrency("JPY").get());
|
||||
@ -795,8 +945,34 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
add(getTradeCurrency("NZD").get());
|
||||
}};
|
||||
verifyAccountTradeCurrencies(expectedTradeCurrencies, paymentAccount);
|
||||
TradeCurrency expectedSelectedCurrency = expectedTradeCurrencies.get(0);
|
||||
assertEquals(expectedSelectedCurrency, paymentAccount.getSelectedTradeCurrency());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
|
||||
paymentAccount.getSelectedTradeCurrency().getCode());
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
|
||||
print(paymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTransferwiseAccountWithSupportedTradeCurrencies(TestInfo testInfo) {
|
||||
File emptyForm = getEmptyForm(testInfo, TRANSFERWISE_ID);
|
||||
verifyEmptyForm(emptyForm,
|
||||
TRANSFERWISE_ID,
|
||||
PROPERTY_NAME_EMAIL);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, TRANSFERWISE_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Transferwise Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, TransferwiseAccount.SUPPORTED_CURRENCIES
|
||||
.stream()
|
||||
.map(TradeCurrency::getCode)
|
||||
.collect(Collectors.joining(",")));
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "AUD");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "jane@doe.info");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
TransferwiseAccount paymentAccount = (TransferwiseAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountTradeCurrencies(TransferwiseAccount.SUPPORTED_CURRENCIES, paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
|
||||
paymentAccount.getSelectedTradeCurrency().getCode());
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
|
||||
print(paymentAccount);
|
||||
@ -836,7 +1012,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||
createPaymentAccount(aliceClient, jsonString));
|
||||
assertEquals("INVALID_ARGUMENT: no trade currencies defined for transferwise payment account",
|
||||
assertEquals("INVALID_ARGUMENT: no trade currency defined for transferwise payment account",
|
||||
exception.getMessage());
|
||||
}
|
||||
|
||||
@ -849,11 +1025,18 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, UPHOLD_ID);
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Uphold Acct");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_ID, "UA 9876");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_TRADE_CURRENCIES, UpholdAccount.SUPPORTED_CURRENCIES
|
||||
.stream()
|
||||
.map(TradeCurrency::getCode)
|
||||
.collect(Collectors.joining(",")));
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SELECTED_TRADE_CURRENCY, "MXN");
|
||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Uphold Acct Salt"));
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
UpholdAccount paymentAccount = (UpholdAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountTradeCurrencies(getAllUpholdCurrencies(), paymentAccount);
|
||||
verifyAccountTradeCurrencies(UpholdAccount.SUPPORTED_CURRENCIES, paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
|
||||
paymentAccount.getSelectedTradeCurrency().getCode());
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_ID), paymentAccount.getAccountId());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||
@ -875,7 +1058,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
USPostalMoneyOrderAccount paymentAccount = (USPostalMoneyOrderAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
||||
verifyAccountSingleTradeCurrency(USD, paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_POSTAL_ADDRESS), paymentAccount.getPostalAddress());
|
||||
@ -923,7 +1106,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
String jsonString = getCompletedFormAsJsonString();
|
||||
WesternUnionAccount paymentAccount = (WesternUnionAccount) createPaymentAccount(aliceClient, jsonString);
|
||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
||||
verifyAccountSingleTradeCurrency(USD, paymentAccount);
|
||||
verifyCommonFormEntries(paymentAccount);
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getFullName());
|
||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_CITY), paymentAccount.getCity());
|
||||
@ -942,7 +1125,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
private void print(PaymentAccount paymentAccount) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
|
||||
log.debug("\n{}", formatPaymentAcctTbl(singletonList(paymentAccount.toProtoMessage())));
|
||||
log.debug("\n{}", new TableBuilder(PAYMENT_ACCOUNT_TBL, paymentAccount.toProtoMessage()).build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,28 +2,47 @@ package bisq.apitest.method.trade;
|
||||
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
|
||||
import static bisq.cli.TradeFormat.format;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
|
||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_UNLOCKED;
|
||||
import static bisq.core.trade.Trade.Phase.PAYMENT_SENT;
|
||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.Trade.State.BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG;
|
||||
import static bisq.core.trade.Trade.State.DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN;
|
||||
import static bisq.core.trade.Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG;
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.System.out;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.offer.AbstractOfferTest;
|
||||
import bisq.cli.CliMain;
|
||||
import bisq.cli.GrpcClient;
|
||||
import bisq.cli.table.builder.TableBuilder;
|
||||
|
||||
public class AbstractTradeTest extends AbstractOfferTest {
|
||||
|
||||
public static final ExpectedProtocolStatus EXPECTED_PROTOCOL_STATUS = new ExpectedProtocolStatus();
|
||||
|
||||
// A Trade ID cache for use in @Test sequences.
|
||||
@Getter
|
||||
protected static String tradeId;
|
||||
|
||||
protected final Supplier<Integer> maxTradeStateAndPhaseChecks = () -> isLongRunningTest ? 10 : 2;
|
||||
protected final Function<TradeInfo, String> toTradeDetailTable = (trade) ->
|
||||
new TableBuilder(TRADE_DETAIL_TBL, trade).build().toString();
|
||||
protected final Function<GrpcClient, String> toUserName = (client) -> client.equals(aliceClient) ? "Alice" : "Bob";
|
||||
|
||||
@BeforeAll
|
||||
public static void initStaticFixtures() {
|
||||
@ -32,13 +51,129 @@ public class AbstractTradeTest extends AbstractOfferTest {
|
||||
|
||||
protected final TradeInfo takeAlicesOffer(String offerId,
|
||||
String paymentAccountId) {
|
||||
return bobClient.takeOffer(offerId, paymentAccountId);
|
||||
return takeAlicesOffer(offerId,
|
||||
paymentAccountId,
|
||||
true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected final TradeInfo takeBobsOffer(String offerId,
|
||||
String paymentAccountId) {
|
||||
return aliceClient.takeOffer(offerId, paymentAccountId);
|
||||
protected final TradeInfo takeAlicesOffer(String offerId,
|
||||
String paymentAccountId,
|
||||
boolean generateBtcBlock) {
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
var trade = bobClient.takeOffer(offerId,
|
||||
paymentAccountId);
|
||||
assertNotNull(trade);
|
||||
assertEquals(offerId, trade.getTradeId());
|
||||
|
||||
// Cache the trade id for the other tests.
|
||||
tradeId = trade.getTradeId();
|
||||
|
||||
if (generateBtcBlock)
|
||||
genBtcBlocksThenWait(1, 6_000);
|
||||
|
||||
return trade;
|
||||
}
|
||||
|
||||
protected final void waitForDepositConfirmation(Logger log,
|
||||
TestInfo testInfo,
|
||||
GrpcClient grpcClient,
|
||||
String tradeId) {
|
||||
Predicate<TradeInfo> isTradeInDepositUnlockedStateAndPhase = (t) ->
|
||||
t.getState().equals(DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN.name())
|
||||
&& t.getPhase().equals(DEPOSIT_UNLOCKED.name());
|
||||
|
||||
String userName = toUserName.apply(grpcClient);
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
TradeInfo trade = grpcClient.getTrade(tradeId);
|
||||
if (!isTradeInDepositUnlockedStateAndPhase.test(trade)) {
|
||||
log.warn("{} still waiting on trade {} tx {}: DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN, attempt # {}",
|
||||
userName,
|
||||
trade.getShortId(),
|
||||
trade.getMakerDepositTxId(),
|
||||
trade.getTakerDepositTxId(),
|
||||
i);
|
||||
genBtcBlocksThenWait(1, 4_000);
|
||||
} else {
|
||||
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN)
|
||||
.setPhase(DEPOSIT_UNLOCKED)
|
||||
.setDepositPublished(true)
|
||||
.setDepositConfirmed(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log,
|
||||
testInfo,
|
||||
userName + "'s view after deposit is confirmed",
|
||||
trade);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final void verifyTakerDepositConfirmed(TradeInfo trade) {
|
||||
if (!trade.getIsDepositUnlocked()) {
|
||||
fail(format("INVALID_PHASE for trade %s in STATE=%s PHASE=%s, deposit tx never unlocked.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase()));
|
||||
}
|
||||
}
|
||||
|
||||
protected final void waitForBuyerSeesPaymentInitiatedMessage(Logger log,
|
||||
TestInfo testInfo,
|
||||
GrpcClient grpcClient,
|
||||
String tradeId) {
|
||||
String userName = toUserName.apply(grpcClient);
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
TradeInfo trade = grpcClient.getTrade(tradeId);
|
||||
if (!trade.getIsPaymentSent()) {
|
||||
log.warn("{} still waiting for trade {} {}, attempt # {}",
|
||||
userName,
|
||||
trade.getShortId(),
|
||||
BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG,
|
||||
i);
|
||||
sleep(5_000);
|
||||
} else {
|
||||
// Do not check trade.getOffer().getState() here because
|
||||
// it might be AVAILABLE, not OFFER_FEE_RESERVED.
|
||||
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG)
|
||||
.setPhase(PAYMENT_SENT)
|
||||
.setPaymentStartedMessageSent(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, userName + "'s view after confirming trade payment sent", trade);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final void waitForSellerSeesPaymentInitiatedMessage(Logger log,
|
||||
TestInfo testInfo,
|
||||
GrpcClient grpcClient,
|
||||
String tradeId) {
|
||||
Predicate<TradeInfo> isTradeInPaymentReceiptConfirmedStateAndPhase = (t) ->
|
||||
t.getState().equals(SELLER_RECEIVED_PAYMENT_SENT_MSG.name()) &&
|
||||
(t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(PAYMENT_SENT.name()));
|
||||
String userName = toUserName.apply(grpcClient);
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
TradeInfo trade = grpcClient.getTrade(tradeId);
|
||||
if (!isTradeInPaymentReceiptConfirmedStateAndPhase.test(trade)) {
|
||||
log.warn("INVALID_PHASE for {}'s trade {} in STATE={} PHASE={}, cannot confirm payment received yet.",
|
||||
userName,
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase());
|
||||
sleep(10_000);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TradeInfo trade = grpcClient.getTrade(tradeId);
|
||||
if (!isTradeInPaymentReceiptConfirmedStateAndPhase.test(trade)) {
|
||||
fail(format("INVALID_PHASE for %s's trade %s in STATE=%s PHASE=%s, cannot confirm payment received.",
|
||||
userName,
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase()));
|
||||
}
|
||||
}
|
||||
|
||||
protected final void verifyExpectedProtocolStatus(TradeInfo trade) {
|
||||
@ -49,35 +184,54 @@ public class AbstractTradeTest extends AbstractOfferTest {
|
||||
if (!isLongRunningTest)
|
||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isDepositPublished, trade.getIsDepositPublished());
|
||||
|
||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isDepositUnlocked, trade.getIsDepositUnlocked());
|
||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isPaymentSent, trade.getIsPaymentSent());
|
||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isPaymentReceived, trade.getIsPaymentReceived());
|
||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isDepositConfirmed, trade.getIsDepositUnlocked());
|
||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isPaymentStartedMessageSent, trade.getIsPaymentSent());
|
||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isPaymentReceivedMessageSent, trade.getIsPaymentReceived());
|
||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isPayoutPublished, trade.getIsPayoutPublished());
|
||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isWithdrawn, trade.getIsWithdrawn());
|
||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isCompleted, trade.getIsCompleted());
|
||||
}
|
||||
|
||||
protected final void logBalances(Logger log, TestInfo testInfo) {
|
||||
var alicesBalances = aliceClient.getBalances();
|
||||
log.debug("{} Alice's Current Balances:\n{}",
|
||||
testName(testInfo),
|
||||
formatBalancesTbls(alicesBalances));
|
||||
var bobsBalances = bobClient.getBalances();
|
||||
log.debug("{} Bob's Current Balances:\n{}",
|
||||
testName(testInfo),
|
||||
formatBalancesTbls(bobsBalances));
|
||||
}
|
||||
|
||||
protected final void logTrade(Logger log,
|
||||
TestInfo testInfo,
|
||||
String description,
|
||||
TradeInfo trade) {
|
||||
logTrade(log, testInfo, description, trade, false);
|
||||
}
|
||||
|
||||
protected final void logTrade(Logger log,
|
||||
TestInfo testInfo,
|
||||
String description,
|
||||
TradeInfo trade,
|
||||
boolean force) {
|
||||
if (force)
|
||||
log.info(String.format("%s %s%n%s",
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(format("%s %s%n%s",
|
||||
testName(testInfo),
|
||||
description.toUpperCase(),
|
||||
format(trade)));
|
||||
else if (log.isDebugEnabled()) {
|
||||
log.debug(String.format("%s %s%n%s",
|
||||
testName(testInfo),
|
||||
description.toUpperCase(),
|
||||
format(trade)));
|
||||
description,
|
||||
new TableBuilder(TRADE_DETAIL_TBL, trade).build()));
|
||||
}
|
||||
}
|
||||
|
||||
protected static void runCliGetTrade(String tradeId) {
|
||||
out.println("Alice's CLI 'gettrade' response:");
|
||||
CliMain.main(new String[]{"--password=xyz", "--port=9998", "gettrade", "--trade-id=" + tradeId});
|
||||
out.println("Bob's CLI 'gettrade' response:");
|
||||
CliMain.main(new String[]{"--password=xyz", "--port=9999", "gettrade", "--trade-id=" + tradeId});
|
||||
}
|
||||
|
||||
protected static void runCliGetOpenTrades() {
|
||||
out.println("Alice's CLI 'gettrades --category=open' response:");
|
||||
CliMain.main(new String[]{"--password=xyz", "--port=9998", "gettrades", "--category=open"});
|
||||
out.println("Bob's CLI 'gettrades --category=open' response:");
|
||||
CliMain.main(new String[]{"--password=xyz", "--port=9999", "gettrades", "--category=open"});
|
||||
}
|
||||
|
||||
protected static void runCliGetClosedTrades() {
|
||||
out.println("Alice's CLI 'gettrades --category=closed' response:");
|
||||
CliMain.main(new String[]{"--password=xyz", "--port=9998", "gettrades", "--category=closed"});
|
||||
out.println("Bob's CLI 'gettrades --category=closed' response:");
|
||||
CliMain.main(new String[]{"--password=xyz", "--port=9999", "gettrades", "--category=closed"});
|
||||
}
|
||||
}
|
||||
|
@ -10,11 +10,11 @@ public class ExpectedProtocolStatus {
|
||||
Trade.State state;
|
||||
Trade.Phase phase;
|
||||
boolean isDepositPublished;
|
||||
boolean isDepositUnlocked;
|
||||
boolean isPaymentSent;
|
||||
boolean isPaymentReceived;
|
||||
boolean isDepositConfirmed;
|
||||
boolean isPaymentStartedMessageSent;
|
||||
boolean isPaymentReceivedMessageSent;
|
||||
boolean isPayoutPublished;
|
||||
boolean isWithdrawn;
|
||||
boolean isCompleted;
|
||||
|
||||
public ExpectedProtocolStatus setState(Trade.State state) {
|
||||
this.state = state;
|
||||
@ -31,18 +31,18 @@ public class ExpectedProtocolStatus {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedProtocolStatus setDepositUnlocked(boolean depositUnlocked) {
|
||||
isDepositUnlocked = depositUnlocked;
|
||||
public ExpectedProtocolStatus setDepositConfirmed(boolean depositConfirmed) {
|
||||
isDepositConfirmed = depositConfirmed;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedProtocolStatus setFiatSent(boolean paymentSent) {
|
||||
isPaymentSent = paymentSent;
|
||||
public ExpectedProtocolStatus setPaymentStartedMessageSent(boolean paymentStartedMessageSent) {
|
||||
isPaymentStartedMessageSent = paymentStartedMessageSent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedProtocolStatus setFiatReceived(boolean paymentReceived) {
|
||||
isPaymentReceived = paymentReceived;
|
||||
public ExpectedProtocolStatus setPaymentReceivedMessageSent(boolean paymentReceivedMessageSent) {
|
||||
isPaymentReceivedMessageSent = paymentReceivedMessageSent;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -51,8 +51,8 @@ public class ExpectedProtocolStatus {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedProtocolStatus setWithdrawn(boolean withdrawn) {
|
||||
isWithdrawn = withdrawn;
|
||||
public ExpectedProtocolStatus setCompleted(boolean completed) {
|
||||
isCompleted = completed;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -60,10 +60,10 @@ public class ExpectedProtocolStatus {
|
||||
state = null;
|
||||
phase = null;
|
||||
isDepositPublished = false;
|
||||
isDepositUnlocked = false;
|
||||
isPaymentSent = false;
|
||||
isPaymentReceived = false;
|
||||
isDepositConfirmed = false;
|
||||
isPaymentStartedMessageSent = false;
|
||||
isPaymentReceivedMessageSent = false;
|
||||
isPayoutPublished = false;
|
||||
isWithdrawn = false;
|
||||
isCompleted = false;
|
||||
}
|
||||
}
|
||||
|
@ -19,12 +19,8 @@ package bisq.apitest.method.trade;
|
||||
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
@ -34,18 +30,15 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_UNLOCKED;
|
||||
import static bisq.core.trade.Trade.Phase.PAYMENT_SENT;
|
||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||
import static bisq.apitest.config.ApiTestConfig.USD;
|
||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.Trade.State.*;
|
||||
import static java.lang.String.format;
|
||||
import static bisq.core.trade.Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG;
|
||||
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static protobuf.Offer.State.OFFER_FEE_RESERVED;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
import static protobuf.OpenOffer.State.AVAILABLE;
|
||||
|
||||
@Disabled
|
||||
@ -61,62 +54,35 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||
try {
|
||||
PaymentAccount alicesUsdAccount = createDummyF2FAccount(aliceClient, "US");
|
||||
var alicesOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
|
||||
"usd",
|
||||
USD,
|
||||
12_500_000L,
|
||||
12_500_000L, // min-amount = amount
|
||||
0.00,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
alicesUsdAccount.getId());
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
alicesUsdAccount.getId(),
|
||||
NO_TRIGGER_PRICE);
|
||||
var offerId = alicesOffer.getId();
|
||||
|
||||
// Wait for Alice's AddToOfferBook task.
|
||||
// Wait times vary; my logs show >= 2 second delay.
|
||||
sleep(3000); // TODO loop instead of hard code wait time
|
||||
var alicesUsdOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), "usd");
|
||||
// Wait times vary; my logs show >= 2-second delay.
|
||||
sleep(3_000); // TODO loop instead of hard code a wait time
|
||||
var alicesUsdOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), USD);
|
||||
assertEquals(1, alicesUsdOffers.size());
|
||||
|
||||
PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobClient, "US");
|
||||
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId());
|
||||
assertNotNull(trade);
|
||||
assertEquals(offerId, trade.getTradeId());
|
||||
// Cache the trade id for the other tests.
|
||||
tradeId = trade.getTradeId();
|
||||
|
||||
genBtcBlocksThenWait(1, 4000);
|
||||
alicesUsdOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), "usd");
|
||||
var trade = takeAlicesOffer(offerId,
|
||||
bobsUsdAccount.getId(),
|
||||
false);
|
||||
sleep(2_500); // Allow available offer to be removed from offer book.
|
||||
alicesUsdOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), USD);
|
||||
assertEquals(0, alicesUsdOffers.size());
|
||||
genBtcBlocksThenWait(1, 2_500);
|
||||
waitForDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
|
||||
|
||||
genBtcBlocksThenWait(1, 2500);
|
||||
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
trade = bobClient.getTrade(trade.getTradeId());
|
||||
|
||||
if (!trade.getIsDepositUnlocked()) {
|
||||
log.warn("Bob still waiting on trade {} maker tx {} taker tx {}: DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN, attempt # {}",
|
||||
trade.getShortId(),
|
||||
trade.getMakerDepositTxId(),
|
||||
trade.getTakerDepositTxId(),
|
||||
i);
|
||||
genBtcBlocksThenWait(1, 4000);
|
||||
continue;
|
||||
} else {
|
||||
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN)
|
||||
.setPhase(DEPOSIT_UNLOCKED)
|
||||
.setDepositPublished(true)
|
||||
.setDepositUnlocked(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Bob's view after deposit is unlocked", trade, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!trade.getIsDepositUnlocked()) {
|
||||
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, deposit tx never unlocked.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase()));
|
||||
}
|
||||
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
verifyTakerDepositConfirmed(trade);
|
||||
logTrade(log, testInfo, "Alice's Maker/Buyer View", aliceClient.getTrade(tradeId));
|
||||
logTrade(log, testInfo, "Bob's Taker/Seller View", bobClient.getTrade(tradeId));
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
@ -127,56 +93,10 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||
public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) {
|
||||
try {
|
||||
var trade = aliceClient.getTrade(tradeId);
|
||||
|
||||
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
||||
t.getState().equals(DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN.name())
|
||||
&& t.getPhase().equals(DEPOSIT_UNLOCKED.name());
|
||||
|
||||
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
log.warn("INVALID_PHASE for Alice's trade {} in STATE={} PHASE={}, cannot confirm payment started yet.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase());
|
||||
// fail("Bad trade state and phase.");
|
||||
sleep(1000 * 10);
|
||||
trade = aliceClient.getTrade(tradeId);
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
fail(format("INVALID_PHASE for Alice's trade %s in STATE=%s PHASE=%s, could not confirm payment started.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase()));
|
||||
}
|
||||
|
||||
waitForDepositConfirmation(log, testInfo, aliceClient, trade.getTradeId());
|
||||
aliceClient.confirmPaymentStarted(trade.getTradeId());
|
||||
sleep(6000);
|
||||
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
trade = aliceClient.getTrade(tradeId);
|
||||
|
||||
if (!trade.getIsPaymentSent()) {
|
||||
log.warn("Alice still waiting for trade {} BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG, attempt # {}",
|
||||
trade.getShortId(),
|
||||
i);
|
||||
sleep(5000);
|
||||
continue;
|
||||
} else {
|
||||
assertEquals(OFFER_FEE_RESERVED.name(), trade.getOffer().getState());
|
||||
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG)
|
||||
.setPhase(PAYMENT_SENT)
|
||||
.setFiatSent(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Alice's view after confirming fiat payment sent", trade);
|
||||
break;
|
||||
}
|
||||
}
|
||||
sleep(6_000);
|
||||
waitForBuyerSeesPaymentInitiatedMessage(log, testInfo, aliceClient, tradeId);
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
@ -186,82 +106,19 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||
@Order(3)
|
||||
public void testBobsConfirmPaymentReceived(final TestInfo testInfo) {
|
||||
try {
|
||||
waitForSellerSeesPaymentInitiatedMessage(log, testInfo, bobClient, tradeId);
|
||||
var trade = bobClient.getTrade(tradeId);
|
||||
|
||||
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
||||
t.getState().equals(SELLER_RECEIVED_PAYMENT_SENT_MSG.name())
|
||||
&& (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(PAYMENT_SENT.name()));
|
||||
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
log.warn("INVALID_PHASE for Bob's trade {} in STATE={} PHASE={}, cannot confirm payment received yet.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase());
|
||||
// fail("Bad trade state and phase.");
|
||||
sleep(1000 * 10);
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, cannot confirm payment received.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase()));
|
||||
}
|
||||
|
||||
bobClient.confirmPaymentReceived(trade.getTradeId());
|
||||
sleep(3000);
|
||||
|
||||
sleep(3_000);
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
// Note: offer.state == available
|
||||
assertEquals(AVAILABLE.name(), trade.getOffer().getState());
|
||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
||||
.setPhase(PAYOUT_PUBLISHED)
|
||||
.setPayoutPublished(true)
|
||||
.setFiatReceived(true);
|
||||
.setPaymentReceivedMessageSent(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Bob's view after confirming fiat payment received", trade);
|
||||
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testAlicesKeepFunds(final TestInfo testInfo) {
|
||||
try {
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
|
||||
var trade = aliceClient.getTrade(tradeId);
|
||||
logTrade(log, testInfo, "Alice's view before keeping funds", trade);
|
||||
|
||||
aliceClient.keepFunds(tradeId);
|
||||
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
|
||||
trade = aliceClient.getTrade(tradeId);
|
||||
EXPECTED_PROTOCOL_STATUS.setState(BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG)
|
||||
.setPhase(PAYOUT_PUBLISHED);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Alice's view after keeping funds", trade);
|
||||
|
||||
logTrade(log, testInfo, "Alice's Maker/Buyer View (Done)", aliceClient.getTrade(tradeId), true);
|
||||
logTrade(log, testInfo, "Bob's Taker/Seller View (Done)", bobClient.getTrade(tradeId), true);
|
||||
|
||||
var alicesBalances = aliceClient.getBalances();
|
||||
log.info("{} Alice's Current Balance:\n{}",
|
||||
testName(testInfo),
|
||||
formatBalancesTbls(alicesBalances));
|
||||
var bobsBalances = bobClient.getBalances();
|
||||
log.info("{} Bob's Current Balance:\n{}",
|
||||
testName(testInfo),
|
||||
formatBalancesTbls(bobsBalances));
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
|
@ -0,0 +1,253 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method.trade;
|
||||
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.payload.NationalBankAccountPayload;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static protobuf.Offer.State.OFFER_FEE_RESERVED;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
import static protobuf.OpenOffer.State.AVAILABLE;
|
||||
|
||||
/**
|
||||
* Test case verifies trade can be made with national bank payment method,
|
||||
* and json contracts exclude bank acct details until deposit tx is confirmed.
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class TakeBuyBTCOfferWithNationalBankAcctTest extends AbstractTradeTest {
|
||||
|
||||
// Alice is maker/buyer, Bob is taker/seller.
|
||||
|
||||
private static final String BRL = "BRL";
|
||||
|
||||
private static PaymentAccount alicesPaymentAccount;
|
||||
private static PaymentAccount bobsPaymentAccount;
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
setUp(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testTakeAlicesBuyOffer(final TestInfo testInfo) {
|
||||
try {
|
||||
alicesPaymentAccount = createDummyBRLAccount(aliceClient,
|
||||
"Alicia da Silva",
|
||||
String.valueOf(System.currentTimeMillis()),
|
||||
"123.456.789-01");
|
||||
bobsPaymentAccount = createDummyBRLAccount(bobClient,
|
||||
"Roberto da Silva",
|
||||
String.valueOf(System.currentTimeMillis()),
|
||||
"123.456.789-02");
|
||||
var alicesOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
|
||||
BRL,
|
||||
1_000_000L,
|
||||
1_000_000L, // min-amount = amount
|
||||
0.00,
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
alicesPaymentAccount.getId(),
|
||||
NO_TRIGGER_PRICE);
|
||||
var offerId = alicesOffer.getId();
|
||||
|
||||
// Wait for Alice's AddToOfferBook task.
|
||||
// Wait times vary; my logs show >= 2 second delay.
|
||||
sleep(3_000); // TODO loop instead of hard code wait time
|
||||
var alicesOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), BRL);
|
||||
assertEquals(1, alicesOffers.size());
|
||||
|
||||
|
||||
var trade = takeAlicesOffer(offerId,
|
||||
bobsPaymentAccount.getId(),
|
||||
false);
|
||||
|
||||
// Before generating a blk and confirming deposit tx, make sure there
|
||||
// are no bank acct details in the either side's contract.
|
||||
while (true) {
|
||||
try {
|
||||
var alicesContract = aliceClient.getTrade(trade.getTradeId()).getContractAsJson();
|
||||
var bobsContract = bobClient.getTrade(trade.getTradeId()).getContractAsJson();
|
||||
verifyJsonContractExcludesBankAccountDetails(alicesContract, alicesPaymentAccount);
|
||||
verifyJsonContractExcludesBankAccountDetails(alicesContract, bobsPaymentAccount);
|
||||
verifyJsonContractExcludesBankAccountDetails(bobsContract, alicesPaymentAccount);
|
||||
verifyJsonContractExcludesBankAccountDetails(bobsContract, bobsPaymentAccount);
|
||||
break;
|
||||
} catch (StatusRuntimeException ex) {
|
||||
if (ex.getMessage() == null) {
|
||||
String message = ex.getMessage().replaceFirst("^[A-Z_]+: ", "");
|
||||
if (message.contains("trade") && message.contains("not found")) {
|
||||
fail(ex);
|
||||
}
|
||||
} else {
|
||||
sleep(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
genBtcBlocksThenWait(1, 4000);
|
||||
alicesOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), BRL);
|
||||
assertEquals(0, alicesOffers.size());
|
||||
genBtcBlocksThenWait(1, 2_500);
|
||||
waitForDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
|
||||
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
verifyTakerDepositConfirmed(trade);
|
||||
logTrade(log, testInfo, "Alice's Maker/Buyer View", aliceClient.getTrade(tradeId));
|
||||
logTrade(log, testInfo, "Bob's Taker/Seller View", bobClient.getTrade(tradeId));
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testBankAcctDetailsIncludedInContracts(final TestInfo testInfo) {
|
||||
assertNotNull(alicesPaymentAccount);
|
||||
assertNotNull(bobsPaymentAccount);
|
||||
|
||||
var alicesTrade = aliceClient.getTrade(tradeId);
|
||||
assertNotEquals("", alicesTrade.getContract().getMakerPaymentAccountPayload().getPaymentDetails());
|
||||
assertNotEquals("", alicesTrade.getContract().getTakerPaymentAccountPayload().getPaymentDetails());
|
||||
var alicesContractJson = alicesTrade.getContractAsJson();
|
||||
verifyJsonContractIncludesBankAccountDetails(alicesContractJson, alicesPaymentAccount);
|
||||
verifyJsonContractIncludesBankAccountDetails(alicesContractJson, bobsPaymentAccount);
|
||||
|
||||
var bobsTrade = bobClient.getTrade(tradeId);
|
||||
assertNotEquals("", bobsTrade.getContract().getMakerPaymentAccountPayload().getPaymentDetails());
|
||||
assertNotEquals("", bobsTrade.getContract().getTakerPaymentAccountPayload().getPaymentDetails());
|
||||
var bobsContractJson = bobsTrade.getContractAsJson();
|
||||
verifyJsonContractIncludesBankAccountDetails(bobsContractJson, alicesPaymentAccount);
|
||||
verifyJsonContractIncludesBankAccountDetails(bobsContractJson, bobsPaymentAccount);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) {
|
||||
try {
|
||||
var trade = aliceClient.getTrade(tradeId);
|
||||
waitForDepositConfirmation(log, testInfo, aliceClient, trade.getTradeId());
|
||||
aliceClient.confirmPaymentStarted(trade.getTradeId());
|
||||
sleep(6_000);
|
||||
waitForBuyerSeesPaymentInitiatedMessage(log, testInfo, aliceClient, tradeId);
|
||||
trade = aliceClient.getTrade(tradeId);
|
||||
assertEquals(OFFER_FEE_RESERVED.name(), trade.getOffer().getState());
|
||||
logTrade(log, testInfo, "Alice's Maker/Buyer View (Payment Sent)", aliceClient.getTrade(tradeId));
|
||||
logTrade(log, testInfo, "Bob's Taker/Seller View (Payment Sent)", bobClient.getTrade(tradeId));
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testBobsConfirmPaymentReceived(final TestInfo testInfo) {
|
||||
try {
|
||||
waitForSellerSeesPaymentInitiatedMessage(log, testInfo, bobClient, tradeId);
|
||||
var trade = bobClient.getTrade(tradeId);
|
||||
bobClient.confirmPaymentReceived(trade.getTradeId());
|
||||
sleep(3_000);
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
// Note: offer.state == available
|
||||
assertEquals(AVAILABLE.name(), trade.getOffer().getState());
|
||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
||||
.setPhase(PAYOUT_PUBLISHED)
|
||||
.setPayoutPublished(true)
|
||||
.setPaymentReceivedMessageSent(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Bob's view after confirming fiat payment received", trade);
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyJsonContractExcludesBankAccountDetails(String jsonContract,
|
||||
PaymentAccount paymentAccount) {
|
||||
NationalBankAccountPayload nationalBankAccountPayload =
|
||||
(NationalBankAccountPayload) paymentAccount.getPaymentAccountPayload();
|
||||
// The client cannot know exactly when payment acct payloads are added to a contract,
|
||||
// so auto-failing here results in a flaky test.
|
||||
// assertFalse(jsonContract.contains(nationalBankAccountPayload.getNationalAccountId()));
|
||||
// assertFalse(jsonContract.contains(nationalBankAccountPayload.getBranchId()));
|
||||
// assertFalse(jsonContract.contains(nationalBankAccountPayload.getAccountNr()));
|
||||
// assertFalse(jsonContract.contains(nationalBankAccountPayload.getHolderName()));
|
||||
// assertFalse(jsonContract.contains(nationalBankAccountPayload.getHolderTaxId()));
|
||||
|
||||
// Log warning if bank acct details are found in json contract.
|
||||
if (jsonContract.contains(nationalBankAccountPayload.getNationalAccountId()))
|
||||
log.warn("Could not check json contract soon enough; it contains national bank acct id");
|
||||
|
||||
if (jsonContract.contains(nationalBankAccountPayload.getBranchId()))
|
||||
log.warn("Could not check json contract soon enough; it contains natl bank branch id");
|
||||
|
||||
if (jsonContract.contains(nationalBankAccountPayload.getAccountNr()))
|
||||
log.warn("Could not check json contract soon enough; it contains natl bank acct #");
|
||||
|
||||
if (jsonContract.contains(nationalBankAccountPayload.getHolderName()))
|
||||
log.warn("Could not check json contract soon enough; it contains natl bank acct holder name");
|
||||
|
||||
if (jsonContract.contains(nationalBankAccountPayload.getHolderTaxId()))
|
||||
log.warn("Could not check json contract soon enough; it contains natl bank acct holder tax id");
|
||||
}
|
||||
|
||||
private void verifyJsonContractIncludesBankAccountDetails(String jsonContract,
|
||||
PaymentAccount paymentAccount) {
|
||||
NationalBankAccountPayload nationalBankAccountPayload =
|
||||
(NationalBankAccountPayload) paymentAccount.getPaymentAccountPayload();
|
||||
assertTrue(jsonContract.contains(nationalBankAccountPayload.getNationalAccountId()));
|
||||
assertTrue(jsonContract.contains(nationalBankAccountPayload.getBranchId()));
|
||||
assertTrue(jsonContract.contains(nationalBankAccountPayload.getAccountNr()));
|
||||
assertTrue(jsonContract.contains(nationalBankAccountPayload.getHolderName()));
|
||||
assertTrue(jsonContract.contains(nationalBankAccountPayload.getHolderTaxId()));
|
||||
}
|
||||
}
|
@ -0,0 +1,144 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method.trade;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||
import static bisq.apitest.config.ApiTestConfig.XMR;
|
||||
import static bisq.cli.table.builder.TableType.OFFER_TBL;
|
||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static protobuf.Offer.State.OFFER_FEE_RESERVED;
|
||||
import static protobuf.OfferDirection.SELL;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.offer.AbstractOfferTest;
|
||||
import bisq.cli.table.builder.TableBuilder;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class TakeBuyXMROfferTest extends AbstractTradeTest {
|
||||
|
||||
// Alice is maker / xmr buyer (btc seller), Bob is taker / xmr seller (btc buyer).
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
AbstractOfferTest.setUp();
|
||||
createXmrPaymentAccounts();
|
||||
EXPECTED_PROTOCOL_STATUS.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testTakeAlicesSellBTCForXMROffer(final TestInfo testInfo) {
|
||||
try {
|
||||
// Alice is going to BUY XMR, but the Offer direction = SELL because it is a
|
||||
// BTC trade; Alice will SELL BTC for XMR. Bob will send Alice XMR.
|
||||
// Confused me, but just need to remember there are only BTC offers.
|
||||
var btcTradeDirection = SELL.name();
|
||||
var alicesOffer = aliceClient.createFixedPricedOffer(btcTradeDirection,
|
||||
XMR,
|
||||
15_000_000L,
|
||||
7_500_000L,
|
||||
"0.00455500", // FIXED PRICE IN BTC (satoshis) FOR 1 XMR
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
alicesXmrAcct.getId());
|
||||
log.debug("Alice's BUY XMR (SELL BTC) Offer:\n{}", new TableBuilder(OFFER_TBL, alicesOffer).build());
|
||||
genBtcBlocksThenWait(1, 5000);
|
||||
var offerId = alicesOffer.getId();
|
||||
|
||||
var alicesXmrOffers = aliceClient.getMyOffers(btcTradeDirection, XMR);
|
||||
assertEquals(1, alicesXmrOffers.size());
|
||||
var trade = takeAlicesOffer(offerId, bobsXmrAcct.getId());
|
||||
alicesXmrOffers = aliceClient.getMyOffersSortedByDate(XMR);
|
||||
assertEquals(0, alicesXmrOffers.size());
|
||||
genBtcBlocksThenWait(1, 2_500);
|
||||
waitForDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
|
||||
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
verifyTakerDepositConfirmed(trade);
|
||||
logTrade(log, testInfo, "Alice's Maker/Buyer View", aliceClient.getTrade(tradeId));
|
||||
logTrade(log, testInfo, "Bob's Taker/Seller View", bobClient.getTrade(tradeId));
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testBobsConfirmPaymentStarted(final TestInfo testInfo) {
|
||||
try {
|
||||
var trade = bobClient.getTrade(tradeId);
|
||||
|
||||
verifyTakerDepositConfirmed(trade);
|
||||
log.debug("Bob sends XMR payment to Alice for trade {}", trade.getTradeId());
|
||||
bobClient.confirmPaymentStarted(trade.getTradeId());
|
||||
sleep(3500);
|
||||
waitForBuyerSeesPaymentInitiatedMessage(log, testInfo, bobClient, tradeId);
|
||||
|
||||
logTrade(log, testInfo, "Alice's Maker/Buyer View (Payment Sent)", aliceClient.getTrade(tradeId));
|
||||
logTrade(log, testInfo, "Bob's Taker/Seller View (Payment Sent)", bobClient.getTrade(tradeId));
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testAlicesConfirmPaymentReceived(final TestInfo testInfo) {
|
||||
try {
|
||||
waitForSellerSeesPaymentInitiatedMessage(log, testInfo, aliceClient, tradeId);
|
||||
|
||||
sleep(2_000);
|
||||
var trade = aliceClient.getTrade(tradeId);
|
||||
// If we were trading BSQ, Alice would verify payment has been sent to her
|
||||
// Bisq wallet, but we can do no such checks for XMR payments.
|
||||
// All XMR transfers are done outside Bisq.
|
||||
log.debug("Alice verifies XMR payment was received from Bob, for trade {}", trade.getTradeId());
|
||||
aliceClient.confirmPaymentReceived(trade.getTradeId());
|
||||
sleep(3_000);
|
||||
|
||||
trade = aliceClient.getTrade(tradeId);
|
||||
assertEquals(OFFER_FEE_RESERVED.name(), trade.getOffer().getState());
|
||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
||||
.setPhase(PAYOUT_PUBLISHED)
|
||||
.setPayoutPublished(true)
|
||||
.setPaymentReceivedMessageSent(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Alice's Maker/Buyer View (Payment Received)", aliceClient.getTrade(tradeId));
|
||||
logTrade(log, testInfo, "Bob's Taker/Seller View (Payment Received)", bobClient.getTrade(tradeId));
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -19,12 +19,8 @@ package bisq.apitest.method.trade;
|
||||
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
@ -35,20 +31,16 @@ import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static bisq.core.trade.Trade.Phase.DEPOSIT_UNLOCKED;
|
||||
import static bisq.core.trade.Trade.Phase.PAYMENT_SENT;
|
||||
import static bisq.apitest.config.ApiTestConfig.USD;
|
||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.Trade.Phase.WITHDRAWN;
|
||||
import static bisq.core.trade.Trade.State.*;
|
||||
import static java.lang.String.format;
|
||||
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
|
||||
import static bisq.core.trade.Trade.State.WITHDRAW_COMPLETED;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static protobuf.Offer.State.OFFER_FEE_RESERVED;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
import static protobuf.OpenOffer.State.AVAILABLE;
|
||||
import static protobuf.OfferDirection.SELL;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@ -57,6 +49,9 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
||||
|
||||
// Alice is maker/seller, Bob is taker/buyer.
|
||||
|
||||
// Maker and Taker fees are in BTC.
|
||||
private static final String TRADE_FEE_CURRENCY_CODE = BTC;
|
||||
|
||||
private static final String WITHDRAWAL_TX_MEMO = "Bob's trade withdrawal";
|
||||
|
||||
@Test
|
||||
@ -65,63 +60,35 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
||||
try {
|
||||
PaymentAccount alicesUsdAccount = createDummyF2FAccount(aliceClient, "US");
|
||||
var alicesOffer = aliceClient.createMarketBasedPricedOffer(SELL.name(),
|
||||
"usd",
|
||||
USD,
|
||||
12_500_000L,
|
||||
12_500_000L, // min-amount = amount
|
||||
0.00,
|
||||
getDefaultBuyerSecurityDepositAsPercent(),
|
||||
alicesUsdAccount.getId());
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
alicesUsdAccount.getId(),
|
||||
NO_TRIGGER_PRICE);
|
||||
var offerId = alicesOffer.getId();
|
||||
|
||||
// Wait for Alice's AddToOfferBook task.
|
||||
// Wait times vary; my logs show >= 2 second delay, but taking sell offers
|
||||
// Wait times vary; my logs show >= 2-second delay, but taking sell offers
|
||||
// seems to require more time to prepare.
|
||||
sleep(3000); // TODO loop instead of hard code wait time
|
||||
var alicesUsdOffers = aliceClient.getMyOffersSortedByDate(SELL.name(), "usd");
|
||||
sleep(3_000); // TODO loop instead of hard code a wait time
|
||||
var alicesUsdOffers = aliceClient.getMyOffersSortedByDate(SELL.name(), USD);
|
||||
assertEquals(1, alicesUsdOffers.size());
|
||||
|
||||
PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobClient, "US");
|
||||
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId());
|
||||
assertNotNull(trade);
|
||||
assertEquals(offerId, trade.getTradeId());
|
||||
// Cache the trade id for the other tests.
|
||||
tradeId = trade.getTradeId();
|
||||
|
||||
genBtcBlocksThenWait(1, 4000);
|
||||
var takeableUsdOffers = bobClient.getOffersSortedByDate(SELL.name(), "usd");
|
||||
var trade = takeAlicesOffer(offerId,
|
||||
bobsUsdAccount.getId(),
|
||||
false);
|
||||
sleep(2_500); // Allow available offer to be removed from offer book.
|
||||
var takeableUsdOffers = bobClient.getOffersSortedByDate(SELL.name(), USD);
|
||||
assertEquals(0, takeableUsdOffers.size());
|
||||
|
||||
genBtcBlocksThenWait(1, 2500);
|
||||
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
trade = bobClient.getTrade(trade.getTradeId());
|
||||
|
||||
if (!trade.getIsDepositUnlocked()) {
|
||||
log.warn("Bob still waiting on trade {} maker tx {} taker tx {}: DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN, attempt # {}",
|
||||
trade.getShortId(),
|
||||
trade.getMakerDepositTxId(),
|
||||
trade.getTakerDepositTxId(),
|
||||
i);
|
||||
genBtcBlocksThenWait(1, 4000);
|
||||
continue;
|
||||
} else {
|
||||
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN)
|
||||
.setPhase(DEPOSIT_UNLOCKED)
|
||||
.setDepositPublished(true)
|
||||
.setDepositUnlocked(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Bob's view after deposit is confirmed", trade, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!trade.getIsDepositUnlocked()) {
|
||||
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, deposit tx never unlocked.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase()));
|
||||
}
|
||||
|
||||
genBtcBlocksThenWait(1, 2_500);
|
||||
waitForDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
verifyTakerDepositConfirmed(trade);
|
||||
logTrade(log, testInfo, "Alice's Maker/Buyer View", aliceClient.getTrade(tradeId));
|
||||
logTrade(log, testInfo, "Bob's Taker/Seller View", bobClient.getTrade(tradeId));
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
@ -132,54 +99,10 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
||||
public void testBobsConfirmPaymentStarted(final TestInfo testInfo) {
|
||||
try {
|
||||
var trade = bobClient.getTrade(tradeId);
|
||||
|
||||
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
||||
t.getState().equals(DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN.name()) && t.getPhase().equals(DEPOSIT_UNLOCKED.name());
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
log.warn("INVALID_PHASE for Bob's trade {} in STATE={} PHASE={}, cannot confirm payment started yet.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase());
|
||||
// fail("Bad trade state and phase.");
|
||||
sleep(1000 * 10);
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, could not confirm payment started.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase()));
|
||||
}
|
||||
|
||||
verifyTakerDepositConfirmed(trade);
|
||||
bobClient.confirmPaymentStarted(tradeId);
|
||||
sleep(6000);
|
||||
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
|
||||
if (!trade.getIsPaymentSent()) {
|
||||
log.warn("Bob still waiting for trade {} BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG, attempt # {}",
|
||||
trade.getShortId(),
|
||||
i);
|
||||
sleep(5000);
|
||||
continue;
|
||||
} else {
|
||||
// Note: offer.state == available
|
||||
assertEquals(AVAILABLE.name(), trade.getOffer().getState());
|
||||
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG)
|
||||
.setPhase(PAYMENT_SENT)
|
||||
.setFiatSent(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Bob's view after confirming fiat payment sent", trade);
|
||||
break;
|
||||
}
|
||||
}
|
||||
sleep(6_000);
|
||||
waitForBuyerSeesPaymentInitiatedMessage(log, testInfo, bobClient, tradeId);
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
@ -189,83 +112,21 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
||||
@Order(3)
|
||||
public void testAlicesConfirmPaymentReceived(final TestInfo testInfo) {
|
||||
try {
|
||||
waitForSellerSeesPaymentInitiatedMessage(log, testInfo, aliceClient, tradeId);
|
||||
|
||||
var trade = aliceClient.getTrade(tradeId);
|
||||
|
||||
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
||||
t.getState().equals(SELLER_RECEIVED_PAYMENT_SENT_MSG.name())
|
||||
&& (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(PAYMENT_SENT.name()));
|
||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
log.warn("INVALID_PHASE for Alice's trade {} in STATE={} PHASE={}, cannot confirm payment received yet.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase());
|
||||
// fail("Bad trade state and phase.");
|
||||
sleep(1000 * 10);
|
||||
trade = aliceClient.getTrade(tradeId);
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||
fail(format("INVALID_PHASE for Alice's trade %s in STATE=%s PHASE=%s, could not confirm payment received.",
|
||||
trade.getShortId(),
|
||||
trade.getState(),
|
||||
trade.getPhase()));
|
||||
}
|
||||
|
||||
aliceClient.confirmPaymentReceived(trade.getTradeId());
|
||||
sleep(3000);
|
||||
|
||||
sleep(3_000);
|
||||
trade = aliceClient.getTrade(tradeId);
|
||||
assertEquals(OFFER_FEE_RESERVED.name(), trade.getOffer().getState());
|
||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
||||
.setPhase(PAYOUT_PUBLISHED)
|
||||
.setPayoutPublished(true)
|
||||
.setFiatReceived(true);
|
||||
.setPaymentReceivedMessageSent(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Alice's view after confirming fiat payment received", trade);
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testBobsBtcWithdrawalToExternalAddress(final TestInfo testInfo) {
|
||||
try {
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
|
||||
var trade = bobClient.getTrade(tradeId);
|
||||
logTrade(log, testInfo, "Bob's view before withdrawing funds to external wallet", trade);
|
||||
|
||||
String toAddress = bitcoinCli.getNewBtcAddress();
|
||||
bobClient.withdrawFunds(tradeId, toAddress, WITHDRAWAL_TX_MEMO);
|
||||
|
||||
genBtcBlocksThenWait(1, 1000);
|
||||
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
EXPECTED_PROTOCOL_STATUS.setState(WITHDRAW_COMPLETED)
|
||||
.setPhase(WITHDRAWN)
|
||||
.setWithdrawn(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Bob's view after withdrawing BTC funds to external wallet", trade);
|
||||
|
||||
logTrade(log, testInfo, "Alice's Maker/Buyer View (Done)", aliceClient.getTrade(tradeId), true);
|
||||
logTrade(log, testInfo, "Bob's Taker/Seller View (Done)", bobClient.getTrade(tradeId), true);
|
||||
|
||||
var alicesBalances = aliceClient.getBalances();
|
||||
log.info("{} Alice's Current Balance:\n{}",
|
||||
testName(testInfo),
|
||||
formatBalancesTbls(alicesBalances));
|
||||
var bobsBalances = bobClient.getBalances();
|
||||
log.info("{} Bob's Current Balance:\n{}",
|
||||
testName(testInfo),
|
||||
formatBalancesTbls(bobsBalances));
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method.trade;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||
import static bisq.apitest.config.ApiTestConfig.XMR;
|
||||
import static bisq.cli.table.builder.TableType.OFFER_TBL;
|
||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
||||
import static bisq.core.trade.Trade.Phase.WITHDRAWN;
|
||||
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
|
||||
import static bisq.core.trade.Trade.State.WITHDRAW_COMPLETED;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.offer.AbstractOfferTest;
|
||||
import bisq.cli.table.builder.TableBuilder;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class TakeSellXMROfferTest extends AbstractTradeTest {
|
||||
|
||||
// Alice is maker / xmr seller (btc buyer), Bob is taker / xmr buyer (btc seller).
|
||||
|
||||
// Maker and Taker fees are in BTC.
|
||||
private static final String TRADE_FEE_CURRENCY_CODE = BTC;
|
||||
|
||||
private static final String WITHDRAWAL_TX_MEMO = "Bob's trade withdrawal";
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
AbstractOfferTest.setUp();
|
||||
createXmrPaymentAccounts();
|
||||
EXPECTED_PROTOCOL_STATUS.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testTakeAlicesBuyBTCForXMROffer(final TestInfo testInfo) {
|
||||
try {
|
||||
// Alice is going to SELL XMR, but the Offer direction = BUY because it is a
|
||||
// BTC trade; Alice will BUY BTC for XMR. Alice will send Bob XMR.
|
||||
// Confused me, but just need to remember there are only BTC offers.
|
||||
var btcTradeDirection = BUY.name();
|
||||
double priceMarginPctInput = 1.50;
|
||||
var alicesOffer = aliceClient.createMarketBasedPricedOffer(btcTradeDirection,
|
||||
XMR,
|
||||
20_000_000L,
|
||||
10_500_000L,
|
||||
priceMarginPctInput,
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
alicesXmrAcct.getId(),
|
||||
NO_TRIGGER_PRICE);
|
||||
log.debug("Alice's SELL XMR (BUY BTC) Offer:\n{}", new TableBuilder(OFFER_TBL, alicesOffer).build());
|
||||
genBtcBlocksThenWait(1, 4000);
|
||||
var offerId = alicesOffer.getId();
|
||||
|
||||
var alicesXmrOffers = aliceClient.getMyOffers(btcTradeDirection, XMR);
|
||||
assertEquals(1, alicesXmrOffers.size());
|
||||
var trade = takeAlicesOffer(offerId, bobsXmrAcct.getId());
|
||||
alicesXmrOffers = aliceClient.getMyOffersSortedByDate(XMR);
|
||||
assertEquals(0, alicesXmrOffers.size());
|
||||
genBtcBlocksThenWait(1, 2_500);
|
||||
|
||||
waitForDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
|
||||
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
verifyTakerDepositConfirmed(trade);
|
||||
logTrade(log, testInfo, "Alice's Maker/Seller View", aliceClient.getTrade(tradeId));
|
||||
logTrade(log, testInfo, "Bob's Taker/Buyer View", bobClient.getTrade(tradeId));
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) {
|
||||
try {
|
||||
var trade = aliceClient.getTrade(tradeId);
|
||||
waitForDepositConfirmation(log, testInfo, aliceClient, trade.getTradeId());
|
||||
log.debug("Alice sends XMR payment to Bob for trade {}", trade.getTradeId());
|
||||
aliceClient.confirmPaymentStarted(trade.getTradeId());
|
||||
sleep(3500);
|
||||
|
||||
waitForBuyerSeesPaymentInitiatedMessage(log, testInfo, aliceClient, tradeId);
|
||||
logTrade(log, testInfo, "Alice's Maker/Seller View (Payment Sent)", aliceClient.getTrade(tradeId));
|
||||
logTrade(log, testInfo, "Bob's Taker/Buyer View (Payment Sent)", bobClient.getTrade(tradeId));
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testBobsConfirmPaymentReceived(final TestInfo testInfo) {
|
||||
try {
|
||||
waitForSellerSeesPaymentInitiatedMessage(log, testInfo, bobClient, tradeId);
|
||||
|
||||
var trade = bobClient.getTrade(tradeId);
|
||||
sleep(2_000);
|
||||
// If we were trading BTC, Bob would verify payment has been sent to his
|
||||
// Bisq wallet, but we can do no such checks for XMR payments.
|
||||
// All XMR transfers are done outside Bisq.
|
||||
log.debug("Bob verifies XMR payment was received from Alice, for trade {}", trade.getTradeId());
|
||||
bobClient.confirmPaymentReceived(trade.getTradeId());
|
||||
sleep(3_000);
|
||||
|
||||
trade = bobClient.getTrade(tradeId);
|
||||
// Warning: trade.getOffer().getState() might be AVAILABLE, not OFFER_FEE_RESERVED.
|
||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
||||
.setPhase(PAYOUT_PUBLISHED)
|
||||
.setPayoutPublished(true)
|
||||
.setPaymentReceivedMessageSent(true);
|
||||
verifyExpectedProtocolStatus(trade);
|
||||
logTrade(log, testInfo, "Alice's Maker/Seller View (Payment Received)", aliceClient.getTrade(tradeId));
|
||||
logTrade(log, testInfo, "Bob's Taker/Buyer View (Payment Received)", bobClient.getTrade(tradeId));
|
||||
} catch (StatusRuntimeException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -56,10 +56,9 @@ public class BtcTxFeeRateTest extends MethodTest {
|
||||
@Order(2)
|
||||
public void testSetInvalidTxFeeRateShouldThrowException(final TestInfo testInfo) {
|
||||
var currentTxFeeRateInfo = TxFeeRateInfo.fromProto(aliceClient.getTxFeeRate());
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||
aliceClient.setTxFeeRate(10));
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.setTxFeeRate(1));
|
||||
String expectedExceptionMessage =
|
||||
format("UNKNOWN: tx fee rate preference must be >= %d sats/byte",
|
||||
format("INVALID_ARGUMENT: tx fee rate preference must be >= %d sats/byte",
|
||||
currentTxFeeRateInfo.getMinFeeServiceRate());
|
||||
assertEquals(expectedExceptionMessage, exception.getMessage());
|
||||
}
|
||||
|
@ -19,9 +19,8 @@ import static bisq.apitest.config.HavenoAppConfig.bobdaemon;
|
||||
import static bisq.apitest.config.HavenoAppConfig.seednode;
|
||||
import static bisq.apitest.method.wallet.WalletTestUtil.INITIAL_BTC_BALANCES;
|
||||
import static bisq.apitest.method.wallet.WalletTestUtil.verifyBtcBalances;
|
||||
import static bisq.cli.TableFormat.formatAddressBalanceTbl;
|
||||
import static bisq.cli.TableFormat.formatBtcBalanceInfoTbl;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static bisq.cli.table.builder.TableType.ADDRESS_BALANCE_TBL;
|
||||
import static bisq.cli.table.builder.TableType.BTC_BALANCE_TBL;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
@ -30,6 +29,7 @@ import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
||||
|
||||
|
||||
import bisq.apitest.method.MethodTest;
|
||||
import bisq.cli.table.builder.TableBuilder;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@ -54,10 +54,14 @@ public class BtcWalletTest extends MethodTest {
|
||||
// Bob & Alice's regtest Bisq wallets were initialized with 10 BTC.
|
||||
|
||||
BtcBalanceInfo alicesBalances = aliceClient.getBtcBalances();
|
||||
log.debug("{} Alice's BTC Balances:\n{}", testName(testInfo), formatBtcBalanceInfoTbl(alicesBalances));
|
||||
log.debug("{} Alice's BTC Balances:\n{}",
|
||||
testName(testInfo),
|
||||
new TableBuilder(BTC_BALANCE_TBL, alicesBalances).build());
|
||||
|
||||
BtcBalanceInfo bobsBalances = bobClient.getBtcBalances();
|
||||
log.debug("{} Bob's BTC Balances:\n{}", testName(testInfo), formatBtcBalanceInfoTbl(bobsBalances));
|
||||
log.debug("{} Bob's BTC Balances:\n{}",
|
||||
testName(testInfo),
|
||||
new TableBuilder(BTC_BALANCE_TBL, bobsBalances).build());
|
||||
|
||||
assertEquals(INITIAL_BTC_BALANCES.getAvailableBalance(), alicesBalances.getAvailableBalance());
|
||||
assertEquals(INITIAL_BTC_BALANCES.getAvailableBalance(), bobsBalances.getAvailableBalance());
|
||||
@ -76,7 +80,8 @@ public class BtcWalletTest extends MethodTest {
|
||||
|
||||
log.debug("{} -> Alice's Funded Address Balance -> \n{}",
|
||||
testName(testInfo),
|
||||
formatAddressBalanceTbl(singletonList(aliceClient.getAddressBalance(newAddress))));
|
||||
new TableBuilder(ADDRESS_BALANCE_TBL,
|
||||
aliceClient.getAddressBalance(newAddress)));
|
||||
|
||||
// New balance is 12.5 BTC
|
||||
btcBalanceInfo = aliceClient.getBtcBalances();
|
||||
@ -88,7 +93,7 @@ public class BtcWalletTest extends MethodTest {
|
||||
verifyBtcBalances(alicesExpectedBalances, btcBalanceInfo);
|
||||
log.debug("{} -> Alice's BTC Balances After Sending 2.5 BTC -> \n{}",
|
||||
testName(testInfo),
|
||||
formatBtcBalanceInfoTbl(btcBalanceInfo));
|
||||
new TableBuilder(BTC_BALANCE_TBL, btcBalanceInfo).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -115,7 +120,7 @@ public class BtcWalletTest extends MethodTest {
|
||||
BtcBalanceInfo alicesBalances = aliceClient.getBtcBalances();
|
||||
log.debug("{} Alice's BTC Balances:\n{}",
|
||||
testName(testInfo),
|
||||
formatBtcBalanceInfoTbl(alicesBalances));
|
||||
new TableBuilder(BTC_BALANCE_TBL, alicesBalances).build());
|
||||
bisq.core.api.model.BtcBalanceInfo alicesExpectedBalances =
|
||||
bisq.core.api.model.BtcBalanceInfo.valueOf(700000000,
|
||||
0,
|
||||
@ -126,7 +131,7 @@ public class BtcWalletTest extends MethodTest {
|
||||
BtcBalanceInfo bobsBalances = bobClient.getBtcBalances();
|
||||
log.debug("{} Bob's BTC Balances:\n{}",
|
||||
testName(testInfo),
|
||||
formatBtcBalanceInfoTbl(bobsBalances));
|
||||
new TableBuilder(BTC_BALANCE_TBL, bobsBalances).build());
|
||||
// The sendbtc tx weight and size randomly varies between two distinct values
|
||||
// (876 wu, 219 bytes, OR 880 wu, 220 bytes) from test run to test run, hence
|
||||
// the assertion of an available balance range [1549978000, 1549978100].
|
||||
|
@ -48,7 +48,7 @@ public class WalletProtectionTest extends MethodTest {
|
||||
@Order(2)
|
||||
public void testGetBalanceOnEncryptedWalletShouldThrowException() {
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
|
||||
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
|
||||
assertEquals("FAILED_PRECONDITION: wallet is locked", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -58,7 +58,7 @@ public class WalletProtectionTest extends MethodTest {
|
||||
aliceClient.getBtcBalances(); // should not throw 'wallet locked' exception
|
||||
sleep(4500); // let unlock timeout expire
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
|
||||
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
|
||||
assertEquals("FAILED_PRECONDITION: wallet is locked", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -67,7 +67,7 @@ public class WalletProtectionTest extends MethodTest {
|
||||
aliceClient.unlockWallet("first-password", 3);
|
||||
sleep(4000); // let unlock timeout expire
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
|
||||
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
|
||||
assertEquals("FAILED_PRECONDITION: wallet is locked", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -76,14 +76,14 @@ public class WalletProtectionTest extends MethodTest {
|
||||
aliceClient.unlockWallet("first-password", 60);
|
||||
aliceClient.lockWallet();
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
|
||||
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
|
||||
assertEquals("FAILED_PRECONDITION: wallet is locked", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(6)
|
||||
public void testLockWalletWhenWalletAlreadyLockedShouldThrowException() {
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.lockWallet());
|
||||
assertEquals("UNKNOWN: wallet is already locked", exception.getMessage());
|
||||
assertEquals("ALREADY_EXISTS: wallet is already locked", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -110,7 +110,7 @@ public class WalletProtectionTest extends MethodTest {
|
||||
public void testSetNewWalletPasswordWithIncorrectNewPasswordShouldThrowException() {
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||
aliceClient.setWalletPassword("bad old password", "irrelevant"));
|
||||
assertEquals("UNKNOWN: incorrect old password", exception.getMessage());
|
||||
assertEquals("INVALID_ARGUMENT: incorrect old password", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -9,7 +9,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@Slf4j
|
||||
public class WalletTestUtil {
|
||||
|
||||
// All api tests depend on the regtest environment, and Bob & Alice's wallets
|
||||
// All api tests depend on the DAO / regtest environment, and Bob & Alice's wallets
|
||||
// are initialized with 10 BTC during the scaffolding setup.
|
||||
public static final bisq.core.api.model.BtcBalanceInfo INITIAL_BTC_BALANCES =
|
||||
bisq.core.api.model.BtcBalanceInfo.valueOf(1000000000,
|
||||
@ -17,7 +17,6 @@ public class WalletTestUtil {
|
||||
1000000000,
|
||||
0);
|
||||
|
||||
|
||||
public static void verifyBtcBalances(bisq.core.api.model.BtcBalanceInfo expected,
|
||||
BtcBalanceInfo actual) {
|
||||
assertEquals(expected.getAvailableBalance(), actual.getAvailableBalance());
|
||||
|
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.scenario;
|
||||
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.junit.jupiter.api.condition.EnabledIf;
|
||||
|
||||
import static java.lang.System.getenv;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
import static protobuf.OfferDirection.SELL;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.offer.AbstractOfferTest;
|
||||
|
||||
/**
|
||||
* Used to verify trigger based, automatic offer deactivation works.
|
||||
* Disabled by default.
|
||||
* Set ENV or IDE-ENV LONG_RUNNING_OFFER_DEACTIVATION_TEST_ENABLED=true to run.
|
||||
*/
|
||||
@EnabledIf("envLongRunningTestEnabled")
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class LongRunningOfferDeactivationTest extends AbstractOfferTest {
|
||||
|
||||
private static final int MAX_ITERATIONS = 500;
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testSellOfferAutoDisable(final TestInfo testInfo) {
|
||||
PaymentAccount paymentAcct = createDummyF2FAccount(aliceClient, "US");
|
||||
double mktPriceAsDouble = aliceClient.getBtcPrice("USD");
|
||||
String triggerPrice = calcPriceAsString(mktPriceAsDouble, -50.0000, 4);
|
||||
log.info("Current USD mkt price = {} Trigger Price = {}", mktPriceAsDouble, triggerPrice);
|
||||
OfferInfo offer = aliceClient.createMarketBasedPricedOffer(SELL.name(),
|
||||
"USD",
|
||||
1_000_000,
|
||||
1_000_000,
|
||||
0.00,
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
paymentAcct.getId(),
|
||||
triggerPrice);
|
||||
log.info("SELL offer {} created with margin based price {}.",
|
||||
offer.getId(),
|
||||
offer.getPrice());
|
||||
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
|
||||
|
||||
offer = aliceClient.getOffer(offer.getId()); // Offer has trigger price now.
|
||||
log.info("SELL offer should be automatically disabled when mkt price falls below {}.", offer.getTriggerPrice());
|
||||
|
||||
int numIterations = 0;
|
||||
while (++numIterations < MAX_ITERATIONS) {
|
||||
offer = aliceClient.getOffer(offer.getId());
|
||||
|
||||
var mktPrice = aliceClient.getBtcPrice("USD");
|
||||
if (offer.getIsActivated()) {
|
||||
log.info("Offer still enabled at mkt price {} > {} trigger price",
|
||||
mktPrice,
|
||||
offer.getTriggerPrice());
|
||||
sleep(1000 * 60); // 60s
|
||||
} else {
|
||||
log.info("Successful test completion after offer disabled at mkt price {} < {} trigger price.",
|
||||
mktPrice,
|
||||
offer.getTriggerPrice());
|
||||
break;
|
||||
}
|
||||
if (numIterations == MAX_ITERATIONS)
|
||||
fail("Offer never disabled");
|
||||
|
||||
genBtcBlocksThenWait(1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testBuyOfferAutoDisable(final TestInfo testInfo) {
|
||||
PaymentAccount paymentAcct = createDummyF2FAccount(aliceClient, "US");
|
||||
double mktPriceAsDouble = aliceClient.getBtcPrice("USD");
|
||||
String triggerPrice = calcPriceAsString(mktPriceAsDouble, 50.0000, 4);
|
||||
log.info("Current USD mkt price = {} Trigger Price = {}", mktPriceAsDouble, triggerPrice);
|
||||
OfferInfo offer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
|
||||
"USD",
|
||||
1_000_000,
|
||||
1_000_000,
|
||||
0.00,
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
paymentAcct.getId(),
|
||||
triggerPrice);
|
||||
log.info("BUY offer {} created with margin based price {}.",
|
||||
offer.getId(),
|
||||
offer.getPrice());
|
||||
genBtcBlocksThenWait(1, 2500); // Wait for offer book entry.
|
||||
|
||||
offer = aliceClient.getOffer(offer.getId()); // Offer has trigger price now.
|
||||
log.info("BUY offer should be automatically disabled when mkt price rises above {}.",
|
||||
offer.getTriggerPrice());
|
||||
|
||||
int numIterations = 0;
|
||||
while (++numIterations < MAX_ITERATIONS) {
|
||||
offer = aliceClient.getOffer(offer.getId());
|
||||
|
||||
var mktPrice = aliceClient.getBtcPrice("USD");
|
||||
if (offer.getIsActivated()) {
|
||||
log.info("Offer still enabled at mkt price {} < {} trigger price",
|
||||
mktPrice,
|
||||
offer.getTriggerPrice());
|
||||
sleep(1000 * 60); // 60s
|
||||
} else {
|
||||
log.info("Successful test completion after offer disabled at mkt price {} > {} trigger price.",
|
||||
mktPrice,
|
||||
offer.getTriggerPrice());
|
||||
break;
|
||||
}
|
||||
if (numIterations == MAX_ITERATIONS)
|
||||
fail("Offer never disabled");
|
||||
|
||||
genBtcBlocksThenWait(1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
protected static boolean envLongRunningTestEnabled() {
|
||||
String envName = "LONG_RUNNING_OFFER_DEACTIVATION_TEST_ENABLED";
|
||||
String envX = getenv(envName);
|
||||
if (envX != null) {
|
||||
log.info("Enabled, found {}.", envName);
|
||||
return true;
|
||||
} else {
|
||||
log.info("Skipped, no environment variable {} defined.", envName);
|
||||
log.info("To enable on Mac OS or Linux:"
|
||||
+ "\tIf running in terminal, export LONG_RUNNING_OFFER_DEACTIVATION_TEST_ENABLED=true in bash shell."
|
||||
+ "\tIf running in Intellij, set LONG_RUNNING_OFFER_DEACTIVATION_TEST_ENABLED=true in launcher's Environment variables field.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -71,7 +71,6 @@ public class LongRunningTradesTest extends AbstractTradeTest {
|
||||
test.testTakeAlicesBuyOffer(testInfo);
|
||||
test.testAlicesConfirmPaymentStarted(testInfo);
|
||||
test.testBobsConfirmPaymentReceived(testInfo);
|
||||
test.testAlicesKeepFunds(testInfo);
|
||||
}
|
||||
|
||||
public void testTakeSellBTCOffer(final TestInfo testInfo) {
|
||||
@ -80,7 +79,6 @@ public class LongRunningTradesTest extends AbstractTradeTest {
|
||||
test.testTakeAlicesSellOffer(testInfo);
|
||||
test.testBobsConfirmPaymentStarted(testInfo);
|
||||
test.testAlicesConfirmPaymentReceived(testInfo);
|
||||
test.testBobsBtcWithdrawalToExternalAddress(testInfo);
|
||||
}
|
||||
|
||||
protected static boolean envLongRunningTestEnabled() {
|
||||
|
@ -20,6 +20,7 @@ package bisq.apitest.scenario;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -31,15 +32,21 @@ import bisq.apitest.method.offer.AbstractOfferTest;
|
||||
import bisq.apitest.method.offer.CancelOfferTest;
|
||||
import bisq.apitest.method.offer.CreateOfferUsingFixedPriceTest;
|
||||
import bisq.apitest.method.offer.CreateOfferUsingMarketPriceMarginTest;
|
||||
import bisq.apitest.method.offer.CreateXMROffersTest;
|
||||
import bisq.apitest.method.offer.ValidateCreateOfferTest;
|
||||
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class OfferTest extends AbstractOfferTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
setUp(false); // Use setUp(true) for running API daemons in remote debug mode.
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testAmtTooLargeShouldThrowException() {
|
||||
public void testCreateOfferValidation() {
|
||||
ValidateCreateOfferTest test = new ValidateCreateOfferTest();
|
||||
test.testAmtTooLargeShouldThrowException();
|
||||
test.testNoMatchingEURPaymentAccountShouldThrowException();
|
||||
@ -57,18 +64,32 @@ public class OfferTest extends AbstractOfferTest {
|
||||
@Order(3)
|
||||
public void testCreateOfferUsingFixedPrice() {
|
||||
CreateOfferUsingFixedPriceTest test = new CreateOfferUsingFixedPriceTest();
|
||||
test.testCreateAUDXMRBuyOfferUsingFixedPrice16000();
|
||||
test.testCreateUSDXMRBuyOfferUsingFixedPrice100001234();
|
||||
test.testCreateEURXMRSellOfferUsingFixedPrice95001234();
|
||||
test.testCreateAUDBTCBuyOfferUsingFixedPrice16000();
|
||||
test.testCreateUSDBTCBuyOfferUsingFixedPrice100001234();
|
||||
test.testCreateEURBTCSellOfferUsingFixedPrice95001234();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testCreateOfferUsingMarketPriceMargin() {
|
||||
public void testCreateOfferUsingMarketPriceMarginPct() {
|
||||
CreateOfferUsingMarketPriceMarginTest test = new CreateOfferUsingMarketPriceMarginTest();
|
||||
test.testCreateUSDXMRBuyOffer5PctPriceMargin();
|
||||
test.testCreateNZDXMRBuyOfferMinus2PctPriceMargin();
|
||||
test.testCreateGBPXMRSellOfferMinus1Point5PctPriceMargin();
|
||||
test.testCreateBRLXMRSellOffer6Point55PctPriceMargin();
|
||||
test.testCreateUSDBTCBuyOffer5PctPriceMargin();
|
||||
test.testCreateNZDBTCBuyOfferMinus2PctPriceMargin();
|
||||
test.testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin();
|
||||
test.testCreateBRLBTCSellOffer6Point55PctPriceMargin();
|
||||
test.testCreateUSDBTCBuyOfferWithTriggerPrice();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(6)
|
||||
public void testCreateXMROffers() {
|
||||
CreateXMROffersTest test = new CreateXMROffersTest();
|
||||
CreateXMROffersTest.createXmrPaymentAccounts();
|
||||
test.testCreateFixedPriceBuy1BTCFor200KXMROffer();
|
||||
test.testCreateFixedPriceSell1BTCFor200KXMROffer();
|
||||
test.testCreatePriceMarginBasedBuy1BTCOfferWithTriggerPrice();
|
||||
test.testCreatePriceMarginBasedSell1BTCOffer();
|
||||
test.testGetAllMyXMROffers();
|
||||
test.testGetAvailableXMROffers();
|
||||
}
|
||||
}
|
||||
|
@ -49,9 +49,9 @@ public class PaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
test.testCreateAdvancedCashAccount(testInfo);
|
||||
test.testCreateAliPayAccount(testInfo);
|
||||
test.testCreateAustraliaPayidAccount(testInfo);
|
||||
test.testCreateCapitualAccount(testInfo);
|
||||
test.testCreateCashDepositAccount(testInfo);
|
||||
test.testCreateBrazilNationalBankAccount(testInfo);
|
||||
test.testCreateChaseQuickPayAccount(testInfo);
|
||||
test.testCreateClearXChangeAccount(testInfo);
|
||||
test.testCreateF2FAccount(testInfo);
|
||||
test.testCreateFasterPaymentsAccount(testInfo);
|
||||
@ -61,6 +61,8 @@ public class PaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
test.testCreateMoneyBeamAccount(testInfo);
|
||||
test.testCreateMoneyGramAccount(testInfo);
|
||||
test.testCreatePerfectMoneyAccount(testInfo);
|
||||
test.testCreatePaxumAccount(testInfo);
|
||||
test.testCreatePayseraAccount(testInfo);
|
||||
test.testCreatePopmoneyAccount(testInfo);
|
||||
test.testCreatePromptPayAccount(testInfo);
|
||||
test.testCreateRevolutAccount(testInfo);
|
||||
@ -68,12 +70,12 @@ public class PaymentAccountTest extends AbstractPaymentAccountTest {
|
||||
test.testCreateSepaInstantAccount(testInfo);
|
||||
test.testCreateSepaAccount(testInfo);
|
||||
test.testCreateSpecificBanksAccount(testInfo);
|
||||
test.testCreateSwiftAccount(testInfo);
|
||||
test.testCreateSwishAccount(testInfo);
|
||||
|
||||
// TransferwiseAccount is only PaymentAccount with a
|
||||
// tradeCurrencies field in the json form.
|
||||
test.testCreateTransferwiseAccountWith1TradeCurrency(testInfo);
|
||||
test.testCreateTransferwiseAccountWith10TradeCurrencies(testInfo);
|
||||
test.testCreateTransferwiseAccountWithSupportedTradeCurrencies(testInfo);
|
||||
test.testCreateTransferwiseAccountWithInvalidBrlTradeCurrencyShouldThrowException(testInfo);
|
||||
test.testCreateTransferwiseAccountWithoutTradeCurrenciesShouldThrowException(testInfo);
|
||||
|
||||
|
@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
||||
import static bisq.apitest.config.ApiTestRateMeterInterceptorConfig.getTestRateMeterInterceptorConfig;
|
||||
import static bisq.apitest.config.HavenoAppConfig.alicedaemon;
|
||||
import static bisq.apitest.config.HavenoAppConfig.arbdaemon;
|
||||
import static bisq.apitest.config.HavenoAppConfig.seednode;
|
||||
@ -54,7 +55,7 @@ public class StartupTest extends MethodTest {
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
try {
|
||||
callRateMeteringConfigFile = defaultRateMeterInterceptorConfig();
|
||||
callRateMeteringConfigFile = getTestRateMeterInterceptorConfig();
|
||||
startSupportingApps(callRateMeteringConfigFile,
|
||||
false,
|
||||
false,
|
||||
|
@ -30,7 +30,10 @@ import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import bisq.apitest.method.trade.AbstractTradeTest;
|
||||
import bisq.apitest.method.trade.TakeBuyBTCOfferTest;
|
||||
import bisq.apitest.method.trade.TakeBuyBTCOfferWithNationalBankAcctTest;
|
||||
import bisq.apitest.method.trade.TakeBuyXMROfferTest;
|
||||
import bisq.apitest.method.trade.TakeSellBTCOfferTest;
|
||||
import bisq.apitest.method.trade.TakeSellXMROfferTest;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@ -49,7 +52,6 @@ public class TradeTest extends AbstractTradeTest {
|
||||
test.testTakeAlicesBuyOffer(testInfo);
|
||||
test.testAlicesConfirmPaymentStarted(testInfo);
|
||||
test.testBobsConfirmPaymentReceived(testInfo);
|
||||
test.testAlicesKeepFunds(testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -59,6 +61,35 @@ public class TradeTest extends AbstractTradeTest {
|
||||
test.testTakeAlicesSellOffer(testInfo);
|
||||
test.testBobsConfirmPaymentStarted(testInfo);
|
||||
test.testAlicesConfirmPaymentReceived(testInfo);
|
||||
test.testBobsBtcWithdrawalToExternalAddress(testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testTakeBuyBTCOfferWithNationalBankAcct(final TestInfo testInfo) {
|
||||
TakeBuyBTCOfferWithNationalBankAcctTest test = new TakeBuyBTCOfferWithNationalBankAcctTest();
|
||||
test.testTakeAlicesBuyOffer(testInfo);
|
||||
test.testBankAcctDetailsIncludedInContracts(testInfo);
|
||||
test.testAlicesConfirmPaymentStarted(testInfo);
|
||||
test.testBobsConfirmPaymentReceived(testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(6)
|
||||
public void testTakeBuyXMROffer(final TestInfo testInfo) {
|
||||
TakeBuyXMROfferTest test = new TakeBuyXMROfferTest();
|
||||
TakeBuyXMROfferTest.createXmrPaymentAccounts();
|
||||
test.testTakeAlicesSellBTCForXMROffer(testInfo);
|
||||
test.testBobsConfirmPaymentStarted(testInfo);
|
||||
test.testAlicesConfirmPaymentReceived(testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(7)
|
||||
public void testTakeSellXMROffer(final TestInfo testInfo) {
|
||||
TakeSellXMROfferTest test = new TakeSellXMROfferTest();
|
||||
TakeBuyXMROfferTest.createXmrPaymentAccounts();
|
||||
test.testTakeAlicesBuyBTCForXMROffer(testInfo);
|
||||
test.testAlicesConfirmPaymentStarted(testInfo);
|
||||
test.testBobsConfirmPaymentReceived(testInfo);
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.core.locale.CountryUtil.findCountryByCode;
|
||||
import static bisq.core.payment.payload.PaymentMethod.CLEAR_X_CHANGE_ID;
|
||||
import static bisq.core.payment.payload.PaymentMethod.getPaymentMethodById;
|
||||
import static bisq.core.payment.payload.PaymentMethod.getPaymentMethod;
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.System.getProperty;
|
||||
import static java.nio.file.Files.readAllBytes;
|
||||
@ -74,7 +74,7 @@ public abstract class AbstractBotTest extends MethodTest {
|
||||
} else {
|
||||
throw new UnsupportedOperationException(
|
||||
format("This test harness bot does not work with %s payment accounts yet.",
|
||||
getPaymentMethodById(paymentMethodId).getDisplayString()));
|
||||
getPaymentMethod(paymentMethodId).getDisplayString()));
|
||||
}
|
||||
} else {
|
||||
String countryCode = botScript.getCountryCode();
|
||||
|
@ -8,7 +8,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.core.locale.CountryUtil.findCountryByCode;
|
||||
import static bisq.core.payment.payload.PaymentMethod.CLEAR_X_CHANGE_ID;
|
||||
import static bisq.core.payment.payload.PaymentMethod.getPaymentMethodById;
|
||||
import static bisq.core.payment.payload.PaymentMethod.getPaymentMethod;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
|
||||
@ -62,7 +62,7 @@ class Bot {
|
||||
} else {
|
||||
throw new UnsupportedOperationException(
|
||||
format("This bot test does not work with %s payment accounts yet.",
|
||||
getPaymentMethodById(paymentMethodId).getDisplayString()));
|
||||
getPaymentMethod(paymentMethodId).getDisplayString()));
|
||||
}
|
||||
} else {
|
||||
Country country = findCountry(botScript.getCountryCode());
|
||||
|
@ -39,11 +39,6 @@ import bisq.cli.GrpcClient;
|
||||
|
||||
/**
|
||||
* Convenience GrpcClient wrapper for bots using gRPC services.
|
||||
*
|
||||
* TODO Consider if the duplication smell is bad enough to force a BotClient user
|
||||
* to use the GrpcClient instead (and delete this class). But right now, I think it is
|
||||
* OK because moving some of the non-gRPC related methods to GrpcClient is even smellier.
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings({"JavaDoc", "unused"})
|
||||
@Slf4j
|
||||
@ -124,6 +119,8 @@ public class BotClient {
|
||||
* @param minAmountInSatoshis
|
||||
* @param priceMarginAsPercent
|
||||
* @param securityDepositAsPercent
|
||||
* @param feeCurrency
|
||||
* @param triggerPrice
|
||||
* @return OfferInfo
|
||||
*/
|
||||
public OfferInfo createOfferAtMarketBasedPrice(PaymentAccount paymentAccount,
|
||||
@ -132,14 +129,16 @@ public class BotClient {
|
||||
long amountInSatoshis,
|
||||
long minAmountInSatoshis,
|
||||
double priceMarginAsPercent,
|
||||
double securityDepositAsPercent) {
|
||||
double securityDepositAsPercent,
|
||||
String triggerPrice) {
|
||||
return grpcClient.createMarketBasedPricedOffer(direction,
|
||||
currencyCode,
|
||||
amountInSatoshis,
|
||||
minAmountInSatoshis,
|
||||
priceMarginAsPercent,
|
||||
securityDepositAsPercent,
|
||||
paymentAccount.getId());
|
||||
paymentAccount.getId(),
|
||||
triggerPrice);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -151,6 +150,7 @@ public class BotClient {
|
||||
* @param minAmountInSatoshis
|
||||
* @param fixedOfferPriceAsString
|
||||
* @param securityDepositAsPercent
|
||||
* @param feeCurrency
|
||||
* @return OfferInfo
|
||||
*/
|
||||
public OfferInfo createOfferAtFixedPrice(PaymentAccount paymentAccount,
|
||||
@ -225,11 +225,11 @@ public class BotClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the trade's taker deposit fee transaction is unlocked.
|
||||
* Returns true if the trade's taker deposit fee transaction has been confirmed.
|
||||
* @param tradeId a valid trade id
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean isTakerDepositFeeTxUnlocked(String tradeId) {
|
||||
public boolean isTakerDepositFeeTxConfirmed(String tradeId) {
|
||||
return grpcClient.getTrade(tradeId).getIsDepositUnlocked();
|
||||
}
|
||||
|
||||
@ -278,15 +278,6 @@ public class BotClient {
|
||||
grpcClient.confirmPaymentReceived(tradeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a 'keep funds in wallet message' for a trade with the given tradeId,
|
||||
* or throws an exception.
|
||||
* @param tradeId
|
||||
*/
|
||||
public void sendKeepFundsMessage(String tradeId) {
|
||||
grpcClient.keepFunds(tradeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and save a new PaymentAccount with details in the given json.
|
||||
* @param json
|
||||
|
@ -33,10 +33,10 @@ import java.util.function.Supplier;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.cli.CurrencyFormat.formatMarketPrice;
|
||||
import static bisq.apitest.method.offer.AbstractOfferTest.defaultBuyerSecurityDepositPct;
|
||||
import static bisq.cli.CurrencyFormat.formatInternalFiatPrice;
|
||||
import static bisq.cli.CurrencyFormat.formatSatoshis;
|
||||
import static bisq.common.util.MathUtils.scaleDownByPowerOf10;
|
||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||
import static bisq.core.payment.payload.PaymentMethod.F2F_ID;
|
||||
import static java.lang.String.format;
|
||||
import static java.math.RoundingMode.HALF_UP;
|
||||
@ -124,7 +124,8 @@ public class RandomOffer {
|
||||
amount,
|
||||
minAmount,
|
||||
priceMargin,
|
||||
getDefaultBuyerSecurityDepositAsPercent());
|
||||
defaultBuyerSecurityDepositPct.get(),
|
||||
"0" /*no trigger price*/);
|
||||
} else {
|
||||
this.offer = botClient.createOfferAtFixedPrice(paymentAccount,
|
||||
direction,
|
||||
@ -132,7 +133,7 @@ public class RandomOffer {
|
||||
amount,
|
||||
minAmount,
|
||||
fixedOfferPrice,
|
||||
getDefaultBuyerSecurityDepositAsPercent());
|
||||
defaultBuyerSecurityDepositPct.get());
|
||||
}
|
||||
this.id = offer.getId();
|
||||
return this;
|
||||
@ -162,11 +163,11 @@ public class RandomOffer {
|
||||
log.info(description);
|
||||
if (useMarketBasedPrice) {
|
||||
log.info("Offer Price Margin = {}%", priceMargin);
|
||||
log.info("Expected Offer Price = {} {}", formatMarketPrice(Double.parseDouble(fixedOfferPrice)), currencyCode);
|
||||
log.info("Expected Offer Price = {} {}", formatInternalFiatPrice(Double.parseDouble(fixedOfferPrice)), currencyCode);
|
||||
} else {
|
||||
|
||||
log.info("Fixed Offer Price = {} {}", fixedOfferPrice, currencyCode);
|
||||
}
|
||||
log.info("Current Market Price = {} {}", formatMarketPrice(currentMarketPrice), currencyCode);
|
||||
log.info("Current Market Price = {} {}", formatInternalFiatPrice(currentMarketPrice), currencyCode);
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.DONE;
|
||||
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.isShutdownCalled;
|
||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
||||
import static bisq.cli.table.builder.TableType.BTC_BALANCE_TBL;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
|
||||
@ -34,6 +34,7 @@ import bisq.apitest.scenario.bot.protocol.TakerBotProtocol;
|
||||
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
||||
import bisq.apitest.scenario.bot.script.BotScript;
|
||||
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
||||
import bisq.cli.table.builder.TableBuilder;
|
||||
|
||||
@Slf4j
|
||||
public
|
||||
@ -74,10 +75,14 @@ class RobotBob extends Bot {
|
||||
throw new IllegalStateException(botProtocol.getClass().getSimpleName() + " failed to complete.");
|
||||
}
|
||||
|
||||
StringBuilder balancesBuilder = new StringBuilder();
|
||||
balancesBuilder.append("BTC").append("\n");
|
||||
balancesBuilder.append(new TableBuilder(BTC_BALANCE_TBL, botClient.getBalance().getBtc()).build().toString()).append("\n");
|
||||
|
||||
log.info("Completed {} successful trade{}. Current Balance:\n{}",
|
||||
++numTrades,
|
||||
numTrades == 1 ? "" : "s",
|
||||
formatBalancesTbls(botClient.getBalance()));
|
||||
balancesBuilder);
|
||||
|
||||
if (numTrades < actions.length) {
|
||||
try {
|
||||
|
@ -39,6 +39,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.*;
|
||||
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.checkIfShutdownCalled;
|
||||
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.System.currentTimeMillis;
|
||||
import static java.util.Arrays.stream;
|
||||
@ -50,7 +51,7 @@ import bisq.apitest.method.BitcoinCliHelper;
|
||||
import bisq.apitest.scenario.bot.BotClient;
|
||||
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
||||
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
||||
import bisq.cli.TradeFormat;
|
||||
import bisq.cli.table.builder.TableBuilder;
|
||||
|
||||
@Slf4j
|
||||
public abstract class BotProtocol {
|
||||
@ -110,7 +111,7 @@ public abstract class BotProtocol {
|
||||
log.info("Starting protocol step {}. Bot will shutdown if step not completed within {} minutes.",
|
||||
currentProtocolStep.name(), MILLISECONDS.toMinutes(protocolStepTimeLimitInMs));
|
||||
|
||||
if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_UNLOCKED)) {
|
||||
if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED)) {
|
||||
log.info("Generate a btc block to trigger taker's deposit fee tx confirmation.");
|
||||
createGenerateBtcBlockScript();
|
||||
}
|
||||
@ -133,7 +134,8 @@ public abstract class BotProtocol {
|
||||
try {
|
||||
var t = this.getBotClient().getTrade(trade.getTradeId());
|
||||
if (t.getIsPaymentSent()) {
|
||||
log.info("Buyer has started payment for trade:\n{}", TradeFormat.format(t));
|
||||
log.info("Buyer has started payment for trade:\n{}",
|
||||
new TableBuilder(TRADE_DETAIL_TBL, t).build().toString());
|
||||
return t;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
@ -167,7 +169,8 @@ public abstract class BotProtocol {
|
||||
try {
|
||||
var t = this.getBotClient().getTrade(trade.getTradeId());
|
||||
if (t.getIsPaymentReceived()) {
|
||||
log.info("Seller has received payment for trade:\n{}", TradeFormat.format(t));
|
||||
log.info("Seller has received payment for trade:\n{}",
|
||||
new TableBuilder(TRADE_DETAIL_TBL, t).build().toString());
|
||||
return t;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
@ -202,7 +205,7 @@ public abstract class BotProtocol {
|
||||
if (t.getIsPayoutPublished()) {
|
||||
log.info("Payout tx {} has been published for trade:\n{}",
|
||||
t.getPayoutTxId(),
|
||||
TradeFormat.format(t));
|
||||
new TableBuilder(TRADE_DETAIL_TBL, t).build().toString());
|
||||
return t;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
@ -219,21 +222,6 @@ public abstract class BotProtocol {
|
||||
}
|
||||
};
|
||||
|
||||
protected final Function<TradeInfo, TradeInfo> keepFundsFromTrade = (trade) -> {
|
||||
initProtocolStep.accept(KEEP_FUNDS);
|
||||
var isBuy = trade.getOffer().getDirection().equalsIgnoreCase(BUY);
|
||||
var isSell = trade.getOffer().getDirection().equalsIgnoreCase(SELL);
|
||||
var cliUserIsSeller = (this instanceof MakerBotProtocol && isBuy) || (this instanceof TakerBotProtocol && isSell);
|
||||
if (cliUserIsSeller) {
|
||||
createKeepFundsScript(trade);
|
||||
} else {
|
||||
createGetBalanceScript();
|
||||
}
|
||||
checkIfShutdownCalled("Interrupted before closing trade with 'keep funds' command.");
|
||||
this.getBotClient().sendKeepFundsMessage(trade.getTradeId());
|
||||
return trade;
|
||||
};
|
||||
|
||||
protected void createPaymentStartedScript(TradeInfo trade) {
|
||||
File script = bashScriptGenerator.createPaymentStartedScript(trade);
|
||||
printCliHintAndOrScript(script, "The manual CLI side can send a 'payment started' message");
|
||||
@ -281,12 +269,12 @@ public abstract class BotProtocol {
|
||||
}
|
||||
|
||||
private void waitForTakerFeeTxConfirmed(String tradeId) {
|
||||
waitForTakerDepositFee(tradeId, WAIT_FOR_TAKER_DEPOSIT_TX_UNLOCKED);
|
||||
waitForTakerDepositFee(tradeId, WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED);
|
||||
}
|
||||
|
||||
private void waitForTakerDepositFee(String tradeId, ProtocolStep depositTxProtocolStep) {
|
||||
initProtocolStep.accept(depositTxProtocolStep);
|
||||
validateCurrentProtocolStep(WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED, WAIT_FOR_TAKER_DEPOSIT_TX_UNLOCKED);
|
||||
validateCurrentProtocolStep(WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED, WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED);
|
||||
try {
|
||||
log.info(waitingForDepositFeeTxMsg(tradeId));
|
||||
while (isWithinProtocolStepTimeLimit()) {
|
||||
@ -316,8 +304,8 @@ public abstract class BotProtocol {
|
||||
if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED) && trade.getIsDepositPublished()) {
|
||||
log.info("Taker deposit fee tx {} has been published.", trade.getTakerDepositTxId());
|
||||
return true;
|
||||
} else if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_UNLOCKED) && trade.getIsDepositUnlocked()) {
|
||||
log.info("Taker deposit fee tx {} is unlocked.", trade.getTakerDepositTxId());
|
||||
} else if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED) && trade.getIsDepositUnlocked()) {
|
||||
log.info("Taker deposit fee tx {} has been confirmed.", trade.getTakerDepositTxId());
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
@ -16,8 +16,8 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.DONE;
|
||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.WAIT_FOR_OFFER_TAKER;
|
||||
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.checkIfShutdownCalled;
|
||||
import static bisq.cli.TableFormat.formatOfferTable;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static bisq.cli.table.builder.TableType.OFFER_TBL;
|
||||
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
|
||||
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ import bisq.apitest.scenario.bot.BotClient;
|
||||
import bisq.apitest.scenario.bot.RandomOffer;
|
||||
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
||||
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
||||
import bisq.cli.TradeFormat;
|
||||
import bisq.cli.table.builder.TableBuilder;
|
||||
|
||||
@Slf4j
|
||||
public class MakerBotProtocol extends BotProtocol {
|
||||
@ -56,16 +56,13 @@ public class MakerBotProtocol extends BotProtocol {
|
||||
: waitForPaymentStartedMessage.andThen(sendPaymentReceivedMessage);
|
||||
completeFiatTransaction.apply(trade);
|
||||
|
||||
Function<TradeInfo, TradeInfo> closeTrade = waitForPayoutTx.andThen(keepFundsFromTrade);
|
||||
closeTrade.apply(trade);
|
||||
|
||||
currentProtocolStep = DONE;
|
||||
}
|
||||
|
||||
private final Supplier<OfferInfo> randomOffer = () -> {
|
||||
checkIfShutdownCalled("Interrupted before creating random offer.");
|
||||
OfferInfo offer = new RandomOffer(botClient, paymentAccount).create().getOffer();
|
||||
log.info("Created random {} offer\n{}", currencyCode, formatOfferTable(singletonList(offer), currencyCode));
|
||||
log.info("Created random {} offer\n{}", currencyCode, new TableBuilder(OFFER_TBL, offer).build());
|
||||
return offer;
|
||||
};
|
||||
|
||||
@ -98,7 +95,9 @@ public class MakerBotProtocol extends BotProtocol {
|
||||
private Optional<TradeInfo> getNewTrade(String offerId) {
|
||||
try {
|
||||
var trade = botClient.getTrade(offerId);
|
||||
log.info("Offer {} was taken, new trade:\n{}", offerId, TradeFormat.format(trade));
|
||||
log.info("Offer {} was taken, new trade:\n{}",
|
||||
offerId,
|
||||
new TableBuilder(TRADE_DETAIL_TBL, trade).build().toString());
|
||||
return Optional.of(trade);
|
||||
} catch (Exception ex) {
|
||||
// Get trade will throw a non-fatal gRPC exception if not found.
|
||||
|
@ -6,12 +6,12 @@ public enum ProtocolStep {
|
||||
TAKE_OFFER,
|
||||
WAIT_FOR_OFFER_TAKER,
|
||||
WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED,
|
||||
WAIT_FOR_TAKER_DEPOSIT_TX_UNLOCKED,
|
||||
WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED,
|
||||
SEND_PAYMENT_STARTED_MESSAGE,
|
||||
WAIT_FOR_PAYMENT_STARTED_MESSAGE,
|
||||
SEND_PAYMENT_RECEIVED_CONFIRMATION_MESSAGE,
|
||||
WAIT_FOR_PAYMENT_RECEIVED_CONFIRMATION_MESSAGE,
|
||||
WAIT_FOR_PAYOUT_TX,
|
||||
KEEP_FUNDS,
|
||||
CLOSE_TRADE,
|
||||
DONE
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import static bisq.apitest.scenario.bot.protocol.ProtocolStep.DONE;
|
||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.FIND_OFFER;
|
||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.TAKE_OFFER;
|
||||
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.checkIfShutdownCalled;
|
||||
import static bisq.cli.TableFormat.formatOfferTable;
|
||||
import static bisq.cli.table.builder.TableType.OFFER_TBL;
|
||||
import static bisq.core.payment.payload.PaymentMethod.F2F_ID;
|
||||
|
||||
|
||||
@ -26,6 +26,7 @@ import bisq.apitest.method.BitcoinCliHelper;
|
||||
import bisq.apitest.scenario.bot.BotClient;
|
||||
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
||||
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
||||
import bisq.cli.table.builder.TableBuilder;
|
||||
|
||||
@Slf4j
|
||||
public class TakerBotProtocol extends BotProtocol {
|
||||
@ -55,16 +56,13 @@ public class TakerBotProtocol extends BotProtocol {
|
||||
: sendPaymentStartedMessage.andThen(waitForPaymentReceivedConfirmation);
|
||||
completeFiatTransaction.apply(trade);
|
||||
|
||||
Function<TradeInfo, TradeInfo> closeTrade = waitForPayoutTx.andThen(keepFundsFromTrade);
|
||||
closeTrade.apply(trade);
|
||||
|
||||
currentProtocolStep = DONE;
|
||||
}
|
||||
|
||||
private final Supplier<Optional<OfferInfo>> firstOffer = () -> {
|
||||
var offers = botClient.getOffers(currencyCode);
|
||||
if (offers.size() > 0) {
|
||||
log.info("Offers found:\n{}", formatOfferTable(offers, currencyCode));
|
||||
log.info("Offers found:\n{}", new TableBuilder(OFFER_TBL, offers).build());
|
||||
OfferInfo offer = offers.get(0);
|
||||
log.info("Will take first offer {}", offer.getId());
|
||||
return Optional.of(offer);
|
||||
@ -107,7 +105,6 @@ public class TakerBotProtocol extends BotProtocol {
|
||||
|
||||
private void createMakeOfferScript() {
|
||||
String direction = RANDOM.nextBoolean() ? "BUY" : "SELL";
|
||||
String feeCurrency = "BTC";
|
||||
boolean createMarginPricedOffer = RANDOM.nextBoolean();
|
||||
// If not using an F2F account, don't go over possible 0.01 BTC
|
||||
// limit if account is not signed.
|
||||
@ -120,15 +117,13 @@ public class TakerBotProtocol extends BotProtocol {
|
||||
currencyCode,
|
||||
amount,
|
||||
"0.0",
|
||||
"15.0",
|
||||
feeCurrency);
|
||||
"15.0");
|
||||
} else {
|
||||
script = bashScriptGenerator.createMakeFixedPricedOfferScript(direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
botClient.getCurrentBTCMarketPriceAsIntegerString(currencyCode),
|
||||
"15.0",
|
||||
feeCurrency);
|
||||
"15.0");
|
||||
}
|
||||
printCliHintAndOrScript(script, "The manual CLI side can create an offer");
|
||||
}
|
||||
|
@ -67,8 +67,7 @@ public class BashScriptGenerator {
|
||||
String currencyCode,
|
||||
String amount,
|
||||
String marketPriceMargin,
|
||||
String securityDeposit,
|
||||
String feeCurrency) {
|
||||
String securityDeposit) {
|
||||
String makeOfferCmd = format("%s createoffer --payment-account=%s "
|
||||
+ " --direction=%s"
|
||||
+ " --currency-code=%s"
|
||||
@ -82,8 +81,7 @@ public class BashScriptGenerator {
|
||||
currencyCode,
|
||||
amount,
|
||||
marketPriceMargin,
|
||||
securityDeposit,
|
||||
feeCurrency);
|
||||
securityDeposit);
|
||||
String getOffersCmd = format("%s getmyoffers --direction=%s --currency-code=%s",
|
||||
cliBase,
|
||||
direction,
|
||||
@ -98,8 +96,7 @@ public class BashScriptGenerator {
|
||||
String currencyCode,
|
||||
String amount,
|
||||
String fixedPrice,
|
||||
String securityDeposit,
|
||||
String feeCurrency) {
|
||||
String securityDeposit) {
|
||||
String makeOfferCmd = format("%s createoffer --payment-account=%s "
|
||||
+ " --direction=%s"
|
||||
+ " --currency-code=%s"
|
||||
@ -113,8 +110,7 @@ public class BashScriptGenerator {
|
||||
currencyCode,
|
||||
amount,
|
||||
fixedPrice,
|
||||
securityDeposit,
|
||||
feeCurrency);
|
||||
securityDeposit);
|
||||
String getOffersCmd = format("%s getmyoffers --direction=%s --currency-code=%s",
|
||||
cliBase,
|
||||
direction,
|
||||
@ -167,10 +163,10 @@ public class BashScriptGenerator {
|
||||
}
|
||||
|
||||
public File createKeepFundsScript(TradeInfo trade) {
|
||||
String paymentStartedCmd = format("%s keepfunds --trade-id=%s", cliBase, trade.getTradeId());
|
||||
String paymentStartedCmd = format("%s closetrade --trade-id=%s", cliBase, trade.getTradeId());
|
||||
String getTradeCmd = format("%s gettrade --trade-id=%s", cliBase, trade.getTradeId());
|
||||
String getBalanceCmd = format("%s getbalance", cliBase);
|
||||
return createCliScript("keepfunds.sh",
|
||||
return createCliScript("closetrade.sh",
|
||||
paymentStartedCmd,
|
||||
"sleep 2",
|
||||
getTradeCmd,
|
||||
|
@ -17,8 +17,9 @@
|
||||
|
||||
package bisq.apitest.scenario.bot.script;
|
||||
|
||||
import bisq.core.util.JsonUtil;
|
||||
|
||||
import bisq.common.file.JsonFileManager;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import joptsimple.BuiltinHelpFormatter;
|
||||
import joptsimple.OptionParser;
|
||||
@ -214,7 +215,7 @@ public class BotScriptGenerator {
|
||||
}
|
||||
|
||||
private String generateBotScriptTemplate() {
|
||||
return Utilities.objectToJson(new BotScript(
|
||||
return JsonUtil.objectToJson(new BotScript(
|
||||
useTestHarness,
|
||||
botPaymentMethodId,
|
||||
countryCode,
|
||||
|
20
apitest/src/test/resources/logback.xml
Normal file
20
apitest/src/test/resources/logback.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<!--
|
||||
The :daemon & :cli jars contain their own logback.xml config files, which causes chatty logback startup.
|
||||
To avoid chatty logback msgs during its configuration, pass logback.configurationFile as a system property:
|
||||
-Dlogback.configurationFile=apitest/build/resources/main/logback.xml
|
||||
The gradle build file takes care of adding this system property to the bisq-apitest script.
|
||||
-->
|
||||
<appender name="CONSOLE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%highlight(%d{MMM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{30}: %msg %xEx%n)</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE_APPENDER"/>
|
||||
</root>
|
||||
|
||||
<logger name="io.grpc.netty" level="WARN"/>
|
||||
</configuration>
|
@ -31,6 +31,7 @@ configure(subprojects) {
|
||||
bcVersion = '1.63'
|
||||
bitcoinjVersion = '2a80db4'
|
||||
codecVersion = '1.13'
|
||||
cowwocVersion = '1.2'
|
||||
easybindVersion = '1.0.3'
|
||||
easyVersion = '4.0.1'
|
||||
findbugsVersion = '3.0.2'
|
||||
@ -404,6 +405,7 @@ configure(project(':cli')) {
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$jupiterVersion")
|
||||
testAnnotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
||||
testCompileOnly "org.projectlombok:lombok:$lombokVersion"
|
||||
testImplementation "org.bitbucket.cowwoc:diff-match-patch:$cowwocVersion"
|
||||
testRuntime "javax.annotation:javax.annotation-api:$javaxAnnotationVersion"
|
||||
}
|
||||
|
||||
|
71
cli/package/create-cli-dist.sh
Executable file
71
cli/package/create-cli-dist.sh
Executable file
@ -0,0 +1,71 @@
|
||||
#! /bin/bash
|
||||
|
||||
VERSION="$1"
|
||||
if [[ -z "$VERSION" ]]; then
|
||||
VERSION="SNAPSHOT"
|
||||
fi
|
||||
|
||||
export BISQ_RELEASE_NAME="bisq-cli-$VERSION"
|
||||
export BISQ_RELEASE_ZIP_NAME="$BISQ_RELEASE_NAME.zip"
|
||||
|
||||
export GRADLE_DIST_NAME="cli.tar"
|
||||
export GRADLE_DIST_PATH="../build/distributions/$GRADLE_DIST_NAME"
|
||||
|
||||
arrangegradledist() {
|
||||
# Arrange $BISQ_RELEASE_NAME directory structure to contain a runnable
|
||||
# jar at the top-level, and a lib dir containing dependencies:
|
||||
# .
|
||||
# |
|
||||
# |__ cli.jar
|
||||
# |__ lib
|
||||
# |__ |__ dep1.jar
|
||||
# |__ |__ dep2.jar
|
||||
# |__ |__ ...
|
||||
# Copy the build's distribution tarball to this directory.
|
||||
cp -v $GRADLE_DIST_PATH .
|
||||
# Create a clean directory to hold the tarball's content.
|
||||
rm -rf $BISQ_RELEASE_NAME
|
||||
mkdir $BISQ_RELEASE_NAME
|
||||
# Extract the tarball's content into $BISQ_RELEASE_NAME.
|
||||
tar -xf $GRADLE_DIST_NAME -C $BISQ_RELEASE_NAME
|
||||
cd $BISQ_RELEASE_NAME
|
||||
# Rearrange $BISQ_RELEASE_NAME contents: move the lib directory up one level.
|
||||
mv -v cli/lib .
|
||||
# Rearrange $BISQ_RELEASE_NAME contents: remove the cli/bin and cli directories.
|
||||
rm -rf cli
|
||||
# Rearrange $BISQ_RELEASE_NAME contents: move the lib/cli.jar up one level.
|
||||
mv -v lib/cli.jar .
|
||||
}
|
||||
|
||||
writemanifest() {
|
||||
# Make the cli.jar runnable, and define its dependencies in a MANIFEST.MF update.
|
||||
echo "Main-Class: bisq.cli.CliMain" > manifest-update.txt
|
||||
printf "Class-Path: " >> manifest-update.txt
|
||||
for file in lib/*
|
||||
do
|
||||
# Each new line in the classpath must be preceded by two spaces.
|
||||
printf " %s\n" "$file" >> manifest-update.txt
|
||||
done
|
||||
}
|
||||
|
||||
updatemanifest() {
|
||||
# Append contents of to cli.jar's MANIFEST.MF.
|
||||
jar uvfm cli.jar manifest-update.txt
|
||||
}
|
||||
|
||||
ziprelease() {
|
||||
cd ..
|
||||
zip -r $BISQ_RELEASE_ZIP_NAME $BISQ_RELEASE_NAME/lib $BISQ_RELEASE_NAME/cli.jar
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
rm -v ./$GRADLE_DIST_NAME
|
||||
rm -r ./$BISQ_RELEASE_NAME
|
||||
}
|
||||
|
||||
arrangegradledist
|
||||
writemanifest
|
||||
updatemanifest
|
||||
ziprelease
|
||||
cleanup
|
||||
|
@ -18,6 +18,7 @@
|
||||
package bisq.cli;
|
||||
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
@ -39,18 +40,18 @@ import java.util.List;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.cli.CurrencyFormat.formatMarketPrice;
|
||||
import static bisq.cli.CurrencyFormat.formatInternalFiatPrice;
|
||||
import static bisq.cli.CurrencyFormat.formatTxFeeRateInfo;
|
||||
import static bisq.cli.CurrencyFormat.toSatoshis;
|
||||
import static bisq.cli.CurrencyFormat.toSecurityDepositAsPct;
|
||||
import static bisq.cli.Method.*;
|
||||
import static bisq.cli.TableFormat.*;
|
||||
import static bisq.cli.opts.OptLabel.*;
|
||||
import static bisq.cli.table.builder.TableType.*;
|
||||
import static bisq.proto.grpc.GetTradesRequest.Category.CLOSED;
|
||||
import static bisq.proto.grpc.GetTradesRequest.Category.OPEN;
|
||||
import static java.lang.String.format;
|
||||
import static java.lang.System.err;
|
||||
import static java.lang.System.exit;
|
||||
import static java.lang.System.out;
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
|
||||
|
||||
@ -62,11 +63,12 @@ import bisq.cli.opts.CreatePaymentAcctOptionParser;
|
||||
import bisq.cli.opts.GetAddressBalanceOptionParser;
|
||||
import bisq.cli.opts.GetBTCMarketPriceOptionParser;
|
||||
import bisq.cli.opts.GetBalanceOptionParser;
|
||||
import bisq.cli.opts.GetOfferOptionParser;
|
||||
import bisq.cli.opts.GetOffersOptionParser;
|
||||
import bisq.cli.opts.GetPaymentAcctFormOptionParser;
|
||||
import bisq.cli.opts.GetTradeOptionParser;
|
||||
import bisq.cli.opts.GetTradesOptionParser;
|
||||
import bisq.cli.opts.GetTransactionOptionParser;
|
||||
import bisq.cli.opts.OfferIdOptionParser;
|
||||
import bisq.cli.opts.RegisterDisputeAgentOptionParser;
|
||||
import bisq.cli.opts.RemoveWalletPasswordOptionParser;
|
||||
import bisq.cli.opts.SendBtcOptionParser;
|
||||
@ -76,6 +78,7 @@ import bisq.cli.opts.SimpleMethodOptionParser;
|
||||
import bisq.cli.opts.TakeOfferOptionParser;
|
||||
import bisq.cli.opts.UnlockWalletOptionParser;
|
||||
import bisq.cli.opts.WithdrawFundsOptionParser;
|
||||
import bisq.cli.table.builder.TableBuilder;
|
||||
|
||||
/**
|
||||
* A command-line client for the Bisq gRPC API.
|
||||
@ -167,15 +170,14 @@ public class CliMain {
|
||||
var balances = client.getBalances(currencyCode);
|
||||
switch (currencyCode.toUpperCase()) {
|
||||
case "BTC":
|
||||
out.println(formatBtcBalanceInfoTbl(balances.getBtc()));
|
||||
new TableBuilder(BTC_BALANCE_TBL, balances.getBtc()).build().print(out);
|
||||
break;
|
||||
case "XMR":
|
||||
out.println(formatXmrBalanceInfoTbl(balances.getXmr()));
|
||||
break;
|
||||
case "":
|
||||
default:
|
||||
out.println(formatBalancesTbls(balances));
|
||||
default: {
|
||||
out.println("BTC");
|
||||
new TableBuilder(BTC_BALANCE_TBL, balances.getBtc()).build().print(out);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -187,7 +189,7 @@ public class CliMain {
|
||||
}
|
||||
var address = opts.getAddress();
|
||||
var addressBalance = client.getAddressBalance(address);
|
||||
out.println(formatAddressBalanceTbl(singletonList(addressBalance)));
|
||||
new TableBuilder(ADDRESS_BALANCE_TBL, addressBalance).build().print(out);
|
||||
return;
|
||||
}
|
||||
case getbtcprice: {
|
||||
@ -198,7 +200,7 @@ public class CliMain {
|
||||
}
|
||||
var currencyCode = opts.getCurrencyCode();
|
||||
var price = client.getBtcPrice(currencyCode);
|
||||
out.println(formatMarketPrice(price));
|
||||
out.println(formatInternalFiatPrice(price));
|
||||
return;
|
||||
}
|
||||
case getfundingaddresses: {
|
||||
@ -207,7 +209,7 @@ public class CliMain {
|
||||
return;
|
||||
}
|
||||
var fundingAddresses = client.getFundingAddresses();
|
||||
out.println(formatAddressBalanceTbl(fundingAddresses));
|
||||
new TableBuilder(ADDRESS_BALANCE_TBL, fundingAddresses).build().print(out);
|
||||
return;
|
||||
}
|
||||
case sendbtc: {
|
||||
@ -269,7 +271,7 @@ public class CliMain {
|
||||
}
|
||||
var txId = opts.getTxId();
|
||||
var tx = client.getTransaction(txId);
|
||||
out.println(TransactionFormat.format(tx));
|
||||
new TableBuilder(TRANSACTION_TBL, tx).build().print(out);
|
||||
return;
|
||||
}
|
||||
case createoffer: {
|
||||
@ -285,18 +287,21 @@ public class CliMain {
|
||||
var minAmount = toSatoshis(opts.getMinAmount());
|
||||
var useMarketBasedPrice = opts.isUsingMktPriceMargin();
|
||||
var fixedPrice = opts.getFixedPrice();
|
||||
var marketPriceMargin = opts.getMktPriceMarginAsBigDecimal();
|
||||
var securityDeposit = toSecurityDepositAsPct(opts.getSecurityDeposit());
|
||||
var offer = client.createOffer(direction,
|
||||
var marketPriceMarginPct = opts.getMktPriceMarginPct();
|
||||
var securityDepositPct = opts.getSecurityDepositPct();
|
||||
var triggerPrice = "0"; // Cannot be defined until the new offer is added to book.
|
||||
OfferInfo offer;
|
||||
offer = client.createOffer(direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
minAmount,
|
||||
useMarketBasedPrice,
|
||||
fixedPrice,
|
||||
marketPriceMargin.doubleValue(),
|
||||
securityDeposit,
|
||||
paymentAcctId);
|
||||
out.println(formatOfferTable(singletonList(offer), currencyCode));
|
||||
marketPriceMarginPct,
|
||||
securityDepositPct,
|
||||
paymentAcctId,
|
||||
triggerPrice);
|
||||
new TableBuilder(OFFER_TBL, offer).build().print(out);
|
||||
return;
|
||||
}
|
||||
case canceloffer: {
|
||||
@ -311,25 +316,25 @@ public class CliMain {
|
||||
return;
|
||||
}
|
||||
case getoffer: {
|
||||
var opts = new GetOfferOptionParser(args).parse();
|
||||
var opts = new OfferIdOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var offerId = opts.getOfferId();
|
||||
var offer = client.getOffer(offerId);
|
||||
out.println(formatOfferTable(singletonList(offer), offer.getCounterCurrencyCode()));
|
||||
new TableBuilder(OFFER_TBL, offer).build().print(out);
|
||||
return;
|
||||
}
|
||||
case getmyoffer: {
|
||||
var opts = new GetOfferOptionParser(args).parse();
|
||||
var opts = new OfferIdOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var offerId = opts.getOfferId();
|
||||
var offer = client.getMyOffer(offerId);
|
||||
out.println(formatOfferTable(singletonList(offer), offer.getCounterCurrencyCode()));
|
||||
new TableBuilder(OFFER_TBL, offer).build().print(out);
|
||||
return;
|
||||
}
|
||||
case getoffers: {
|
||||
@ -344,7 +349,7 @@ public class CliMain {
|
||||
if (offers.isEmpty())
|
||||
out.printf("no %s %s offers found%n", direction, currencyCode);
|
||||
else
|
||||
out.println(formatOfferTable(offers, currencyCode));
|
||||
new TableBuilder(OFFER_TBL, offers).build().print(out);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -360,11 +365,12 @@ public class CliMain {
|
||||
if (offers.isEmpty())
|
||||
out.printf("no %s %s offers found%n", direction, currencyCode);
|
||||
else
|
||||
out.println(formatOfferTable(offers, currencyCode));
|
||||
new TableBuilder(OFFER_TBL, offers).build().print(out);
|
||||
|
||||
return;
|
||||
}
|
||||
case takeoffer: {
|
||||
|
||||
var opts = new TakeOfferOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(client.getMethodHelp(method));
|
||||
@ -389,10 +395,30 @@ public class CliMain {
|
||||
if (showContract)
|
||||
out.println(trade.getContractAsJson());
|
||||
else
|
||||
out.println(TradeFormat.format(trade));
|
||||
new TableBuilder(TRADE_DETAIL_TBL, trade).build().print(out);
|
||||
|
||||
return;
|
||||
}
|
||||
case gettrades: {
|
||||
var opts = new GetTradesOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var category = opts.getCategory();
|
||||
var trades = category.equals(OPEN)
|
||||
? client.getOpenTrades()
|
||||
: client.getTradeHistory(category);
|
||||
if (trades.isEmpty()) {
|
||||
out.printf("no %s trades found%n", category.name().toLowerCase());
|
||||
} else {
|
||||
var tableType = category.equals(OPEN)
|
||||
? OPEN_TRADES_TBL
|
||||
: category.equals(CLOSED) ? CLOSED_TRADES_TBL : FAILED_TRADES_TBL;
|
||||
new TableBuilder(tableType, trades).build().print(out);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case confirmpaymentstarted: {
|
||||
var opts = new GetTradeOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
@ -415,17 +441,6 @@ public class CliMain {
|
||||
out.printf("trade %s payment received message sent%n", tradeId);
|
||||
return;
|
||||
}
|
||||
case keepfunds: {
|
||||
var opts = new GetTradeOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var tradeId = opts.getTradeId();
|
||||
client.keepFunds(tradeId);
|
||||
out.printf("funds from trade %s saved in bisq wallet%n", tradeId);
|
||||
return;
|
||||
}
|
||||
case withdrawfunds: {
|
||||
var opts = new WithdrawFundsOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
@ -434,7 +449,7 @@ public class CliMain {
|
||||
}
|
||||
var tradeId = opts.getTradeId();
|
||||
var address = opts.getAddress();
|
||||
// Multi-word memos must be double quoted.
|
||||
// Multi-word memos must be double-quoted.
|
||||
var memo = opts.getMemo();
|
||||
client.withdrawFunds(tradeId, address, memo);
|
||||
out.printf("trade %s funds sent to btc address %s%n", tradeId, address);
|
||||
@ -481,11 +496,12 @@ public class CliMain {
|
||||
}
|
||||
var paymentAccount = client.createPaymentAccount(jsonString);
|
||||
out.println("payment account saved");
|
||||
out.println(formatPaymentAcctTbl(singletonList(paymentAccount)));
|
||||
new TableBuilder(PAYMENT_ACCOUNT_TBL, paymentAccount).build().print(out);
|
||||
return;
|
||||
}
|
||||
case createcryptopaymentacct: {
|
||||
var opts = new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse();
|
||||
var opts =
|
||||
new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
@ -499,7 +515,7 @@ public class CliMain {
|
||||
address,
|
||||
isTradeInstant);
|
||||
out.println("payment account saved");
|
||||
out.println(formatPaymentAcctTbl(singletonList(paymentAccount)));
|
||||
new TableBuilder(PAYMENT_ACCOUNT_TBL, paymentAccount).build().print(out);
|
||||
return;
|
||||
}
|
||||
case getpaymentaccts: {
|
||||
@ -509,7 +525,7 @@ public class CliMain {
|
||||
}
|
||||
var paymentAccounts = client.getPaymentAccounts();
|
||||
if (paymentAccounts.size() > 0)
|
||||
out.println(formatPaymentAcctTbl(paymentAccounts));
|
||||
new TableBuilder(PAYMENT_ACCOUNT_TBL, paymentAccounts).build().print(out);
|
||||
else
|
||||
out.println("no payment accounts are saved");
|
||||
|
||||
@ -585,7 +601,8 @@ public class CliMain {
|
||||
}
|
||||
}
|
||||
} catch (StatusRuntimeException ex) {
|
||||
// Remove the leading gRPC status code (e.g. "UNKNOWN: ") from the message
|
||||
// Remove the leading gRPC status code, e.g., INVALID_ARGUMENT,
|
||||
// NOT_FOUND, ..., UNKNOWN from the exception message.
|
||||
String message = ex.getMessage().replaceFirst("^[A-Z_]+: ", "");
|
||||
if (message.equals("io exception"))
|
||||
throw new RuntimeException(message + ", server may not be running", ex);
|
||||
@ -666,7 +683,7 @@ public class CliMain {
|
||||
stream.format(rowFormat, "------", "------", "------------");
|
||||
stream.format(rowFormat, getversion.name(), "", "Get server version");
|
||||
stream.println();
|
||||
stream.format(rowFormat, getbalance.name(), "[--currency-code=<btc>]", "Get server wallet balances");
|
||||
stream.format(rowFormat, getbalance.name(), "[--currency-code=<bsq|btc>]", "Get server wallet balances");
|
||||
stream.println();
|
||||
stream.format(rowFormat, getaddressbalance.name(), "--address=<btc-address>", "Get server wallet address balance");
|
||||
stream.println();
|
||||
@ -674,11 +691,14 @@ public class CliMain {
|
||||
stream.println();
|
||||
stream.format(rowFormat, getfundingaddresses.name(), "", "Get BTC funding addresses");
|
||||
stream.println();
|
||||
stream.format(rowFormat, getunusedbsqaddress.name(), "", "Get unused BSQ address");
|
||||
stream.println();
|
||||
stream.format(rowFormat, "", "[--tx-fee-rate=<sats/byte>]", "");
|
||||
stream.println();
|
||||
stream.format(rowFormat, sendbtc.name(), "--address=<btc-address> --amount=<btc-amount> \\", "Send BTC");
|
||||
stream.format(rowFormat, "", "[--tx-fee-rate=<sats/byte>]", "");
|
||||
stream.format(rowFormat, "", "[--memo=<\"memo\">]", "");
|
||||
stream.println();
|
||||
stream.println();
|
||||
stream.format(rowFormat, gettxfeerate.name(), "", "Get current tx fee rate in sats/byte");
|
||||
stream.println();
|
||||
stream.format(rowFormat, settxfeerate.name(), "--tx-fee-rate=<sats/byte>", "Set custom tx fee rate in sats/byte");
|
||||
@ -692,9 +712,17 @@ public class CliMain {
|
||||
stream.format(rowFormat, "", "--currency-code=<currency-code> \\", "");
|
||||
stream.format(rowFormat, "", "--amount=<btc-amount> \\", "");
|
||||
stream.format(rowFormat, "", "[--min-amount=<min-btc-amount>] \\", "");
|
||||
stream.format(rowFormat, "", "--fixed-price=<price> | --market-price=margin=<percent> \\", "");
|
||||
stream.format(rowFormat, "", "--fixed-price=<price> | --market-price-margin=<percent> \\", "");
|
||||
stream.format(rowFormat, "", "--security-deposit=<percent> \\", "");
|
||||
stream.format(rowFormat, "", "[--fee-currency=<btc>]", "");
|
||||
stream.format(rowFormat, "", "[--fee-currency=<bsq|btc>]", "");
|
||||
stream.format(rowFormat, "", "[--trigger-price=<price>]", "");
|
||||
stream.format(rowFormat, "", "[--swap=<true|false>]", "");
|
||||
stream.println();
|
||||
stream.format(rowFormat, editoffer.name(), "--offer-id=<offer-id> \\", "Edit offer with id");
|
||||
stream.format(rowFormat, "", "[--fixed-price=<price>] \\", "");
|
||||
stream.format(rowFormat, "", "[--market-price-margin=<percent>] \\", "");
|
||||
stream.format(rowFormat, "", "[--trigger-price=<price>] \\", "");
|
||||
stream.format(rowFormat, "", "[--enabled=<true|false>]", "");
|
||||
stream.println();
|
||||
stream.format(rowFormat, canceloffer.name(), "--offer-id=<offer-id>", "Cancel offer with id");
|
||||
stream.println();
|
||||
@ -709,22 +737,28 @@ public class CliMain {
|
||||
stream.format(rowFormat, "", "--currency-code=<currency-code>", "");
|
||||
stream.println();
|
||||
stream.format(rowFormat, takeoffer.name(), "--offer-id=<offer-id> \\", "Take offer with id");
|
||||
stream.format(rowFormat, "", "--payment-account=<payment-account-id>", "");
|
||||
stream.format(rowFormat, "", "[--fee-currency=<btc>]", "");
|
||||
stream.format(rowFormat, "", "[--payment-account=<payment-account-id>]", "");
|
||||
stream.format(rowFormat, "", "[--fee-currency=<btc|bsq>]", "");
|
||||
stream.println();
|
||||
stream.format(rowFormat, gettrade.name(), "--trade-id=<trade-id> \\", "Get trade summary or full contract");
|
||||
stream.format(rowFormat, "", "[--show-contract=<true|false>]", "");
|
||||
stream.println();
|
||||
stream.format(rowFormat, gettrades.name(), "[--category=<open|closed|failed>]", "Get open (default), closed, or failed trades");
|
||||
stream.println();
|
||||
stream.format(rowFormat, confirmpaymentstarted.name(), "--trade-id=<trade-id>", "Confirm payment started");
|
||||
stream.println();
|
||||
stream.format(rowFormat, confirmpaymentreceived.name(), "--trade-id=<trade-id>", "Confirm payment received");
|
||||
stream.println();
|
||||
stream.format(rowFormat, keepfunds.name(), "--trade-id=<trade-id>", "Keep received funds in Bisq wallet");
|
||||
stream.format(rowFormat, closetrade.name(), "--trade-id=<trade-id>", "Close completed trade");
|
||||
stream.println();
|
||||
stream.format(rowFormat, withdrawfunds.name(), "--trade-id=<trade-id> --address=<btc-address> \\",
|
||||
"Withdraw received funds to external wallet address");
|
||||
"Withdraw received trade funds to external wallet address");
|
||||
stream.format(rowFormat, "", "[--memo=<\"memo\">]", "");
|
||||
stream.println();
|
||||
stream.format(rowFormat, failtrade.name(), "--trade-id=<trade-id>", "Change open trade to failed trade");
|
||||
stream.println();
|
||||
stream.format(rowFormat, unfailtrade.name(), "--trade-id=<trade-id>", "Change failed trade to open trade");
|
||||
stream.println();
|
||||
stream.format(rowFormat, getpaymentmethods.name(), "", "Get list of supported payment account method ids");
|
||||
stream.println();
|
||||
stream.format(rowFormat, getpaymentacctform.name(), "--payment-method-id=<payment-method-id>", "Get a new payment account form");
|
||||
@ -732,8 +766,8 @@ public class CliMain {
|
||||
stream.format(rowFormat, createpaymentacct.name(), "--payment-account-form=<path>", "Create a new payment account");
|
||||
stream.println();
|
||||
stream.format(rowFormat, createcryptopaymentacct.name(), "--account-name=<name> \\", "Create a new cryptocurrency payment account");
|
||||
stream.format(rowFormat, "", "--currency-code=<btc> \\", "");
|
||||
stream.format(rowFormat, "", "--address=<address>", "");
|
||||
stream.format(rowFormat, "", "--currency-code=<bsq> \\", "");
|
||||
stream.format(rowFormat, "", "--address=<bsq-address>", "");
|
||||
stream.format(rowFormat, "", "--trade-instant=<true|false>", "");
|
||||
stream.println();
|
||||
stream.format(rowFormat, getpaymentaccts.name(), "", "Get user payment accounts");
|
||||
|
@ -20,14 +20,15 @@ package bisq.cli;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class CryptoCurrencyUtil {
|
||||
public class CryptoCurrencyUtil {
|
||||
|
||||
public static boolean isSupportedCryptoCurrency(String currencyCode) {
|
||||
public static boolean apiDoesSupportCryptoCurrency(String currencyCode) {
|
||||
return getSupportedCryptoCurrencies().contains(currencyCode.toUpperCase());
|
||||
}
|
||||
|
||||
public static List<String> getSupportedCryptoCurrencies() {
|
||||
final List<String> result = new ArrayList<>();
|
||||
result.add("BCH");
|
||||
result.sort(String::compareTo);
|
||||
return result;
|
||||
}
|
||||
|
@ -22,10 +22,10 @@ import bisq.proto.grpc.TxFeeRateInfo;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.text.NumberFormat;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
@ -33,32 +33,48 @@ import static java.lang.String.format;
|
||||
import static java.math.RoundingMode.HALF_UP;
|
||||
import static java.math.RoundingMode.UNNECESSARY;
|
||||
|
||||
|
||||
|
||||
import monero.common.MoneroUtils;
|
||||
|
||||
/**
|
||||
* Utility for formatting amounts, volumes and fees; there is no i18n support in the CLI.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public class CurrencyFormat {
|
||||
|
||||
private static final NumberFormat NUMBER_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
// Use the US locale as a base for all DecimalFormats, but commas should be omitted from number strings.
|
||||
private static final DecimalFormatSymbols DECIMAL_FORMAT_SYMBOLS = DecimalFormatSymbols.getInstance(Locale.US);
|
||||
|
||||
// Use the US locale as a base for all NumberFormats, but commas should be omitted from number strings.
|
||||
private static final NumberFormat US_LOCALE_NUMBER_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||
|
||||
// Formats numbers for internal use, i.e., grpc request parameters.
|
||||
private static final DecimalFormat INTERNAL_FIAT_DECIMAL_FORMAT = new DecimalFormat("##############0.0000");
|
||||
|
||||
static final BigDecimal SATOSHI_DIVISOR = new BigDecimal(100_000_000);
|
||||
static final DecimalFormat BTC_FORMAT = new DecimalFormat("###,##0.00000000");
|
||||
static final DecimalFormat BTC_TX_FEE_FORMAT = new DecimalFormat("###,###,##0");
|
||||
static final DecimalFormat SATOSHI_FORMAT = new DecimalFormat("###,##0.00000000", DECIMAL_FORMAT_SYMBOLS);
|
||||
static final DecimalFormat BTC_FORMAT = new DecimalFormat("###,##0.########", DECIMAL_FORMAT_SYMBOLS);
|
||||
static final DecimalFormat BTC_TX_FEE_FORMAT = new DecimalFormat("###,###,##0", DECIMAL_FORMAT_SYMBOLS);
|
||||
|
||||
static final BigDecimal SECURITY_DEPOSIT_MULTIPLICAND = new BigDecimal("0.01");
|
||||
static final BigDecimal BSQ_SATOSHI_DIVISOR = new BigDecimal(100);
|
||||
static final DecimalFormat BSQ_FORMAT = new DecimalFormat("###,###,###,##0.00", DECIMAL_FORMAT_SYMBOLS);
|
||||
|
||||
// TODO: (woodser): replace formatSatoshis(), formatBsq() with formatXmr()
|
||||
public static String formatSatoshis(String sats) {
|
||||
//noinspection BigDecimalMethodWithoutRoundingCalled
|
||||
return SATOSHI_FORMAT.format(new BigDecimal(sats).divide(SATOSHI_DIVISOR));
|
||||
}
|
||||
|
||||
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
|
||||
public static String formatSatoshis(long sats) {
|
||||
return BTC_FORMAT.format(BigDecimal.valueOf(sats).divide(SATOSHI_DIVISOR));
|
||||
return SATOSHI_FORMAT.format(new BigDecimal(sats).divide(SATOSHI_DIVISOR));
|
||||
}
|
||||
|
||||
public static String formatXmr(BigInteger amount) {
|
||||
return "" + MoneroUtils.atomicUnitsToXmr(amount);
|
||||
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
|
||||
public static String formatBtc(long sats) {
|
||||
return BTC_FORMAT.format(new BigDecimal(sats).divide(SATOSHI_DIVISOR));
|
||||
}
|
||||
|
||||
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
|
||||
public static String formatBsq(long sats) {
|
||||
return BSQ_FORMAT.format(new BigDecimal(sats).divide(BSQ_SATOSHI_DIVISOR));
|
||||
}
|
||||
|
||||
public static String formatTxFeeRateInfo(TxFeeRateInfo txFeeRateInfo) {
|
||||
if (txFeeRateInfo.getUseCustomTxFeeRate())
|
||||
@ -72,56 +88,30 @@ public class CurrencyFormat {
|
||||
formatFeeSatoshis(txFeeRateInfo.getMinFeeServiceRate()));
|
||||
}
|
||||
|
||||
public static String formatAmountRange(long minAmount, long amount) {
|
||||
return minAmount != amount
|
||||
? formatSatoshis(minAmount) + " - " + formatSatoshis(amount)
|
||||
: formatSatoshis(amount);
|
||||
public static String formatInternalFiatPrice(BigDecimal price) {
|
||||
INTERNAL_FIAT_DECIMAL_FORMAT.setMinimumFractionDigits(4);
|
||||
INTERNAL_FIAT_DECIMAL_FORMAT.setMaximumFractionDigits(4);
|
||||
return INTERNAL_FIAT_DECIMAL_FORMAT.format(price);
|
||||
}
|
||||
|
||||
public static String formatVolumeRange(long minVolume, long volume) {
|
||||
return minVolume != volume
|
||||
? formatOfferVolume(minVolume) + " - " + formatOfferVolume(volume)
|
||||
: formatOfferVolume(volume);
|
||||
}
|
||||
|
||||
public static String formatCryptoCurrencyVolumeRange(long minVolume, long volume) {
|
||||
return minVolume != volume
|
||||
? formatCryptoCurrencyOfferVolume(minVolume) + " - " + formatCryptoCurrencyOfferVolume(volume)
|
||||
: formatCryptoCurrencyOfferVolume(volume);
|
||||
}
|
||||
|
||||
public static String formatMarketPrice(double price) {
|
||||
NUMBER_FORMAT.setMinimumFractionDigits(4);
|
||||
NUMBER_FORMAT.setMaximumFractionDigits(4);
|
||||
return NUMBER_FORMAT.format(price);
|
||||
public static String formatInternalFiatPrice(double price) {
|
||||
US_LOCALE_NUMBER_FORMAT.setMinimumFractionDigits(4);
|
||||
US_LOCALE_NUMBER_FORMAT.setMaximumFractionDigits(4);
|
||||
return US_LOCALE_NUMBER_FORMAT.format(price);
|
||||
}
|
||||
|
||||
public static String formatPrice(long price) {
|
||||
NUMBER_FORMAT.setMinimumFractionDigits(4);
|
||||
NUMBER_FORMAT.setMaximumFractionDigits(4);
|
||||
NUMBER_FORMAT.setRoundingMode(UNNECESSARY);
|
||||
return NUMBER_FORMAT.format((double) price / 10_000);
|
||||
US_LOCALE_NUMBER_FORMAT.setMinimumFractionDigits(4);
|
||||
US_LOCALE_NUMBER_FORMAT.setMaximumFractionDigits(4);
|
||||
US_LOCALE_NUMBER_FORMAT.setRoundingMode(UNNECESSARY);
|
||||
return US_LOCALE_NUMBER_FORMAT.format((double) price / 10_000);
|
||||
}
|
||||
|
||||
public static String formatCryptoCurrencyPrice(long price) {
|
||||
NUMBER_FORMAT.setMinimumFractionDigits(8);
|
||||
NUMBER_FORMAT.setMaximumFractionDigits(8);
|
||||
NUMBER_FORMAT.setRoundingMode(UNNECESSARY);
|
||||
return NUMBER_FORMAT.format((double) price / SATOSHI_DIVISOR.doubleValue());
|
||||
}
|
||||
|
||||
public static String formatOfferVolume(long volume) {
|
||||
NUMBER_FORMAT.setMinimumFractionDigits(0);
|
||||
NUMBER_FORMAT.setMaximumFractionDigits(0);
|
||||
NUMBER_FORMAT.setRoundingMode(HALF_UP);
|
||||
return NUMBER_FORMAT.format((double) volume / 10_000);
|
||||
}
|
||||
|
||||
public static String formatCryptoCurrencyOfferVolume(long volume) {
|
||||
NUMBER_FORMAT.setMinimumFractionDigits(2);
|
||||
NUMBER_FORMAT.setMaximumFractionDigits(2);
|
||||
NUMBER_FORMAT.setRoundingMode(HALF_UP);
|
||||
return NUMBER_FORMAT.format((double) volume / SATOSHI_DIVISOR.doubleValue());
|
||||
public static String formatFiatVolume(long volume) {
|
||||
US_LOCALE_NUMBER_FORMAT.setMinimumFractionDigits(0);
|
||||
US_LOCALE_NUMBER_FORMAT.setMaximumFractionDigits(0);
|
||||
US_LOCALE_NUMBER_FORMAT.setRoundingMode(HALF_UP);
|
||||
return US_LOCALE_NUMBER_FORMAT.format((double) volume / 10_000);
|
||||
}
|
||||
|
||||
public static long toSatoshis(String btc) {
|
||||
@ -135,15 +125,6 @@ public class CurrencyFormat {
|
||||
}
|
||||
}
|
||||
|
||||
public static double toSecurityDepositAsPct(String securityDepositInput) {
|
||||
try {
|
||||
return new BigDecimal(securityDepositInput)
|
||||
.multiply(SECURITY_DEPOSIT_MULTIPLICAND).doubleValue();
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(format("'%s' is not a number", securityDepositInput));
|
||||
}
|
||||
}
|
||||
|
||||
public static String formatFeeSatoshis(long sats) {
|
||||
return BTC_TX_FEE_FORMAT.format(BigDecimal.valueOf(sats));
|
||||
}
|
||||
|
@ -24,8 +24,8 @@ import java.util.function.Function;
|
||||
|
||||
import static bisq.cli.ColumnHeaderConstants.COL_HEADER_DIRECTION;
|
||||
import static java.lang.String.format;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
import static protobuf.OfferDirection.SELL;
|
||||
|
||||
class DirectionFormat {
|
||||
|
||||
|
@ -20,62 +20,28 @@ package bisq.cli;
|
||||
import bisq.proto.grpc.AddressBalanceInfo;
|
||||
import bisq.proto.grpc.BalancesInfo;
|
||||
import bisq.proto.grpc.BtcBalanceInfo;
|
||||
import bisq.proto.grpc.CancelOfferRequest;
|
||||
import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
|
||||
import bisq.proto.grpc.ConfirmPaymentStartedRequest;
|
||||
import bisq.proto.grpc.CreateCryptoCurrencyPaymentAccountRequest;
|
||||
import bisq.proto.grpc.CreateOfferRequest;
|
||||
import bisq.proto.grpc.CreatePaymentAccountRequest;
|
||||
import bisq.proto.grpc.GetAddressBalanceRequest;
|
||||
import bisq.proto.grpc.GetBalancesRequest;
|
||||
import bisq.proto.grpc.GetCryptoCurrencyPaymentMethodsRequest;
|
||||
import bisq.proto.grpc.GetFundingAddressesRequest;
|
||||
import bisq.proto.grpc.GetMethodHelpRequest;
|
||||
import bisq.proto.grpc.GetMyOfferRequest;
|
||||
import bisq.proto.grpc.GetMyOffersRequest;
|
||||
import bisq.proto.grpc.GetOfferRequest;
|
||||
import bisq.proto.grpc.GetOffersRequest;
|
||||
import bisq.proto.grpc.GetPaymentAccountFormRequest;
|
||||
import bisq.proto.grpc.GetPaymentAccountsRequest;
|
||||
import bisq.proto.grpc.GetPaymentMethodsRequest;
|
||||
import bisq.proto.grpc.GetTradeRequest;
|
||||
import bisq.proto.grpc.GetTradesRequest;
|
||||
import bisq.proto.grpc.GetTransactionRequest;
|
||||
import bisq.proto.grpc.GetTxFeeRateRequest;
|
||||
import bisq.proto.grpc.GetVersionRequest;
|
||||
import bisq.proto.grpc.KeepFundsRequest;
|
||||
import bisq.proto.grpc.LockWalletRequest;
|
||||
import bisq.proto.grpc.MarketPriceRequest;
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
import bisq.proto.grpc.RegisterDisputeAgentRequest;
|
||||
import bisq.proto.grpc.RemoveWalletPasswordRequest;
|
||||
import bisq.proto.grpc.SendBtcRequest;
|
||||
import bisq.proto.grpc.SetTxFeeRatePreferenceRequest;
|
||||
import bisq.proto.grpc.SetWalletPasswordRequest;
|
||||
import bisq.proto.grpc.StopRequest;
|
||||
import bisq.proto.grpc.TakeOfferReply;
|
||||
import bisq.proto.grpc.TakeOfferRequest;
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
import bisq.proto.grpc.TxFeeRateInfo;
|
||||
import bisq.proto.grpc.TxInfo;
|
||||
import bisq.proto.grpc.UnlockWalletRequest;
|
||||
import bisq.proto.grpc.UnsetTxFeeRatePreferenceRequest;
|
||||
import bisq.proto.grpc.WithdrawFundsRequest;
|
||||
import bisq.proto.grpc.XmrBalanceInfo;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
import protobuf.PaymentMethod;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.cli.CryptoCurrencyUtil.isSupportedCryptoCurrency;
|
||||
import static java.util.Comparator.comparing;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
|
||||
import bisq.cli.request.OffersServiceRequest;
|
||||
import bisq.cli.request.PaymentAccountsServiceRequest;
|
||||
import bisq.cli.request.TradesServiceRequest;
|
||||
import bisq.cli.request.WalletsServiceRequest;
|
||||
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
@ -83,9 +49,19 @@ import static protobuf.OfferPayload.Direction.SELL;
|
||||
public final class GrpcClient {
|
||||
|
||||
private final GrpcStubs grpcStubs;
|
||||
private final OffersServiceRequest offersServiceRequest;
|
||||
private final TradesServiceRequest tradesServiceRequest;
|
||||
private final WalletsServiceRequest walletsServiceRequest;
|
||||
private final PaymentAccountsServiceRequest paymentAccountsServiceRequest;
|
||||
|
||||
public GrpcClient(String apiHost, int apiPort, String apiPassword) {
|
||||
public GrpcClient(String apiHost,
|
||||
int apiPort,
|
||||
String apiPassword) {
|
||||
this.grpcStubs = new GrpcStubs(apiHost, apiPort, apiPassword);
|
||||
this.offersServiceRequest = new OffersServiceRequest(grpcStubs);
|
||||
this.tradesServiceRequest = new TradesServiceRequest(grpcStubs);
|
||||
this.walletsServiceRequest = new WalletsServiceRequest(grpcStubs);
|
||||
this.paymentAccountsServiceRequest = new PaymentAccountsServiceRequest(grpcStubs);
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
@ -94,86 +70,51 @@ public final class GrpcClient {
|
||||
}
|
||||
|
||||
public BalancesInfo getBalances() {
|
||||
return getBalances("");
|
||||
return walletsServiceRequest.getBalances();
|
||||
}
|
||||
|
||||
public BtcBalanceInfo getBtcBalances() {
|
||||
return getBalances("BTC").getBtc();
|
||||
}
|
||||
|
||||
public XmrBalanceInfo getXmrBalances() {
|
||||
return getBalances("XMR").getXmr();
|
||||
return walletsServiceRequest.getBtcBalances();
|
||||
}
|
||||
|
||||
public BalancesInfo getBalances(String currencyCode) {
|
||||
var request = GetBalancesRequest.newBuilder()
|
||||
.setCurrencyCode(currencyCode)
|
||||
.build();
|
||||
return grpcStubs.walletsService.getBalances(request).getBalances();
|
||||
return walletsServiceRequest.getBalances(currencyCode);
|
||||
}
|
||||
|
||||
public AddressBalanceInfo getAddressBalance(String address) {
|
||||
var request = GetAddressBalanceRequest.newBuilder()
|
||||
.setAddress(address).build();
|
||||
return grpcStubs.walletsService.getAddressBalance(request).getAddressBalanceInfo();
|
||||
return walletsServiceRequest.getAddressBalance(address);
|
||||
}
|
||||
|
||||
public double getBtcPrice(String currencyCode) {
|
||||
var request = MarketPriceRequest.newBuilder()
|
||||
.setCurrencyCode(currencyCode)
|
||||
.build();
|
||||
return grpcStubs.priceService.getMarketPrice(request).getPrice();
|
||||
return walletsServiceRequest.getBtcPrice(currencyCode);
|
||||
}
|
||||
|
||||
public List<AddressBalanceInfo> getFundingAddresses() {
|
||||
var request = GetFundingAddressesRequest.newBuilder().build();
|
||||
return grpcStubs.walletsService.getFundingAddresses(request).getAddressBalanceInfoList();
|
||||
return walletsServiceRequest.getFundingAddresses();
|
||||
}
|
||||
|
||||
public String getUnusedBtcAddress() {
|
||||
var request = GetFundingAddressesRequest.newBuilder().build();
|
||||
var addressBalances = grpcStubs.walletsService.getFundingAddresses(request)
|
||||
.getAddressBalanceInfoList();
|
||||
//noinspection OptionalGetWithoutIsPresent
|
||||
return addressBalances.stream()
|
||||
.filter(AddressBalanceInfo::getIsAddressUnused)
|
||||
.findFirst()
|
||||
.get()
|
||||
.getAddress();
|
||||
return walletsServiceRequest.getUnusedBtcAddress();
|
||||
}
|
||||
|
||||
public TxInfo sendBtc(String address, String amount, String txFeeRate, String memo) {
|
||||
var request = SendBtcRequest.newBuilder()
|
||||
.setAddress(address)
|
||||
.setAmount(amount)
|
||||
.setTxFeeRate(txFeeRate)
|
||||
.setMemo(memo)
|
||||
.build();
|
||||
return grpcStubs.walletsService.sendBtc(request).getTxInfo();
|
||||
return walletsServiceRequest.sendBtc(address, amount, txFeeRate, memo);
|
||||
}
|
||||
|
||||
public TxFeeRateInfo getTxFeeRate() {
|
||||
var request = GetTxFeeRateRequest.newBuilder().build();
|
||||
return grpcStubs.walletsService.getTxFeeRate(request).getTxFeeRateInfo();
|
||||
return walletsServiceRequest.getTxFeeRate();
|
||||
}
|
||||
|
||||
public TxFeeRateInfo setTxFeeRate(long txFeeRate) {
|
||||
var request = SetTxFeeRatePreferenceRequest.newBuilder()
|
||||
.setTxFeeRatePreference(txFeeRate)
|
||||
.build();
|
||||
return grpcStubs.walletsService.setTxFeeRatePreference(request).getTxFeeRateInfo();
|
||||
return walletsServiceRequest.setTxFeeRate(txFeeRate);
|
||||
}
|
||||
|
||||
public TxFeeRateInfo unsetTxFeeRate() {
|
||||
var request = UnsetTxFeeRatePreferenceRequest.newBuilder().build();
|
||||
return grpcStubs.walletsService.unsetTxFeeRatePreference(request).getTxFeeRateInfo();
|
||||
return walletsServiceRequest.unsetTxFeeRate();
|
||||
}
|
||||
|
||||
public TxInfo getTransaction(String txId) {
|
||||
var request = GetTransactionRequest.newBuilder()
|
||||
.setTxId(txId)
|
||||
.build();
|
||||
return grpcStubs.walletsService.getTransaction(request).getTxInfo();
|
||||
return walletsServiceRequest.getTransaction(txId);
|
||||
}
|
||||
|
||||
public OfferInfo createFixedPricedOffer(String direction,
|
||||
@ -181,35 +122,38 @@ public final class GrpcClient {
|
||||
long amount,
|
||||
long minAmount,
|
||||
String fixedPrice,
|
||||
double securityDeposit,
|
||||
double securityDepositPct,
|
||||
String paymentAcctId) {
|
||||
return createOffer(direction,
|
||||
return offersServiceRequest.createOffer(direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
minAmount,
|
||||
false,
|
||||
fixedPrice,
|
||||
0.00,
|
||||
securityDeposit,
|
||||
paymentAcctId);
|
||||
securityDepositPct,
|
||||
paymentAcctId,
|
||||
"0" /* no trigger price */);
|
||||
}
|
||||
|
||||
public OfferInfo createMarketBasedPricedOffer(String direction,
|
||||
String currencyCode,
|
||||
long amount,
|
||||
long minAmount,
|
||||
double marketPriceMargin,
|
||||
double securityDeposit,
|
||||
String paymentAcctId) {
|
||||
return createOffer(direction,
|
||||
double marketPriceMarginPct,
|
||||
double securityDepositPct,
|
||||
String paymentAcctId,
|
||||
String triggerPrice) {
|
||||
return offersServiceRequest.createOffer(direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
minAmount,
|
||||
true,
|
||||
"0",
|
||||
marketPriceMargin,
|
||||
securityDeposit,
|
||||
paymentAcctId);
|
||||
marketPriceMarginPct,
|
||||
securityDepositPct,
|
||||
paymentAcctId,
|
||||
triggerPrice);
|
||||
}
|
||||
|
||||
public OfferInfo createOffer(String direction,
|
||||
@ -218,244 +162,140 @@ public final class GrpcClient {
|
||||
long minAmount,
|
||||
boolean useMarketBasedPrice,
|
||||
String fixedPrice,
|
||||
double marketPriceMargin,
|
||||
double securityDeposit,
|
||||
String paymentAcctId) {
|
||||
var request = CreateOfferRequest.newBuilder()
|
||||
.setDirection(direction)
|
||||
.setCurrencyCode(currencyCode)
|
||||
.setAmount(amount)
|
||||
.setMinAmount(minAmount)
|
||||
.setUseMarketBasedPrice(useMarketBasedPrice)
|
||||
.setPrice(fixedPrice)
|
||||
.setMarketPriceMargin(marketPriceMargin)
|
||||
.setBuyerSecurityDeposit(securityDeposit)
|
||||
.setPaymentAccountId(paymentAcctId)
|
||||
.build();
|
||||
return grpcStubs.offersService.createOffer(request).getOffer();
|
||||
double marketPriceMarginPct,
|
||||
double securityDepositPct,
|
||||
String paymentAcctId,
|
||||
String triggerPrice) {
|
||||
return offersServiceRequest.createOffer(direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
minAmount,
|
||||
useMarketBasedPrice,
|
||||
fixedPrice,
|
||||
marketPriceMarginPct,
|
||||
securityDepositPct,
|
||||
paymentAcctId,
|
||||
triggerPrice);
|
||||
}
|
||||
|
||||
public void cancelOffer(String offerId) {
|
||||
var request = CancelOfferRequest.newBuilder()
|
||||
.setId(offerId)
|
||||
.build();
|
||||
grpcStubs.offersService.cancelOffer(request);
|
||||
offersServiceRequest.cancelOffer(offerId);
|
||||
}
|
||||
|
||||
public OfferInfo getOffer(String offerId) {
|
||||
var request = GetOfferRequest.newBuilder()
|
||||
.setId(offerId)
|
||||
.build();
|
||||
return grpcStubs.offersService.getOffer(request).getOffer();
|
||||
return offersServiceRequest.getOffer(offerId);
|
||||
}
|
||||
|
||||
@Deprecated // Since 5-Dec-2021.
|
||||
// Endpoint to be removed from future version. Use getOffer service method instead.
|
||||
public OfferInfo getMyOffer(String offerId) {
|
||||
var request = GetMyOfferRequest.newBuilder()
|
||||
.setId(offerId)
|
||||
.build();
|
||||
return grpcStubs.offersService.getMyOffer(request).getOffer();
|
||||
return offersServiceRequest.getMyOffer(offerId);
|
||||
}
|
||||
|
||||
public List<OfferInfo> getOffers(String direction, String currencyCode) {
|
||||
if (isSupportedCryptoCurrency(currencyCode)) {
|
||||
return getCryptoCurrencyOffers(direction, currencyCode);
|
||||
} else {
|
||||
var request = GetOffersRequest.newBuilder()
|
||||
.setDirection(direction)
|
||||
.setCurrencyCode(currencyCode)
|
||||
.build();
|
||||
return grpcStubs.offersService.getOffers(request).getOffersList();
|
||||
}
|
||||
}
|
||||
|
||||
public List<OfferInfo> getCryptoCurrencyOffers(String direction, String currencyCode) {
|
||||
return getOffers(direction, "XMR").stream()
|
||||
.filter(o -> o.getBaseCurrencyCode().equalsIgnoreCase(currencyCode))
|
||||
.collect(toList());
|
||||
return offersServiceRequest.getOffers(direction, currencyCode);
|
||||
}
|
||||
|
||||
public List<OfferInfo> getOffersSortedByDate(String currencyCode) {
|
||||
ArrayList<OfferInfo> offers = new ArrayList<>();
|
||||
offers.addAll(getOffers(BUY.name(), currencyCode));
|
||||
offers.addAll(getOffers(SELL.name(), currencyCode));
|
||||
return sortOffersByDate(offers);
|
||||
return offersServiceRequest.getOffersSortedByDate(currencyCode);
|
||||
}
|
||||
|
||||
public List<OfferInfo> getOffersSortedByDate(String direction, String currencyCode) {
|
||||
var offers = getOffers(direction, currencyCode);
|
||||
return offers.isEmpty() ? offers : sortOffersByDate(offers);
|
||||
return offersServiceRequest.getOffersSortedByDate(direction, currencyCode);
|
||||
}
|
||||
|
||||
public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
|
||||
if (isSupportedCryptoCurrency(currencyCode)) {
|
||||
return getMyCryptoCurrencyOffers(direction, currencyCode);
|
||||
} else {
|
||||
var request = GetMyOffersRequest.newBuilder()
|
||||
.setDirection(direction)
|
||||
.setCurrencyCode(currencyCode)
|
||||
.build();
|
||||
return grpcStubs.offersService.getMyOffers(request).getOffersList();
|
||||
}
|
||||
}
|
||||
|
||||
public List<OfferInfo> getMyCryptoCurrencyOffers(String direction, String currencyCode) {
|
||||
return getMyOffers(direction, "BTC").stream()
|
||||
.filter(o -> o.getBaseCurrencyCode().equalsIgnoreCase(currencyCode))
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
public List<OfferInfo> getMyOffersSortedByDate(String direction, String currencyCode) {
|
||||
var offers = getMyOffers(direction, currencyCode);
|
||||
return offers.isEmpty() ? offers : sortOffersByDate(offers);
|
||||
return offersServiceRequest.getMyOffers(direction, currencyCode);
|
||||
}
|
||||
|
||||
public List<OfferInfo> getMyOffersSortedByDate(String currencyCode) {
|
||||
ArrayList<OfferInfo> offers = new ArrayList<>();
|
||||
offers.addAll(getMyOffers(BUY.name(), currencyCode));
|
||||
offers.addAll(getMyOffers(SELL.name(), currencyCode));
|
||||
return sortOffersByDate(offers);
|
||||
return offersServiceRequest.getMyOffersSortedByDate(currencyCode);
|
||||
}
|
||||
|
||||
public OfferInfo getMostRecentOffer(String direction, String currencyCode) {
|
||||
List<OfferInfo> offers = getOffersSortedByDate(direction, currencyCode);
|
||||
return offers.isEmpty() ? null : offers.get(offers.size() - 1);
|
||||
}
|
||||
|
||||
public List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) {
|
||||
return offerInfoList.stream()
|
||||
.sorted(comparing(OfferInfo::getDate))
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
public TakeOfferReply getTakeOfferReply(String offerId, String paymentAccountId) {
|
||||
var request = TakeOfferRequest.newBuilder()
|
||||
.setOfferId(offerId)
|
||||
.setPaymentAccountId(paymentAccountId)
|
||||
.build();
|
||||
return grpcStubs.tradesService.takeOffer(request);
|
||||
public List<OfferInfo> getMyOffersSortedByDate(String direction, String currencyCode) {
|
||||
return offersServiceRequest.getMyOffersSortedByDate(direction, currencyCode);
|
||||
}
|
||||
|
||||
public TradeInfo takeOffer(String offerId, String paymentAccountId) {
|
||||
var reply = getTakeOfferReply(offerId, paymentAccountId);
|
||||
if (reply.hasTrade())
|
||||
return reply.getTrade();
|
||||
else
|
||||
throw new IllegalStateException(reply.getFailureReason().getDescription());
|
||||
return tradesServiceRequest.takeOffer(offerId, paymentAccountId);
|
||||
}
|
||||
|
||||
public TradeInfo getTrade(String tradeId) {
|
||||
var request = GetTradeRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.build();
|
||||
return grpcStubs.tradesService.getTrade(request).getTrade();
|
||||
return tradesServiceRequest.getTrade(tradeId);
|
||||
}
|
||||
|
||||
public List<TradeInfo> getTrades() {
|
||||
var request = GetTradesRequest.newBuilder().build();
|
||||
return grpcStubs.tradesService.getTrades(request).getTradesList();
|
||||
public List<TradeInfo> getOpenTrades() {
|
||||
return tradesServiceRequest.getOpenTrades();
|
||||
}
|
||||
|
||||
public List<TradeInfo> getTradeHistory(GetTradesRequest.Category category) {
|
||||
return tradesServiceRequest.getTradeHistory(category);
|
||||
}
|
||||
|
||||
public void confirmPaymentStarted(String tradeId) {
|
||||
var request = ConfirmPaymentStartedRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.build();
|
||||
grpcStubs.tradesService.confirmPaymentStarted(request);
|
||||
tradesServiceRequest.confirmPaymentStarted(tradeId);
|
||||
}
|
||||
|
||||
public void confirmPaymentReceived(String tradeId) {
|
||||
var request = ConfirmPaymentReceivedRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.build();
|
||||
grpcStubs.tradesService.confirmPaymentReceived(request);
|
||||
}
|
||||
|
||||
public void keepFunds(String tradeId) {
|
||||
var request = KeepFundsRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.build();
|
||||
grpcStubs.tradesService.keepFunds(request);
|
||||
tradesServiceRequest.confirmPaymentReceived(tradeId);
|
||||
}
|
||||
|
||||
public void withdrawFunds(String tradeId, String address, String memo) {
|
||||
var request = WithdrawFundsRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.setAddress(address)
|
||||
.setMemo(memo)
|
||||
.build();
|
||||
grpcStubs.tradesService.withdrawFunds(request);
|
||||
tradesServiceRequest.withdrawFunds(tradeId, address, memo);
|
||||
}
|
||||
|
||||
public List<PaymentMethod> getPaymentMethods() {
|
||||
var request = GetPaymentMethodsRequest.newBuilder().build();
|
||||
return grpcStubs.paymentAccountsService.getPaymentMethods(request).getPaymentMethodsList();
|
||||
return paymentAccountsServiceRequest.getPaymentMethods();
|
||||
}
|
||||
|
||||
public String getPaymentAcctFormAsJson(String paymentMethodId) {
|
||||
var request = GetPaymentAccountFormRequest.newBuilder()
|
||||
.setPaymentMethodId(paymentMethodId)
|
||||
.build();
|
||||
return grpcStubs.paymentAccountsService.getPaymentAccountForm(request).getPaymentAccountFormJson();
|
||||
return paymentAccountsServiceRequest.getPaymentAcctFormAsJson(paymentMethodId);
|
||||
}
|
||||
|
||||
public PaymentAccount createPaymentAccount(String json) {
|
||||
var request = CreatePaymentAccountRequest.newBuilder()
|
||||
.setPaymentAccountForm(json)
|
||||
.build();
|
||||
return grpcStubs.paymentAccountsService.createPaymentAccount(request).getPaymentAccount();
|
||||
return paymentAccountsServiceRequest.createPaymentAccount(json);
|
||||
}
|
||||
|
||||
public List<PaymentAccount> getPaymentAccounts() {
|
||||
var request = GetPaymentAccountsRequest.newBuilder().build();
|
||||
return grpcStubs.paymentAccountsService.getPaymentAccounts(request).getPaymentAccountsList();
|
||||
return paymentAccountsServiceRequest.getPaymentAccounts();
|
||||
}
|
||||
|
||||
public PaymentAccount getPaymentAccount(String accountName) {
|
||||
return paymentAccountsServiceRequest.getPaymentAccount(accountName);
|
||||
}
|
||||
|
||||
public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
|
||||
String currencyCode,
|
||||
String address,
|
||||
boolean tradeInstant) {
|
||||
var request = CreateCryptoCurrencyPaymentAccountRequest.newBuilder()
|
||||
.setAccountName(accountName)
|
||||
.setCurrencyCode(currencyCode)
|
||||
.setAddress(address)
|
||||
.setTradeInstant(tradeInstant)
|
||||
.build();
|
||||
return grpcStubs.paymentAccountsService.createCryptoCurrencyPaymentAccount(request).getPaymentAccount();
|
||||
return paymentAccountsServiceRequest.createCryptoCurrencyPaymentAccount(accountName,
|
||||
currencyCode,
|
||||
address,
|
||||
tradeInstant);
|
||||
}
|
||||
|
||||
public List<PaymentMethod> getCryptoPaymentMethods() {
|
||||
var request = GetCryptoCurrencyPaymentMethodsRequest.newBuilder().build();
|
||||
return grpcStubs.paymentAccountsService.getCryptoCurrencyPaymentMethods(request).getPaymentMethodsList();
|
||||
return paymentAccountsServiceRequest.getCryptoPaymentMethods();
|
||||
}
|
||||
|
||||
public void lockWallet() {
|
||||
var request = LockWalletRequest.newBuilder().build();
|
||||
grpcStubs.walletsService.lockWallet(request);
|
||||
walletsServiceRequest.lockWallet();
|
||||
}
|
||||
|
||||
public void unlockWallet(String walletPassword, long timeout) {
|
||||
var request = UnlockWalletRequest.newBuilder()
|
||||
.setPassword(walletPassword)
|
||||
.setTimeout(timeout).build();
|
||||
grpcStubs.walletsService.unlockWallet(request);
|
||||
walletsServiceRequest.unlockWallet(walletPassword, timeout);
|
||||
}
|
||||
|
||||
public void removeWalletPassword(String walletPassword) {
|
||||
var request = RemoveWalletPasswordRequest.newBuilder()
|
||||
.setPassword(walletPassword).build();
|
||||
grpcStubs.walletsService.removeWalletPassword(request);
|
||||
walletsServiceRequest.removeWalletPassword(walletPassword);
|
||||
}
|
||||
|
||||
public void setWalletPassword(String walletPassword) {
|
||||
var request = SetWalletPasswordRequest.newBuilder()
|
||||
.setPassword(walletPassword).build();
|
||||
grpcStubs.walletsService.setWalletPassword(request);
|
||||
walletsServiceRequest.setWalletPassword(walletPassword);
|
||||
}
|
||||
|
||||
public void setWalletPassword(String oldWalletPassword, String newWalletPassword) {
|
||||
var request = SetWalletPasswordRequest.newBuilder()
|
||||
.setPassword(oldWalletPassword)
|
||||
.setNewPassword(newWalletPassword).build();
|
||||
grpcStubs.walletsService.setWalletPassword(request);
|
||||
walletsServiceRequest.setWalletPassword(oldWalletPassword, newWalletPassword);
|
||||
}
|
||||
|
||||
public void registerDisputeAgent(String disputeAgentType, String registrationKey) {
|
||||
|
@ -22,16 +22,19 @@ package bisq.cli;
|
||||
*/
|
||||
public enum Method {
|
||||
canceloffer,
|
||||
closetrade,
|
||||
confirmpaymentreceived,
|
||||
confirmpaymentstarted,
|
||||
createoffer,
|
||||
editoffer,
|
||||
createpaymentacct,
|
||||
createcryptopaymentacct,
|
||||
getaddressbalance,
|
||||
getbalance,
|
||||
getbtcprice,
|
||||
getfundingaddresses,
|
||||
getmyoffer,
|
||||
@Deprecated // Since 27-Dec-2021.
|
||||
getmyoffer, // Endpoint to be removed from future version. Use getoffer instead.
|
||||
getmyoffers,
|
||||
getoffer,
|
||||
getoffers,
|
||||
@ -39,10 +42,13 @@ public enum Method {
|
||||
getpaymentaccts,
|
||||
getpaymentmethods,
|
||||
gettrade,
|
||||
gettrades,
|
||||
failtrade,
|
||||
unfailtrade,
|
||||
gettransaction,
|
||||
gettxfeerate,
|
||||
getunusedbsqaddress,
|
||||
getversion,
|
||||
keepfunds,
|
||||
lockwallet,
|
||||
registerdisputeagent,
|
||||
removewalletpassword,
|
||||
|
@ -1,270 +0,0 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli;
|
||||
|
||||
import bisq.proto.grpc.AddressBalanceInfo;
|
||||
import bisq.proto.grpc.BalancesInfo;
|
||||
import bisq.proto.grpc.BtcBalanceInfo;
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
import bisq.proto.grpc.XmrBalanceInfo;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static bisq.cli.ColumnHeaderConstants.*;
|
||||
import static bisq.cli.CurrencyFormat.*;
|
||||
import static bisq.cli.DirectionFormat.directionFormat;
|
||||
import static bisq.cli.DirectionFormat.getLongestDirectionColWidth;
|
||||
import static com.google.common.base.Strings.padEnd;
|
||||
import static com.google.common.base.Strings.padStart;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Collections.max;
|
||||
import static java.util.Comparator.comparing;
|
||||
import static java.util.TimeZone.getTimeZone;
|
||||
|
||||
@VisibleForTesting
|
||||
public class TableFormat {
|
||||
|
||||
static final TimeZone TZ_UTC = getTimeZone("UTC");
|
||||
static final SimpleDateFormat DATE_FORMAT_ISO_8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||
|
||||
public static String formatAddressBalanceTbl(List<AddressBalanceInfo> addressBalanceInfo) {
|
||||
String headerFormatString = COL_HEADER_ADDRESS + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_AVAILABLE_BALANCE + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_CONFIRMATIONS + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_IS_USED_ADDRESS + COL_HEADER_DELIMITER + "\n";
|
||||
String headerLine = format(headerFormatString, "XMR");
|
||||
|
||||
String colDataFormat = "%-" + COL_HEADER_ADDRESS.length() + "s" // lt justify
|
||||
+ " %" + (COL_HEADER_AVAILABLE_BALANCE.length() - 1) + "s" // rt justify
|
||||
+ " %" + COL_HEADER_CONFIRMATIONS.length() + "d" // rt justify
|
||||
+ " %-" + COL_HEADER_IS_USED_ADDRESS.length() + "s"; // lt justify
|
||||
return headerLine
|
||||
+ addressBalanceInfo.stream()
|
||||
.map(info -> format(colDataFormat,
|
||||
info.getAddress(),
|
||||
formatSatoshis(info.getBalance()),
|
||||
info.getNumConfirmations(),
|
||||
info.getIsAddressUnused() ? "NO" : "YES"))
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
public static String formatBalancesTbls(BalancesInfo balancesInfo) {
|
||||
return "XMR" + "\n"
|
||||
+ formatBtcBalanceInfoTbl(balancesInfo.getBtc());
|
||||
}
|
||||
|
||||
public static String formatBtcBalanceInfoTbl(BtcBalanceInfo btcBalanceInfo) {
|
||||
String headerLine = COL_HEADER_AVAILABLE_BALANCE + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_RESERVED_BALANCE + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_TOTAL_AVAILABLE_BALANCE + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_LOCKED_BALANCE + COL_HEADER_DELIMITER + "\n";
|
||||
String colDataFormat = "%" + COL_HEADER_AVAILABLE_BALANCE.length() + "s" // rt justify
|
||||
+ " %" + (COL_HEADER_RESERVED_BALANCE.length() + 1) + "s" // rt justify
|
||||
+ " %" + (COL_HEADER_TOTAL_AVAILABLE_BALANCE.length() + 1) + "s" // rt justify
|
||||
+ " %" + (COL_HEADER_LOCKED_BALANCE.length() + 1) + "s"; // rt justify
|
||||
return headerLine + format(colDataFormat,
|
||||
formatSatoshis(btcBalanceInfo.getAvailableBalance()),
|
||||
formatSatoshis(btcBalanceInfo.getReservedBalance()),
|
||||
formatSatoshis(btcBalanceInfo.getTotalAvailableBalance()),
|
||||
formatSatoshis(btcBalanceInfo.getLockedBalance()));
|
||||
}
|
||||
|
||||
public static String formatXmrBalanceInfoTbl(XmrBalanceInfo xmrBalanceInfo) {
|
||||
String headerLine = COL_HEADER_BALANCE + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_AVAILABLE_BALANCE + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_LOCKED_BALANCE + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_RESERVED_OFFER_BALANCE + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_RESERVED_TRADE_BALANCE + COL_HEADER_DELIMITER + "\n";
|
||||
String colDataFormat = "%" + COL_HEADER_BALANCE.length() + "s" // rt justify
|
||||
+ " %" + (COL_HEADER_AVAILABLE_BALANCE.length() + 1) + "s" // rt justify
|
||||
+ " %" + (COL_HEADER_LOCKED_BALANCE.length() + 1) + "s" // rt justify
|
||||
+ " %" + (COL_HEADER_RESERVED_BALANCE.length() + 1) + "s" // rt justify
|
||||
+ " %" + (COL_HEADER_TOTAL_AVAILABLE_BALANCE.length() + 1) + "s"; // rt justify
|
||||
return headerLine + format(colDataFormat,
|
||||
formatSatoshis(xmrBalanceInfo.getUnlockedBalance() + xmrBalanceInfo.getLockedBalance()), // total balance
|
||||
formatSatoshis(xmrBalanceInfo.getUnlockedBalance()),
|
||||
formatSatoshis(xmrBalanceInfo.getReservedOfferBalance()),
|
||||
formatSatoshis(xmrBalanceInfo.getReservedTradeBalance()));
|
||||
}
|
||||
|
||||
public static String formatPaymentAcctTbl(List<PaymentAccount> paymentAccounts) {
|
||||
// Some column values might be longer than header, so we need to calculate them.
|
||||
int nameColWidth = getLongestColumnSize(
|
||||
COL_HEADER_NAME.length(),
|
||||
paymentAccounts.stream().map(PaymentAccount::getAccountName)
|
||||
.collect(Collectors.toList()));
|
||||
int paymentMethodColWidth = getLongestColumnSize(
|
||||
COL_HEADER_PAYMENT_METHOD.length(),
|
||||
paymentAccounts.stream().map(a -> a.getPaymentMethod().getId())
|
||||
.collect(Collectors.toList()));
|
||||
String headerLine = padEnd(COL_HEADER_NAME, nameColWidth, ' ') + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_CURRENCY + COL_HEADER_DELIMITER
|
||||
+ padEnd(COL_HEADER_PAYMENT_METHOD, paymentMethodColWidth, ' ') + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_UUID + COL_HEADER_DELIMITER + "\n";
|
||||
String colDataFormat = "%-" + nameColWidth + "s" // left justify
|
||||
+ " %-" + COL_HEADER_CURRENCY.length() + "s" // left justify
|
||||
+ " %-" + paymentMethodColWidth + "s" // left justify
|
||||
+ " %-" + COL_HEADER_UUID.length() + "s"; // left justify
|
||||
return headerLine
|
||||
+ paymentAccounts.stream()
|
||||
.map(a -> format(colDataFormat,
|
||||
a.getAccountName(),
|
||||
a.getSelectedTradeCurrency().getCode(),
|
||||
a.getPaymentMethod().getId(),
|
||||
a.getId()))
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
public static String formatOfferTable(List<OfferInfo> offers, String currencyCode) {
|
||||
if (offers == null || offers.isEmpty())
|
||||
throw new IllegalArgumentException(format("%s offers argument is empty", currencyCode.toLowerCase()));
|
||||
|
||||
String baseCurrencyCode = offers.get(0).getBaseCurrencyCode();
|
||||
return baseCurrencyCode.equalsIgnoreCase("XMR")
|
||||
? formatFiatOfferTable(offers, currencyCode)
|
||||
: formatCryptoCurrencyOfferTable(offers, baseCurrencyCode);
|
||||
}
|
||||
|
||||
private static String formatFiatOfferTable(List<OfferInfo> offers, String fiatCurrencyCode) {
|
||||
// Some column values might be longer than header, so we need to calculate them.
|
||||
int amountColWith = getLongestAmountColWidth(offers);
|
||||
int volumeColWidth = getLongestVolumeColWidth(offers);
|
||||
int paymentMethodColWidth = getLongestPaymentMethodColWidth(offers);
|
||||
String headersFormat = COL_HEADER_DIRECTION + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_PRICE + COL_HEADER_DELIMITER // includes %s -> fiatCurrencyCode
|
||||
+ padStart(COL_HEADER_AMOUNT, amountColWith, ' ') + COL_HEADER_DELIMITER
|
||||
// COL_HEADER_VOLUME includes %s -> fiatCurrencyCode
|
||||
+ padStart(COL_HEADER_VOLUME, volumeColWidth, ' ') + COL_HEADER_DELIMITER
|
||||
+ padEnd(COL_HEADER_PAYMENT_METHOD, paymentMethodColWidth, ' ') + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_CREATION_DATE + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_UUID.trim() + "%n";
|
||||
String headerLine = format(headersFormat,
|
||||
fiatCurrencyCode.toUpperCase(),
|
||||
fiatCurrencyCode.toUpperCase());
|
||||
String colDataFormat = "%-" + (COL_HEADER_DIRECTION.length() + COL_HEADER_DELIMITER.length()) + "s"
|
||||
+ "%" + (COL_HEADER_PRICE.length() - 1) + "s"
|
||||
+ " %" + amountColWith + "s"
|
||||
+ " %" + (volumeColWidth - 1) + "s"
|
||||
+ " %-" + paymentMethodColWidth + "s"
|
||||
+ " %-" + (COL_HEADER_CREATION_DATE.length()) + "s"
|
||||
+ " %-" + COL_HEADER_UUID.length() + "s";
|
||||
return headerLine
|
||||
+ offers.stream()
|
||||
.map(o -> format(colDataFormat,
|
||||
o.getDirection(),
|
||||
formatPrice(o.getPrice()),
|
||||
formatAmountRange(o.getMinAmount(), o.getAmount()),
|
||||
formatVolumeRange(o.getMinVolume(), o.getVolume()),
|
||||
o.getPaymentMethodShortName(),
|
||||
formatTimestamp(o.getDate()),
|
||||
o.getId()))
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
private static String formatCryptoCurrencyOfferTable(List<OfferInfo> offers, String cryptoCurrencyCode) {
|
||||
// Some column values might be longer than header, so we need to calculate them.
|
||||
int directionColWidth = getLongestDirectionColWidth(offers);
|
||||
int amountColWith = getLongestAmountColWidth(offers);
|
||||
int volumeColWidth = getLongestCryptoCurrencyVolumeColWidth(offers);
|
||||
int paymentMethodColWidth = getLongestPaymentMethodColWidth(offers);
|
||||
// TODO use memoize function to avoid duplicate the formatting done above?
|
||||
String headersFormat = padEnd(COL_HEADER_DIRECTION, directionColWidth, ' ') + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_PRICE_OF_ALTCOIN + COL_HEADER_DELIMITER // includes %s -> cryptoCurrencyCode
|
||||
+ padStart(COL_HEADER_AMOUNT, amountColWith, ' ') + COL_HEADER_DELIMITER
|
||||
// COL_HEADER_VOLUME includes %s -> cryptoCurrencyCode
|
||||
+ padStart(COL_HEADER_VOLUME, volumeColWidth, ' ') + COL_HEADER_DELIMITER
|
||||
+ padEnd(COL_HEADER_PAYMENT_METHOD, paymentMethodColWidth, ' ') + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_CREATION_DATE + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_UUID.trim() + "%n";
|
||||
String headerLine = format(headersFormat,
|
||||
cryptoCurrencyCode.toUpperCase(),
|
||||
cryptoCurrencyCode.toUpperCase());
|
||||
String colDataFormat = "%-" + directionColWidth + "s"
|
||||
+ "%" + (COL_HEADER_PRICE_OF_ALTCOIN.length() + 1) + "s"
|
||||
+ " %" + amountColWith + "s"
|
||||
+ " %" + (volumeColWidth - 1) + "s"
|
||||
+ " %-" + paymentMethodColWidth + "s"
|
||||
+ " %-" + (COL_HEADER_CREATION_DATE.length()) + "s"
|
||||
+ " %-" + COL_HEADER_UUID.length() + "s";
|
||||
return headerLine
|
||||
+ offers.stream()
|
||||
.map(o -> format(colDataFormat,
|
||||
directionFormat.apply(o),
|
||||
formatCryptoCurrencyPrice(o.getPrice()),
|
||||
formatAmountRange(o.getMinAmount(), o.getAmount()),
|
||||
formatCryptoCurrencyVolumeRange(o.getMinVolume(), o.getVolume()),
|
||||
o.getPaymentMethodShortName(),
|
||||
formatTimestamp(o.getDate()),
|
||||
o.getId()))
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
private static int getLongestPaymentMethodColWidth(List<OfferInfo> offers) {
|
||||
return getLongestColumnSize(
|
||||
COL_HEADER_PAYMENT_METHOD.length(),
|
||||
offers.stream()
|
||||
.map(OfferInfo::getPaymentMethodShortName)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private static int getLongestAmountColWidth(List<OfferInfo> offers) {
|
||||
return getLongestColumnSize(
|
||||
COL_HEADER_AMOUNT.length(),
|
||||
offers.stream()
|
||||
.map(o -> formatAmountRange(o.getMinAmount(), o.getAmount()))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private static int getLongestVolumeColWidth(List<OfferInfo> offers) {
|
||||
// Pad this col width by 1 space.
|
||||
return 1 + getLongestColumnSize(
|
||||
COL_HEADER_VOLUME.length(),
|
||||
offers.stream()
|
||||
.map(o -> formatVolumeRange(o.getMinVolume(), o.getVolume()))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private static int getLongestCryptoCurrencyVolumeColWidth(List<OfferInfo> offers) {
|
||||
// Pad this col width by 1 space.
|
||||
return 1 + getLongestColumnSize(
|
||||
COL_HEADER_VOLUME.length(),
|
||||
offers.stream()
|
||||
.map(o -> formatCryptoCurrencyVolumeRange(o.getMinVolume(), o.getVolume()))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
// Return size of the longest string value, or the header.len, whichever is greater.
|
||||
private static int getLongestColumnSize(int headerLength, List<String> strings) {
|
||||
int longest = max(strings, comparing(String::length)).length();
|
||||
return Math.max(longest, headerLength);
|
||||
}
|
||||
|
||||
private static String formatTimestamp(long timestamp) {
|
||||
DATE_FORMAT_ISO_8601.setTimeZone(TZ_UTC);
|
||||
return DATE_FORMAT_ISO_8601.format(new Date(timestamp));
|
||||
}
|
||||
}
|
@ -1,177 +0,0 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli;
|
||||
|
||||
import bisq.core.util.ParsingUtils;
|
||||
|
||||
import bisq.proto.grpc.ContractInfo;
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static bisq.cli.ColumnHeaderConstants.*;
|
||||
import static bisq.cli.CurrencyFormat.*;
|
||||
import static com.google.common.base.Strings.padEnd;
|
||||
|
||||
@VisibleForTesting
|
||||
public class TradeFormat {
|
||||
|
||||
private static final String YES = "YES";
|
||||
private static final String NO = "NO";
|
||||
|
||||
// TODO add String format(List<TradeInfo> trades)
|
||||
|
||||
@VisibleForTesting
|
||||
public static String format(TradeInfo tradeInfo) {
|
||||
// Some column values might be longer than header, so we need to calculate them.
|
||||
int shortIdColWidth = Math.max(COL_HEADER_TRADE_SHORT_ID.length(), tradeInfo.getShortId().length());
|
||||
int roleColWidth = Math.max(COL_HEADER_TRADE_ROLE.length(), tradeInfo.getRole().length());
|
||||
|
||||
// We only show taker fee under its header when user is the taker.
|
||||
boolean isTaker = tradeInfo.getRole().toLowerCase().contains("taker");
|
||||
Supplier<String> makerFeeHeader = () -> !isTaker ?
|
||||
COL_HEADER_TRADE_MAKER_FEE + COL_HEADER_DELIMITER
|
||||
: "";
|
||||
Supplier<String> makerFeeHeaderSpec = () -> !isTaker ?
|
||||
"%" + (COL_HEADER_TRADE_MAKER_FEE.length() + 2) + "s"
|
||||
: "";
|
||||
Supplier<String> takerFeeHeader = () -> isTaker ?
|
||||
COL_HEADER_TRADE_TAKER_FEE + COL_HEADER_DELIMITER
|
||||
: "";
|
||||
Supplier<String> takerFeeHeaderSpec = () -> isTaker ?
|
||||
"%" + (COL_HEADER_TRADE_TAKER_FEE.length() + 2) + "s"
|
||||
: "";
|
||||
|
||||
|
||||
String headersFormat = padEnd(COL_HEADER_TRADE_SHORT_ID, shortIdColWidth, ' ') + COL_HEADER_DELIMITER
|
||||
+ padEnd(COL_HEADER_TRADE_ROLE, roleColWidth, ' ') + COL_HEADER_DELIMITER
|
||||
+ priceHeader.apply(tradeInfo) + COL_HEADER_DELIMITER // includes %s -> currencyCode
|
||||
+ padEnd(COL_HEADER_TRADE_AMOUNT, 12, ' ') + COL_HEADER_DELIMITER
|
||||
+ padEnd(COL_HEADER_TRADE_TX_FEE, 12, ' ') + COL_HEADER_DELIMITER
|
||||
+ makerFeeHeader.get()
|
||||
// maker or taker fee header, not both
|
||||
+ takerFeeHeader.get()
|
||||
+ COL_HEADER_TRADE_DEPOSIT_PUBLISHED + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_TRADE_DEPOSIT_CONFIRMED + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_TRADE_BUYER_COST + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_TRADE_PAYMENT_SENT + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_TRADE_PAYMENT_RECEIVED + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_TRADE_PAYOUT_PUBLISHED + COL_HEADER_DELIMITER
|
||||
+ COL_HEADER_TRADE_WITHDRAWN + COL_HEADER_DELIMITER
|
||||
+ "%n";
|
||||
|
||||
String counterCurrencyCode = tradeInfo.getOffer().getCounterCurrencyCode();
|
||||
String baseCurrencyCode = tradeInfo.getOffer().getBaseCurrencyCode();
|
||||
|
||||
String headerLine = String.format(headersFormat,
|
||||
/* COL_HEADER_PRICE */ priceHeaderCurrencyCode.apply(tradeInfo),
|
||||
/* COL_HEADER_TRADE_AMOUNT */ baseCurrencyCode,
|
||||
/* COL_HEADER_TRADE_(M||T)AKER_FEE */ makerTakerFeeHeaderCurrencyCode.apply(tradeInfo, isTaker),
|
||||
/* COL_HEADER_TRADE_BUYER_COST */ counterCurrencyCode,
|
||||
/* COL_HEADER_TRADE_PAYMENT_SENT */ paymentStatusHeaderCurrencyCode.apply(tradeInfo),
|
||||
/* COL_HEADER_TRADE_PAYMENT_RECEIVED */ paymentStatusHeaderCurrencyCode.apply(tradeInfo));
|
||||
|
||||
String colDataFormat = "%-" + shortIdColWidth + "s" // lt justify
|
||||
+ " %-" + (roleColWidth + COL_HEADER_DELIMITER.length()) + "s" // left
|
||||
+ "%" + (COL_HEADER_PRICE.length() - 1) + "s" // rt justify
|
||||
+ "%" + (COL_HEADER_TRADE_AMOUNT.length() + 1) + "s" // rt justify
|
||||
+ "%" + (COL_HEADER_TRADE_TX_FEE.length() + 1) + "s" // rt justify
|
||||
+ makerFeeHeaderSpec.get() // rt justify
|
||||
// OR (one of them is an empty string)
|
||||
+ takerFeeHeaderSpec.get() // rt justify
|
||||
+ " %-" + COL_HEADER_TRADE_DEPOSIT_PUBLISHED.length() + "s" // lt justify
|
||||
+ " %-" + COL_HEADER_TRADE_DEPOSIT_CONFIRMED.length() + "s" // lt justify
|
||||
+ "%" + (COL_HEADER_TRADE_BUYER_COST.length() + 1) + "s" // rt justify
|
||||
+ " %-" + (COL_HEADER_TRADE_PAYMENT_SENT.length() - 1) + "s" // left
|
||||
+ " %-" + (COL_HEADER_TRADE_PAYMENT_RECEIVED.length() - 1) + "s" // left
|
||||
+ " %-" + COL_HEADER_TRADE_PAYOUT_PUBLISHED.length() + "s" // lt justify
|
||||
+ " %-" + (COL_HEADER_TRADE_WITHDRAWN.length() + 2) + "s";
|
||||
|
||||
return headerLine + formatTradeData(colDataFormat, tradeInfo, isTaker);
|
||||
}
|
||||
|
||||
private static String formatTradeData(String format,
|
||||
TradeInfo tradeInfo,
|
||||
boolean isTaker) {
|
||||
return String.format(format,
|
||||
tradeInfo.getShortId(),
|
||||
tradeInfo.getRole(),
|
||||
priceFormat.apply(tradeInfo),
|
||||
amountFormat.apply(tradeInfo),
|
||||
makerTakerMinerTxFeeFormat.apply(tradeInfo, isTaker),
|
||||
makerTakerFeeFormat.apply(tradeInfo, isTaker),
|
||||
tradeInfo.getIsDepositPublished() ? YES : NO,
|
||||
tradeInfo.getIsDepositUnlocked() ? YES : NO,
|
||||
tradeCostFormat.apply(tradeInfo),
|
||||
tradeInfo.getIsPaymentSent() ? YES : NO,
|
||||
tradeInfo.getIsPaymentReceived() ? YES : NO,
|
||||
tradeInfo.getIsPayoutPublished() ? YES : NO,
|
||||
tradeInfo.getIsWithdrawn() ? YES : NO);
|
||||
}
|
||||
|
||||
private static final Function<TradeInfo, String> priceHeader = (t) ->
|
||||
t.getOffer().getBaseCurrencyCode().equals("XMR")
|
||||
? COL_HEADER_PRICE
|
||||
: COL_HEADER_PRICE_OF_ALTCOIN;
|
||||
|
||||
private static final Function<TradeInfo, String> priceHeaderCurrencyCode = (t) ->
|
||||
t.getOffer().getBaseCurrencyCode().equals("XMR")
|
||||
? t.getOffer().getCounterCurrencyCode()
|
||||
: t.getOffer().getBaseCurrencyCode();
|
||||
|
||||
private static final BiFunction<TradeInfo, Boolean, String> makerTakerFeeHeaderCurrencyCode = (t, isTaker) -> {
|
||||
return "XMR";
|
||||
};
|
||||
|
||||
private static final Function<TradeInfo, String> paymentStatusHeaderCurrencyCode = (t) ->
|
||||
t.getOffer().getBaseCurrencyCode().equals("XMR")
|
||||
? t.getOffer().getCounterCurrencyCode()
|
||||
: t.getOffer().getBaseCurrencyCode();
|
||||
|
||||
private static final Function<TradeInfo, String> priceFormat = (t) ->
|
||||
t.getOffer().getBaseCurrencyCode().equals("XMR")
|
||||
? formatPrice(t.getTradePrice())
|
||||
: formatCryptoCurrencyPrice(t.getOffer().getPrice());
|
||||
|
||||
private static final Function<TradeInfo, String> amountFormat = (t) ->
|
||||
t.getOffer().getBaseCurrencyCode().equals("XMR")
|
||||
? formatXmr(ParsingUtils.centinerosToAtomicUnits(t.getTradeAmountAsLong()))
|
||||
: formatCryptoCurrencyOfferVolume(t.getOffer().getVolume());
|
||||
|
||||
private static final BiFunction<TradeInfo, Boolean, String> makerTakerMinerTxFeeFormat = (t, isTaker) -> {
|
||||
if (isTaker) {
|
||||
return formatSatoshis(t.getTxFeeAsLong());
|
||||
} else {
|
||||
return formatSatoshis(t.getOffer().getTxFee());
|
||||
}
|
||||
};
|
||||
|
||||
private static final BiFunction<TradeInfo, Boolean, String> makerTakerFeeFormat = (t, isTaker) -> {
|
||||
return formatXmr(ParsingUtils.centinerosToAtomicUnits(t.getTakerFeeAsLong()));
|
||||
};
|
||||
|
||||
private static final Function<TradeInfo, String> tradeCostFormat = (t) ->
|
||||
t.getOffer().getBaseCurrencyCode().equals("XMR")
|
||||
? formatOfferVolume(t.getOffer().getVolume())
|
||||
: formatXmr(ParsingUtils.centinerosToAtomicUnits(t.getTradeAmountAsLong()));
|
||||
|
||||
}
|
@ -24,11 +24,14 @@ import joptsimple.OptionSpec;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import static bisq.cli.opts.OptLabel.OPT_HELP;
|
||||
import static java.lang.String.format;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
abstract class AbstractMethodOptionParser implements MethodOpts {
|
||||
|
||||
// The full command line args passed to CliMain.main(String[] args).
|
||||
@ -37,7 +40,7 @@ abstract class AbstractMethodOptionParser implements MethodOpts {
|
||||
|
||||
protected final OptionParser parser = new OptionParser();
|
||||
|
||||
// The help option for a specific api method, e.g., takeoffer -help.
|
||||
// The help option for a specific api method, e.g., takeoffer --help.
|
||||
protected final OptionSpec<Void> helpOpt = parser.accepts(OPT_HELP, "Print method help").forHelp();
|
||||
|
||||
@Getter
|
||||
@ -52,7 +55,6 @@ abstract class AbstractMethodOptionParser implements MethodOpts {
|
||||
public AbstractMethodOptionParser parse() {
|
||||
try {
|
||||
options = parser.parse(new ArgumentList(args).getMethodArguments());
|
||||
//noinspection unchecked
|
||||
nonOptionArguments = (List<String>) options.nonOptionArguments();
|
||||
return this;
|
||||
} catch (OptionException ex) {
|
||||
@ -64,6 +66,17 @@ abstract class AbstractMethodOptionParser implements MethodOpts {
|
||||
return options.has(helpOpt);
|
||||
}
|
||||
|
||||
protected void verifyStringIsValidDouble(String string) {
|
||||
try {
|
||||
Double.valueOf(string);
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException(format("%s is not a number", string));
|
||||
}
|
||||
}
|
||||
|
||||
protected final Predicate<OptionSpec<String>> valueNotSpecified = (opt) ->
|
||||
!options.hasArgument(opt) || options.valueOf(opt).isEmpty();
|
||||
|
||||
private final Function<OptionException, String> cliExceptionMessageStyle = (ex) -> {
|
||||
if (ex.getMessage() == null)
|
||||
return null;
|
||||
|
@ -18,14 +18,7 @@
|
||||
package bisq.cli.opts;
|
||||
|
||||
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import static bisq.cli.opts.OptLabel.OPT_OFFER_ID;
|
||||
|
||||
public class CancelOfferOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
||||
|
||||
final OptionSpec<String> offerIdOpt = parser.accepts(OPT_OFFER_ID, "id of offer to cancel")
|
||||
.withRequiredArg();
|
||||
public class CancelOfferOptionParser extends OfferIdOptionParser implements MethodOpts {
|
||||
|
||||
public CancelOfferOptionParser(String[] args) {
|
||||
super(args);
|
||||
@ -34,12 +27,7 @@ public class CancelOfferOptionParser extends AbstractMethodOptionParser implemen
|
||||
public CancelOfferOptionParser parse() {
|
||||
super.parse();
|
||||
|
||||
// Short circuit opt validation if user just wants help.
|
||||
if (options.has(helpOpt))
|
||||
return this;
|
||||
|
||||
if (!options.has(offerIdOpt) || options.valueOf(offerIdOpt).isEmpty())
|
||||
throw new IllegalArgumentException("no offer id specified");
|
||||
// Super class will short-circuit parsing if help option is present.
|
||||
|
||||
return this;
|
||||
}
|
||||
|
@ -20,20 +20,22 @@ package bisq.cli.opts;
|
||||
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import static bisq.cli.CryptoCurrencyUtil.apiDoesSupportCryptoCurrency;
|
||||
import static bisq.cli.opts.OptLabel.OPT_ACCOUNT_NAME;
|
||||
import static bisq.cli.opts.OptLabel.OPT_ADDRESS;
|
||||
import static bisq.cli.opts.OptLabel.OPT_CURRENCY_CODE;
|
||||
import static bisq.cli.opts.OptLabel.OPT_TRADE_INSTANT;
|
||||
import static java.lang.String.format;
|
||||
|
||||
public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
||||
|
||||
final OptionSpec<String> accountNameOpt = parser.accepts(OPT_ACCOUNT_NAME, "crypto currency account name")
|
||||
.withRequiredArg();
|
||||
|
||||
final OptionSpec<String> currencyCodeOpt = parser.accepts(OPT_CURRENCY_CODE, "crypto currency code")
|
||||
final OptionSpec<String> currencyCodeOpt = parser.accepts(OPT_CURRENCY_CODE, "crypto currency code (xmr)")
|
||||
.withRequiredArg();
|
||||
|
||||
final OptionSpec<String> addressOpt = parser.accepts(OPT_ADDRESS, "address")
|
||||
final OptionSpec<String> addressOpt = parser.accepts(OPT_ADDRESS, "altcoin address")
|
||||
.withRequiredArg();
|
||||
|
||||
final OptionSpec<Boolean> tradeInstantOpt = parser.accepts(OPT_TRADE_INSTANT, "create trade instant account")
|
||||
@ -54,9 +56,19 @@ public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodO
|
||||
|
||||
if (!options.has(accountNameOpt) || options.valueOf(accountNameOpt).isEmpty())
|
||||
throw new IllegalArgumentException("no payment account name specified");
|
||||
|
||||
|
||||
if (!options.has(currencyCodeOpt) || options.valueOf(currencyCodeOpt).isEmpty())
|
||||
throw new IllegalArgumentException("no currency code specified");
|
||||
|
||||
String cryptoCurrencyCode = options.valueOf(currencyCodeOpt);
|
||||
if (!apiDoesSupportCryptoCurrency(cryptoCurrencyCode))
|
||||
throw new IllegalArgumentException(format("api does not support %s payment accounts",
|
||||
cryptoCurrencyCode.toLowerCase()));
|
||||
|
||||
if (!options.has(addressOpt) || options.valueOf(addressOpt).isEmpty())
|
||||
throw new IllegalArgumentException(format("no %s address specified",
|
||||
cryptoCurrencyCode.toLowerCase()));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -20,22 +20,20 @@ package bisq.cli.opts;
|
||||
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static bisq.cli.opts.OptLabel.*;
|
||||
import static joptsimple.internal.Strings.EMPTY;
|
||||
|
||||
public class CreateOfferOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
||||
|
||||
final OptionSpec<String> paymentAccountIdOpt = parser.accepts(OPT_PAYMENT_ACCOUNT,
|
||||
"id of payment account used for offer")
|
||||
final OptionSpec<String> paymentAccountIdOpt = parser.accepts(OPT_PAYMENT_ACCOUNT_ID,
|
||||
"id of payment account used for offer")
|
||||
.withRequiredArg()
|
||||
.defaultsTo(EMPTY);
|
||||
|
||||
final OptionSpec<String> directionOpt = parser.accepts(OPT_DIRECTION, "offer direction (buy|sell)")
|
||||
.withRequiredArg();
|
||||
|
||||
final OptionSpec<String> currencyCodeOpt = parser.accepts(OPT_CURRENCY_CODE, "currency code (eur|usd|...)")
|
||||
final OptionSpec<String> currencyCodeOpt = parser.accepts(OPT_CURRENCY_CODE, "currency code (xmr|eur|usd|...)")
|
||||
.withRequiredArg();
|
||||
|
||||
final OptionSpec<String> amountOpt = parser.accepts(OPT_AMOUNT, "amount of btc to buy or sell")
|
||||
@ -44,7 +42,7 @@ public class CreateOfferOptionParser extends AbstractMethodOptionParser implemen
|
||||
final OptionSpec<String> minAmountOpt = parser.accepts(OPT_MIN_AMOUNT, "minimum amount of btc to buy or sell")
|
||||
.withOptionalArg();
|
||||
|
||||
final OptionSpec<String> mktPriceMarginOpt = parser.accepts(OPT_MKT_PRICE_MARGIN, "market btc price margin (%)")
|
||||
final OptionSpec<String> mktPriceMarginPctOpt = parser.accepts(OPT_MKT_PRICE_MARGIN, "market btc price margin (%)")
|
||||
.withOptionalArg()
|
||||
.defaultsTo("0.00");
|
||||
|
||||
@ -52,13 +50,14 @@ public class CreateOfferOptionParser extends AbstractMethodOptionParser implemen
|
||||
.withOptionalArg()
|
||||
.defaultsTo("0");
|
||||
|
||||
final OptionSpec<String> securityDepositOpt = parser.accepts(OPT_SECURITY_DEPOSIT, "maker security deposit (%)")
|
||||
final OptionSpec<String> securityDepositPctOpt = parser.accepts(OPT_SECURITY_DEPOSIT, "maker security deposit (%)")
|
||||
.withRequiredArg();
|
||||
|
||||
public CreateOfferOptionParser(String[] args) {
|
||||
super(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CreateOfferOptionParser parse() {
|
||||
super.parse();
|
||||
|
||||
@ -66,9 +65,6 @@ public class CreateOfferOptionParser extends AbstractMethodOptionParser implemen
|
||||
if (options.has(helpOpt))
|
||||
return this;
|
||||
|
||||
if (!options.has(paymentAccountIdOpt) || options.valueOf(paymentAccountIdOpt).isEmpty())
|
||||
throw new IllegalArgumentException("no payment account id specified");
|
||||
|
||||
if (!options.has(directionOpt) || options.valueOf(directionOpt).isEmpty())
|
||||
throw new IllegalArgumentException("no direction (buy|sell) specified");
|
||||
|
||||
@ -78,17 +74,27 @@ public class CreateOfferOptionParser extends AbstractMethodOptionParser implemen
|
||||
if (!options.has(amountOpt) || options.valueOf(amountOpt).isEmpty())
|
||||
throw new IllegalArgumentException("no btc amount specified");
|
||||
|
||||
if (!options.has(mktPriceMarginOpt) && !options.has(fixedPriceOpt))
|
||||
if (!options.has(paymentAccountIdOpt) || options.valueOf(paymentAccountIdOpt).isEmpty())
|
||||
throw new IllegalArgumentException("no payment account id specified");
|
||||
|
||||
if (!options.has(mktPriceMarginPctOpt) && !options.has(fixedPriceOpt))
|
||||
throw new IllegalArgumentException("no market price margin or fixed price specified");
|
||||
|
||||
if (options.has(mktPriceMarginOpt) && options.valueOf(mktPriceMarginOpt).isEmpty())
|
||||
throw new IllegalArgumentException("no market price margin specified");
|
||||
if (options.has(mktPriceMarginPctOpt)) {
|
||||
var mktPriceMarginPctString = options.valueOf(mktPriceMarginPctOpt);
|
||||
if (mktPriceMarginPctString.isEmpty())
|
||||
throw new IllegalArgumentException("no market price margin specified");
|
||||
else
|
||||
verifyStringIsValidDouble(mktPriceMarginPctString);
|
||||
}
|
||||
|
||||
if (options.has(fixedPriceOpt) && options.valueOf(fixedPriceOpt).isEmpty())
|
||||
throw new IllegalArgumentException("no fixed price specified");
|
||||
|
||||
if (!options.has(securityDepositOpt) || options.valueOf(securityDepositOpt).isEmpty())
|
||||
if (!options.has(securityDepositPctOpt) || options.valueOf(securityDepositPctOpt).isEmpty())
|
||||
throw new IllegalArgumentException("no security deposit specified");
|
||||
else
|
||||
verifyStringIsValidDouble(options.valueOf(securityDepositPctOpt));
|
||||
|
||||
return this;
|
||||
}
|
||||
@ -114,23 +120,18 @@ public class CreateOfferOptionParser extends AbstractMethodOptionParser implemen
|
||||
}
|
||||
|
||||
public boolean isUsingMktPriceMargin() {
|
||||
return options.has(mktPriceMarginOpt);
|
||||
return options.has(mktPriceMarginPctOpt);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public String getMktPriceMargin() {
|
||||
return isUsingMktPriceMargin() ? options.valueOf(mktPriceMarginOpt) : "0.00";
|
||||
}
|
||||
|
||||
public BigDecimal getMktPriceMarginAsBigDecimal() {
|
||||
return isUsingMktPriceMargin() ? new BigDecimal(options.valueOf(mktPriceMarginOpt)) : BigDecimal.ZERO;
|
||||
public double getMktPriceMarginPct() {
|
||||
return isUsingMktPriceMargin() ? Double.parseDouble(options.valueOf(mktPriceMarginPctOpt)) : 0.00d;
|
||||
}
|
||||
|
||||
public String getFixedPrice() {
|
||||
return options.has(fixedPriceOpt) ? options.valueOf(fixedPriceOpt) : "0.00";
|
||||
}
|
||||
|
||||
public String getSecurityDeposit() {
|
||||
return options.valueOf(securityDepositOpt);
|
||||
public double getSecurityDepositPct() {
|
||||
return Double.valueOf(options.valueOf(securityDepositPctOpt));
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ import static java.lang.String.format;
|
||||
public class CreatePaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
||||
|
||||
final OptionSpec<String> paymentAcctFormPathOpt = parser.accepts(OPT_PAYMENT_ACCOUNT_FORM,
|
||||
"path to json payment account form")
|
||||
"path to json payment account form")
|
||||
.withRequiredArg();
|
||||
|
||||
public CreatePaymentAcctOptionParser(String[] args) {
|
||||
|
@ -28,7 +28,7 @@ public class GetOffersOptionParser extends AbstractMethodOptionParser implements
|
||||
final OptionSpec<String> directionOpt = parser.accepts(OPT_DIRECTION, "offer direction (buy|sell)")
|
||||
.withRequiredArg();
|
||||
|
||||
final OptionSpec<String> currencyCodeOpt = parser.accepts(OPT_CURRENCY_CODE, "currency code (eur|usd|...)")
|
||||
final OptionSpec<String> currencyCodeOpt = parser.accepts(OPT_CURRENCY_CODE, "currency code (xmr|eur|usd|...)")
|
||||
.withRequiredArg();
|
||||
|
||||
public GetOffersOptionParser(String[] args) {
|
||||
|
@ -25,7 +25,7 @@ import static bisq.cli.opts.OptLabel.OPT_PAYMENT_METHOD_ID;
|
||||
public class GetPaymentAcctFormOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
||||
|
||||
final OptionSpec<String> paymentMethodIdOpt = parser.accepts(OPT_PAYMENT_METHOD_ID,
|
||||
"id of payment method type used by a payment account")
|
||||
"id of payment method type used by a payment account")
|
||||
.withRequiredArg();
|
||||
|
||||
public GetPaymentAcctFormOptionParser(String[] args) {
|
||||
|
84
cli/src/main/java/bisq/cli/opts/GetTradesOptionParser.java
Normal file
84
cli/src/main/java/bisq/cli/opts/GetTradesOptionParser.java
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.opts;
|
||||
|
||||
|
||||
import bisq.proto.grpc.GetTradesRequest;
|
||||
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static bisq.cli.opts.OptLabel.OPT_CATEGORY;
|
||||
import static bisq.proto.grpc.GetTradesRequest.Category.CLOSED;
|
||||
import static bisq.proto.grpc.GetTradesRequest.Category.FAILED;
|
||||
import static bisq.proto.grpc.GetTradesRequest.Category.OPEN;
|
||||
import static java.util.Arrays.stream;
|
||||
|
||||
public class GetTradesOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
||||
|
||||
// Map valid CLI option values to gRPC request parameters.
|
||||
private enum CATEGORY {
|
||||
// Lower case enum fits CLI method and parameter style.
|
||||
open(OPEN),
|
||||
closed(CLOSED),
|
||||
failed(FAILED);
|
||||
|
||||
private final GetTradesRequest.Category grpcRequestCategory;
|
||||
|
||||
CATEGORY(GetTradesRequest.Category grpcRequestCategory) {
|
||||
this.grpcRequestCategory = grpcRequestCategory;
|
||||
}
|
||||
}
|
||||
|
||||
final OptionSpec<String> categoryOpt = parser.accepts(OPT_CATEGORY,
|
||||
"category of trades (open|closed|failed)")
|
||||
.withRequiredArg()
|
||||
.defaultsTo(CATEGORY.open.name());
|
||||
|
||||
private final Predicate<String> isValidCategory = (c) ->
|
||||
stream(CATEGORY.values()).anyMatch(v -> v.name().equalsIgnoreCase(c));
|
||||
|
||||
public GetTradesOptionParser(String[] args) {
|
||||
super(args);
|
||||
}
|
||||
|
||||
public GetTradesOptionParser parse() {
|
||||
super.parse();
|
||||
|
||||
// Short circuit opt validation if user just wants help.
|
||||
if (options.has(helpOpt))
|
||||
return this;
|
||||
|
||||
if (options.has(categoryOpt)) {
|
||||
String category = options.valueOf(categoryOpt);
|
||||
if (category.isEmpty())
|
||||
throw new IllegalArgumentException("no category (open|closed|failed) specified");
|
||||
|
||||
if (!isValidCategory.test(category))
|
||||
throw new IllegalArgumentException("category must be open|closed|failed");
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public GetTradesRequest.Category getCategory() {
|
||||
String categoryOpt = options.valueOf(this.categoryOpt).toLowerCase();
|
||||
return CATEGORY.valueOf(categoryOpt).grpcRequestCategory;
|
||||
}
|
||||
}
|
60
cli/src/main/java/bisq/cli/opts/OfferIdOptionParser.java
Normal file
60
cli/src/main/java/bisq/cli/opts/OfferIdOptionParser.java
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.opts;
|
||||
|
||||
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import static bisq.cli.opts.OptLabel.OPT_OFFER_ID;
|
||||
|
||||
/**
|
||||
* Superclass for option parsers requiring an offer-id. Avoids a small amount of
|
||||
* duplicated boilerplate.
|
||||
*/
|
||||
public class OfferIdOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
||||
|
||||
final OptionSpec<String> offerIdOpt = parser.accepts(OPT_OFFER_ID, "id of offer")
|
||||
.withRequiredArg();
|
||||
|
||||
public OfferIdOptionParser(String[] args) {
|
||||
this(args, false);
|
||||
}
|
||||
|
||||
public OfferIdOptionParser(String[] args, boolean allowsUnrecognizedOptions) {
|
||||
super(args);
|
||||
if (allowsUnrecognizedOptions)
|
||||
this.parser.allowsUnrecognizedOptions();
|
||||
}
|
||||
|
||||
public OfferIdOptionParser parse() {
|
||||
super.parse();
|
||||
|
||||
// Short circuit opt validation if user just wants help.
|
||||
if (options.has(helpOpt))
|
||||
return this;
|
||||
|
||||
if (!options.has(offerIdOpt) || options.valueOf(offerIdOpt).isEmpty())
|
||||
throw new IllegalArgumentException("no offer id specified");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getOfferId() {
|
||||
return options.valueOf(offerIdOpt);
|
||||
}
|
||||
}
|
@ -24,9 +24,11 @@ public class OptLabel {
|
||||
public final static String OPT_ACCOUNT_NAME = "account-name";
|
||||
public final static String OPT_ADDRESS = "address";
|
||||
public final static String OPT_AMOUNT = "amount";
|
||||
public final static String OPT_CATEGORY = "category";
|
||||
public final static String OPT_CURRENCY_CODE = "currency-code";
|
||||
public final static String OPT_DIRECTION = "direction";
|
||||
public final static String OPT_DISPUTE_AGENT_TYPE = "dispute-agent-type";
|
||||
public final static String OPT_ENABLE = "enable";
|
||||
public final static String OPT_FEE_CURRENCY = "fee-currency";
|
||||
public final static String OPT_FIXED_PRICE = "fixed-price";
|
||||
public final static String OPT_HELP = "help";
|
||||
@ -36,7 +38,7 @@ public class OptLabel {
|
||||
public final static String OPT_MIN_AMOUNT = "min-amount";
|
||||
public final static String OPT_OFFER_ID = "offer-id";
|
||||
public final static String OPT_PASSWORD = "password";
|
||||
public final static String OPT_PAYMENT_ACCOUNT = "payment-account";
|
||||
public final static String OPT_PAYMENT_ACCOUNT_ID = "payment-account-id";
|
||||
public final static String OPT_PAYMENT_ACCOUNT_FORM = "payment-account-form";
|
||||
public final static String OPT_PAYMENT_METHOD_ID = "payment-method-id";
|
||||
public final static String OPT_PORT = "port";
|
||||
@ -47,6 +49,7 @@ public class OptLabel {
|
||||
public final static String OPT_TRADE_INSTANT = "trade-instant";
|
||||
public final static String OPT_TIMEOUT = "timeout";
|
||||
public final static String OPT_TRANSACTION_ID = "transaction-id";
|
||||
public final static String OPT_TRIGGER_PRICE = "trigger-price";
|
||||
public final static String OPT_TX_FEE_RATE = "tx-fee-rate";
|
||||
public final static String OPT_WALLET_PASSWORD = "wallet-password";
|
||||
public final static String OPT_NEW_WALLET_PASSWORD = "new-wallet-password";
|
||||
|
@ -25,7 +25,7 @@ import static bisq.cli.opts.OptLabel.OPT_TX_FEE_RATE;
|
||||
public class SetTxFeeRateOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
||||
|
||||
final OptionSpec<String> feeRateOpt = parser.accepts(OPT_TX_FEE_RATE,
|
||||
"tx fee rate preference (sats/byte)")
|
||||
"tx fee rate preference (sats/byte)")
|
||||
.withRequiredArg();
|
||||
|
||||
public SetTxFeeRateOptionParser(String[] args) {
|
||||
|
@ -21,30 +21,21 @@ package bisq.cli.opts;
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import static bisq.cli.opts.OptLabel.OPT_FEE_CURRENCY;
|
||||
import static bisq.cli.opts.OptLabel.OPT_OFFER_ID;
|
||||
import static bisq.cli.opts.OptLabel.OPT_PAYMENT_ACCOUNT;
|
||||
import static bisq.cli.opts.OptLabel.OPT_PAYMENT_ACCOUNT_ID;
|
||||
|
||||
public class TakeOfferOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
||||
public class TakeOfferOptionParser extends OfferIdOptionParser implements MethodOpts {
|
||||
|
||||
final OptionSpec<String> offerIdOpt = parser.accepts(OPT_OFFER_ID, "id of offer to take")
|
||||
.withRequiredArg();
|
||||
|
||||
final OptionSpec<String> paymentAccountIdOpt = parser.accepts(OPT_PAYMENT_ACCOUNT, "id of payment account used for trade")
|
||||
final OptionSpec<String> paymentAccountIdOpt = parser.accepts(OPT_PAYMENT_ACCOUNT_ID, "id of payment account used for trade")
|
||||
.withRequiredArg();
|
||||
|
||||
public TakeOfferOptionParser(String[] args) {
|
||||
super(args);
|
||||
super(args, true);
|
||||
}
|
||||
|
||||
public TakeOfferOptionParser parse() {
|
||||
super.parse();
|
||||
|
||||
// Short circuit opt validation if user just wants help.
|
||||
if (options.has(helpOpt))
|
||||
return this;
|
||||
|
||||
if (!options.has(offerIdOpt) || options.valueOf(offerIdOpt).isEmpty())
|
||||
throw new IllegalArgumentException("no offer id specified");
|
||||
// Super class will short-circuit parsing if help option is present.
|
||||
|
||||
if (!options.has(paymentAccountIdOpt) || options.valueOf(paymentAccountIdOpt).isEmpty())
|
||||
throw new IllegalArgumentException("no payment account id specified");
|
||||
@ -52,10 +43,6 @@ public class TakeOfferOptionParser extends AbstractMethodOptionParser implements
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getOfferId() {
|
||||
return options.valueOf(offerIdOpt);
|
||||
}
|
||||
|
||||
public String getPaymentAccountId() {
|
||||
return options.valueOf(paymentAccountIdOpt);
|
||||
}
|
||||
|
166
cli/src/main/java/bisq/cli/request/OffersServiceRequest.java
Normal file
166
cli/src/main/java/bisq/cli/request/OffersServiceRequest.java
Normal file
@ -0,0 +1,166 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.request;
|
||||
|
||||
import bisq.proto.grpc.CancelOfferRequest;
|
||||
import bisq.proto.grpc.CreateOfferRequest;
|
||||
import bisq.proto.grpc.GetMyOfferRequest;
|
||||
import bisq.proto.grpc.GetMyOffersRequest;
|
||||
import bisq.proto.grpc.GetOfferRequest;
|
||||
import bisq.proto.grpc.GetOffersRequest;
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Comparator.comparing;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
import static protobuf.OfferDirection.SELL;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.GrpcStubs;
|
||||
|
||||
public class OffersServiceRequest {
|
||||
|
||||
private final GrpcStubs grpcStubs;
|
||||
|
||||
public OffersServiceRequest(GrpcStubs grpcStubs) {
|
||||
this.grpcStubs = grpcStubs;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public OfferInfo createFixedPricedOffer(String direction,
|
||||
String currencyCode,
|
||||
long amount,
|
||||
long minAmount,
|
||||
String fixedPrice,
|
||||
double securityDepositPct,
|
||||
String paymentAcctId,
|
||||
String makerFeeCurrencyCode) {
|
||||
return createOffer(direction,
|
||||
currencyCode,
|
||||
amount,
|
||||
minAmount,
|
||||
false,
|
||||
fixedPrice,
|
||||
0.00,
|
||||
securityDepositPct,
|
||||
paymentAcctId,
|
||||
"0" /* no trigger price */);
|
||||
}
|
||||
|
||||
public OfferInfo createOffer(String direction,
|
||||
String currencyCode,
|
||||
long amount,
|
||||
long minAmount,
|
||||
boolean useMarketBasedPrice,
|
||||
String fixedPrice,
|
||||
double marketPriceMarginPct,
|
||||
double securityDepositPct,
|
||||
String paymentAcctId,
|
||||
String triggerPrice) {
|
||||
var request = CreateOfferRequest.newBuilder()
|
||||
.setDirection(direction)
|
||||
.setCurrencyCode(currencyCode)
|
||||
.setAmount(amount)
|
||||
.setMinAmount(minAmount)
|
||||
.setUseMarketBasedPrice(useMarketBasedPrice)
|
||||
.setPrice(fixedPrice)
|
||||
.setMarketPriceMarginPct(marketPriceMarginPct)
|
||||
.setBuyerSecurityDepositPct(securityDepositPct)
|
||||
.setPaymentAccountId(paymentAcctId)
|
||||
.setTriggerPrice(triggerPrice)
|
||||
.build();
|
||||
return grpcStubs.offersService.createOffer(request).getOffer();
|
||||
}
|
||||
|
||||
public void cancelOffer(String offerId) {
|
||||
var request = CancelOfferRequest.newBuilder()
|
||||
.setId(offerId)
|
||||
.build();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
grpcStubs.offersService.cancelOffer(request);
|
||||
}
|
||||
|
||||
public OfferInfo getOffer(String offerId) {
|
||||
var request = GetOfferRequest.newBuilder()
|
||||
.setId(offerId)
|
||||
.build();
|
||||
return grpcStubs.offersService.getOffer(request).getOffer();
|
||||
}
|
||||
|
||||
public OfferInfo getMyOffer(String offerId) {
|
||||
var request = GetMyOfferRequest.newBuilder()
|
||||
.setId(offerId)
|
||||
.build();
|
||||
return grpcStubs.offersService.getMyOffer(request).getOffer();
|
||||
}
|
||||
|
||||
public List<OfferInfo> getOffers(String direction, String currencyCode) {
|
||||
var request = GetOffersRequest.newBuilder()
|
||||
.setDirection(direction)
|
||||
.setCurrencyCode(currencyCode)
|
||||
.build();
|
||||
return grpcStubs.offersService.getOffers(request).getOffersList();
|
||||
}
|
||||
|
||||
public List<OfferInfo> getOffersSortedByDate(String currencyCode) {
|
||||
ArrayList<OfferInfo> offers = new ArrayList<>();
|
||||
offers.addAll(getOffers(BUY.name(), currencyCode));
|
||||
offers.addAll(getOffers(SELL.name(), currencyCode));
|
||||
return offers.isEmpty() ? offers : sortOffersByDate(offers);
|
||||
}
|
||||
|
||||
public List<OfferInfo> getOffersSortedByDate(String direction, String currencyCode) {
|
||||
var offers = getOffers(direction, currencyCode);
|
||||
return offers.isEmpty() ? offers : sortOffersByDate(offers);
|
||||
}
|
||||
|
||||
public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
|
||||
var request = GetMyOffersRequest.newBuilder()
|
||||
.setDirection(direction)
|
||||
.setCurrencyCode(currencyCode)
|
||||
.build();
|
||||
return grpcStubs.offersService.getMyOffers(request).getOffersList();
|
||||
}
|
||||
|
||||
public List<OfferInfo> getMyOffersSortedByDate(String currencyCode) {
|
||||
ArrayList<OfferInfo> offers = new ArrayList<>();
|
||||
offers.addAll(getMyOffers(BUY.name(), currencyCode));
|
||||
offers.addAll(getMyOffers(SELL.name(), currencyCode));
|
||||
return offers.isEmpty() ? offers : sortOffersByDate(offers);
|
||||
}
|
||||
|
||||
public List<OfferInfo> getMyOffersSortedByDate(String direction, String currencyCode) {
|
||||
var offers = getMyOffers(direction, currencyCode);
|
||||
return offers.isEmpty() ? offers : sortOffersByDate(offers);
|
||||
}
|
||||
|
||||
public OfferInfo getMostRecentOffer(String direction, String currencyCode) {
|
||||
List<OfferInfo> offers = getOffersSortedByDate(direction, currencyCode);
|
||||
return offers.isEmpty() ? null : offers.get(offers.size() - 1);
|
||||
}
|
||||
|
||||
public List<OfferInfo> sortOffersByDate(List<OfferInfo> offerInfoList) {
|
||||
return offerInfoList.stream()
|
||||
.sorted(comparing(OfferInfo::getDate))
|
||||
.collect(toList());
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.request;
|
||||
|
||||
import bisq.proto.grpc.CreateCryptoCurrencyPaymentAccountRequest;
|
||||
import bisq.proto.grpc.CreatePaymentAccountRequest;
|
||||
import bisq.proto.grpc.GetCryptoCurrencyPaymentMethodsRequest;
|
||||
import bisq.proto.grpc.GetPaymentAccountFormRequest;
|
||||
import bisq.proto.grpc.GetPaymentAccountsRequest;
|
||||
import bisq.proto.grpc.GetPaymentMethodsRequest;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
import protobuf.PaymentMethod;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.GrpcStubs;
|
||||
|
||||
public class PaymentAccountsServiceRequest {
|
||||
|
||||
private final GrpcStubs grpcStubs;
|
||||
|
||||
public PaymentAccountsServiceRequest(GrpcStubs grpcStubs) {
|
||||
this.grpcStubs = grpcStubs;
|
||||
}
|
||||
|
||||
public List<PaymentMethod> getPaymentMethods() {
|
||||
var request = GetPaymentMethodsRequest.newBuilder().build();
|
||||
return grpcStubs.paymentAccountsService.getPaymentMethods(request).getPaymentMethodsList();
|
||||
}
|
||||
|
||||
public String getPaymentAcctFormAsJson(String paymentMethodId) {
|
||||
var request = GetPaymentAccountFormRequest.newBuilder()
|
||||
.setPaymentMethodId(paymentMethodId)
|
||||
.build();
|
||||
return grpcStubs.paymentAccountsService.getPaymentAccountForm(request).getPaymentAccountFormJson();
|
||||
}
|
||||
|
||||
public PaymentAccount createPaymentAccount(String json) {
|
||||
var request = CreatePaymentAccountRequest.newBuilder()
|
||||
.setPaymentAccountForm(json)
|
||||
.build();
|
||||
return grpcStubs.paymentAccountsService.createPaymentAccount(request).getPaymentAccount();
|
||||
}
|
||||
|
||||
public List<PaymentAccount> getPaymentAccounts() {
|
||||
var request = GetPaymentAccountsRequest.newBuilder().build();
|
||||
return grpcStubs.paymentAccountsService.getPaymentAccounts(request).getPaymentAccountsList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first PaymentAccount found with the given name, or throws an
|
||||
* IllegalArgumentException if not found. This method should be used with care;
|
||||
* it will only return one PaymentAccount, and the account name must be an exact
|
||||
* match on the name argument.
|
||||
* @param accountName the name of the stored PaymentAccount to retrieve
|
||||
* @return PaymentAccount with given name
|
||||
*/
|
||||
public PaymentAccount getPaymentAccount(String accountName) {
|
||||
return getPaymentAccounts().stream()
|
||||
.filter(a -> a.getAccountName().equals(accountName)).findFirst()
|
||||
.orElseThrow(() ->
|
||||
new IllegalArgumentException(format("payment account with name '%s' not found",
|
||||
accountName)));
|
||||
}
|
||||
|
||||
public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
|
||||
String currencyCode,
|
||||
String address,
|
||||
boolean tradeInstant) {
|
||||
var request = CreateCryptoCurrencyPaymentAccountRequest.newBuilder()
|
||||
.setAccountName(accountName)
|
||||
.setCurrencyCode(currencyCode)
|
||||
.setAddress(address)
|
||||
.setTradeInstant(tradeInstant)
|
||||
.build();
|
||||
return grpcStubs.paymentAccountsService.createCryptoCurrencyPaymentAccount(request).getPaymentAccount();
|
||||
}
|
||||
|
||||
public List<PaymentMethod> getCryptoPaymentMethods() {
|
||||
var request = GetCryptoCurrencyPaymentMethodsRequest.newBuilder().build();
|
||||
return grpcStubs.paymentAccountsService.getCryptoCurrencyPaymentMethods(request).getPaymentMethodsList();
|
||||
}
|
||||
}
|
110
cli/src/main/java/bisq/cli/request/TradesServiceRequest.java
Normal file
110
cli/src/main/java/bisq/cli/request/TradesServiceRequest.java
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.request;
|
||||
|
||||
import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
|
||||
import bisq.proto.grpc.ConfirmPaymentStartedRequest;
|
||||
import bisq.proto.grpc.GetTradeRequest;
|
||||
import bisq.proto.grpc.GetTradesRequest;
|
||||
import bisq.proto.grpc.TakeOfferReply;
|
||||
import bisq.proto.grpc.TakeOfferRequest;
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
import bisq.proto.grpc.WithdrawFundsRequest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static bisq.proto.grpc.GetTradesRequest.Category.CLOSED;
|
||||
import static bisq.proto.grpc.GetTradesRequest.Category.FAILED;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.GrpcStubs;
|
||||
|
||||
public class TradesServiceRequest {
|
||||
|
||||
private final GrpcStubs grpcStubs;
|
||||
|
||||
public TradesServiceRequest(GrpcStubs grpcStubs) {
|
||||
this.grpcStubs = grpcStubs;
|
||||
}
|
||||
|
||||
public TakeOfferReply getTakeOfferReply(String offerId, String paymentAccountId) {
|
||||
var request = TakeOfferRequest.newBuilder()
|
||||
.setOfferId(offerId)
|
||||
.setPaymentAccountId(paymentAccountId)
|
||||
.build();
|
||||
return grpcStubs.tradesService.takeOffer(request);
|
||||
}
|
||||
|
||||
public TradeInfo takeOffer(String offerId, String paymentAccountId) {
|
||||
var reply = getTakeOfferReply(offerId, paymentAccountId);
|
||||
if (reply.hasTrade())
|
||||
return reply.getTrade();
|
||||
else
|
||||
throw new IllegalStateException(reply.getFailureReason().getDescription());
|
||||
}
|
||||
|
||||
public TradeInfo getTrade(String tradeId) {
|
||||
var request = GetTradeRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.build();
|
||||
return grpcStubs.tradesService.getTrade(request).getTrade();
|
||||
}
|
||||
|
||||
public List<TradeInfo> getOpenTrades() {
|
||||
var request = GetTradesRequest.newBuilder()
|
||||
.build();
|
||||
return grpcStubs.tradesService.getTrades(request).getTradesList();
|
||||
}
|
||||
|
||||
public List<TradeInfo> getTradeHistory(GetTradesRequest.Category category) {
|
||||
if (!category.equals(CLOSED) && !category.equals(FAILED))
|
||||
throw new IllegalStateException("unrecognized gettrades category parameter " + category.name());
|
||||
|
||||
var request = GetTradesRequest.newBuilder()
|
||||
.setCategory(category)
|
||||
.build();
|
||||
return grpcStubs.tradesService.getTrades(request).getTradesList();
|
||||
}
|
||||
|
||||
public void confirmPaymentStarted(String tradeId) {
|
||||
var request = ConfirmPaymentStartedRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.build();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
grpcStubs.tradesService.confirmPaymentStarted(request);
|
||||
}
|
||||
|
||||
public void confirmPaymentReceived(String tradeId) {
|
||||
var request = ConfirmPaymentReceivedRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.build();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
grpcStubs.tradesService.confirmPaymentReceived(request);
|
||||
}
|
||||
|
||||
public void withdrawFunds(String tradeId, String address, String memo) {
|
||||
var request = WithdrawFundsRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.setAddress(address)
|
||||
.setMemo(memo)
|
||||
.build();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
grpcStubs.tradesService.withdrawFunds(request);
|
||||
}
|
||||
}
|
167
cli/src/main/java/bisq/cli/request/WalletsServiceRequest.java
Normal file
167
cli/src/main/java/bisq/cli/request/WalletsServiceRequest.java
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.request;
|
||||
|
||||
import bisq.proto.grpc.AddressBalanceInfo;
|
||||
import bisq.proto.grpc.BalancesInfo;
|
||||
import bisq.proto.grpc.BtcBalanceInfo;
|
||||
import bisq.proto.grpc.GetAddressBalanceRequest;
|
||||
import bisq.proto.grpc.GetBalancesRequest;
|
||||
import bisq.proto.grpc.GetFundingAddressesRequest;
|
||||
import bisq.proto.grpc.GetTransactionRequest;
|
||||
import bisq.proto.grpc.GetTxFeeRateRequest;
|
||||
import bisq.proto.grpc.LockWalletRequest;
|
||||
import bisq.proto.grpc.MarketPriceRequest;
|
||||
import bisq.proto.grpc.RemoveWalletPasswordRequest;
|
||||
import bisq.proto.grpc.SendBtcRequest;
|
||||
import bisq.proto.grpc.SetTxFeeRatePreferenceRequest;
|
||||
import bisq.proto.grpc.SetWalletPasswordRequest;
|
||||
import bisq.proto.grpc.TxFeeRateInfo;
|
||||
import bisq.proto.grpc.TxInfo;
|
||||
import bisq.proto.grpc.UnlockWalletRequest;
|
||||
import bisq.proto.grpc.UnsetTxFeeRatePreferenceRequest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.GrpcStubs;
|
||||
|
||||
public class WalletsServiceRequest {
|
||||
|
||||
private final GrpcStubs grpcStubs;
|
||||
|
||||
public WalletsServiceRequest(GrpcStubs grpcStubs) {
|
||||
this.grpcStubs = grpcStubs;
|
||||
}
|
||||
|
||||
public BalancesInfo getBalances() {
|
||||
return getBalances("");
|
||||
}
|
||||
|
||||
public BtcBalanceInfo getBtcBalances() {
|
||||
return getBalances("BTC").getBtc();
|
||||
}
|
||||
|
||||
public BalancesInfo getBalances(String currencyCode) {
|
||||
var request = GetBalancesRequest.newBuilder()
|
||||
.setCurrencyCode(currencyCode)
|
||||
.build();
|
||||
return grpcStubs.walletsService.getBalances(request).getBalances();
|
||||
}
|
||||
|
||||
public AddressBalanceInfo getAddressBalance(String address) {
|
||||
var request = GetAddressBalanceRequest.newBuilder()
|
||||
.setAddress(address).build();
|
||||
return grpcStubs.walletsService.getAddressBalance(request).getAddressBalanceInfo();
|
||||
}
|
||||
|
||||
public double getBtcPrice(String currencyCode) {
|
||||
var request = MarketPriceRequest.newBuilder()
|
||||
.setCurrencyCode(currencyCode)
|
||||
.build();
|
||||
return grpcStubs.priceService.getMarketPrice(request).getPrice();
|
||||
}
|
||||
|
||||
public List<AddressBalanceInfo> getFundingAddresses() {
|
||||
var request = GetFundingAddressesRequest.newBuilder().build();
|
||||
return grpcStubs.walletsService.getFundingAddresses(request).getAddressBalanceInfoList();
|
||||
}
|
||||
|
||||
public String getUnusedBtcAddress() {
|
||||
var request = GetFundingAddressesRequest.newBuilder().build();
|
||||
var addressBalances = grpcStubs.walletsService.getFundingAddresses(request)
|
||||
.getAddressBalanceInfoList();
|
||||
//noinspection OptionalGetWithoutIsPresent
|
||||
return addressBalances.stream()
|
||||
.filter(AddressBalanceInfo::getIsAddressUnused)
|
||||
.findFirst()
|
||||
.get()
|
||||
.getAddress();
|
||||
}
|
||||
|
||||
public TxInfo sendBtc(String address, String amount, String txFeeRate, String memo) {
|
||||
var request = SendBtcRequest.newBuilder()
|
||||
.setAddress(address)
|
||||
.setAmount(amount)
|
||||
.setTxFeeRate(txFeeRate)
|
||||
.setMemo(memo)
|
||||
.build();
|
||||
return grpcStubs.walletsService.sendBtc(request).getTxInfo();
|
||||
}
|
||||
|
||||
public TxFeeRateInfo getTxFeeRate() {
|
||||
var request = GetTxFeeRateRequest.newBuilder().build();
|
||||
return grpcStubs.walletsService.getTxFeeRate(request).getTxFeeRateInfo();
|
||||
}
|
||||
|
||||
public TxFeeRateInfo setTxFeeRate(long txFeeRate) {
|
||||
var request = SetTxFeeRatePreferenceRequest.newBuilder()
|
||||
.setTxFeeRatePreference(txFeeRate)
|
||||
.build();
|
||||
return grpcStubs.walletsService.setTxFeeRatePreference(request).getTxFeeRateInfo();
|
||||
}
|
||||
|
||||
public TxFeeRateInfo unsetTxFeeRate() {
|
||||
var request = UnsetTxFeeRatePreferenceRequest.newBuilder().build();
|
||||
return grpcStubs.walletsService.unsetTxFeeRatePreference(request).getTxFeeRateInfo();
|
||||
}
|
||||
|
||||
public TxInfo getTransaction(String txId) {
|
||||
var request = GetTransactionRequest.newBuilder()
|
||||
.setTxId(txId)
|
||||
.build();
|
||||
return grpcStubs.walletsService.getTransaction(request).getTxInfo();
|
||||
}
|
||||
|
||||
public void lockWallet() {
|
||||
var request = LockWalletRequest.newBuilder().build();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
grpcStubs.walletsService.lockWallet(request);
|
||||
}
|
||||
|
||||
public void unlockWallet(String walletPassword, long timeout) {
|
||||
var request = UnlockWalletRequest.newBuilder()
|
||||
.setPassword(walletPassword)
|
||||
.setTimeout(timeout).build();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
grpcStubs.walletsService.unlockWallet(request);
|
||||
}
|
||||
|
||||
public void removeWalletPassword(String walletPassword) {
|
||||
var request = RemoveWalletPasswordRequest.newBuilder()
|
||||
.setPassword(walletPassword).build();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
grpcStubs.walletsService.removeWalletPassword(request);
|
||||
}
|
||||
|
||||
public void setWalletPassword(String walletPassword) {
|
||||
var request = SetWalletPasswordRequest.newBuilder()
|
||||
.setPassword(walletPassword).build();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
grpcStubs.walletsService.setWalletPassword(request);
|
||||
}
|
||||
|
||||
public void setWalletPassword(String oldWalletPassword, String newWalletPassword) {
|
||||
var request = SetWalletPasswordRequest.newBuilder()
|
||||
.setPassword(oldWalletPassword)
|
||||
.setNewPassword(newWalletPassword).build();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
grpcStubs.walletsService.setWalletPassword(request);
|
||||
}
|
||||
}
|
155
cli/src/main/java/bisq/cli/table/Table.java
Normal file
155
cli/src/main/java/bisq/cli/table/Table.java
Normal file
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.table;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
|
||||
import static com.google.common.base.Strings.padStart;
|
||||
import static java.lang.String.format;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.table.column.Column;
|
||||
|
||||
/**
|
||||
* A simple table of formatted data for the CLI's output console. A table must be
|
||||
* created with at least one populated column, and each column passed to the constructor
|
||||
* must contain the same number of rows. Null checking is omitted because tables are
|
||||
* populated by protobuf message fields which cannot be null.
|
||||
*
|
||||
* All data in a column has the same type: long, string, etc., but a table
|
||||
* may contain an arbitrary number of columns of any type. For output formatting
|
||||
* purposes, numeric and date columns should be transformed to a StringColumn type with
|
||||
* formatted and justified string values before being passed to the constructor.
|
||||
*
|
||||
* This is not a relational, rdbms table.
|
||||
*/
|
||||
public class Table {
|
||||
|
||||
public final Column<?>[] columns;
|
||||
public final int rowCount;
|
||||
|
||||
// Each printed column is delimited by two spaces.
|
||||
private final int columnDelimiterLength = 2;
|
||||
|
||||
/**
|
||||
* Default constructor. Takes populated Columns.
|
||||
*
|
||||
* @param columns containing the same number of rows
|
||||
*/
|
||||
public Table(Column<?>... columns) {
|
||||
this.columns = columns;
|
||||
this.rowCount = columns.length > 0 ? columns[0].rowCount() : 0;
|
||||
validateStructure();
|
||||
}
|
||||
|
||||
/**
|
||||
* Print table data to a PrintStream.
|
||||
*
|
||||
* @param printStream the target output stream
|
||||
*/
|
||||
public void print(PrintStream printStream) {
|
||||
printColumnNames(printStream);
|
||||
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
|
||||
printRow(printStream, rowIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print table column names to a PrintStream.
|
||||
*
|
||||
* @param printStream the target output stream
|
||||
*/
|
||||
private void printColumnNames(PrintStream printStream) {
|
||||
IntStream.range(0, columns.length).forEachOrdered(colIndex -> {
|
||||
var c = columns[colIndex];
|
||||
var justifiedName = c.getJustification().equals(RIGHT)
|
||||
? padStart(c.getName(), c.getWidth(), ' ')
|
||||
: c.getName();
|
||||
var paddedWidth = colIndex == columns.length - 1
|
||||
? c.getName().length()
|
||||
: c.getWidth() + columnDelimiterLength;
|
||||
printStream.printf("%-" + paddedWidth + "s", justifiedName);
|
||||
});
|
||||
printStream.println();
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a table row to a PrintStream.
|
||||
*
|
||||
* @param printStream the target output stream
|
||||
*/
|
||||
private void printRow(PrintStream printStream, int rowIndex) {
|
||||
IntStream.range(0, columns.length).forEachOrdered(colIndex -> {
|
||||
var c = columns[colIndex];
|
||||
var paddedWidth = colIndex == columns.length - 1
|
||||
? c.getWidth()
|
||||
: c.getWidth() + columnDelimiterLength;
|
||||
printStream.printf("%-" + paddedWidth + "s", c.getRow(rowIndex));
|
||||
if (colIndex == columns.length - 1)
|
||||
printStream.println();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the table's formatted output as a String.
|
||||
* @return String
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (PrintStream ps = new PrintStream(baos, true, UTF_8)) {
|
||||
print(ps);
|
||||
}
|
||||
return baos.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the table has columns, and each column has the same number of rows.
|
||||
*/
|
||||
private void validateStructure() {
|
||||
if (columns.length == 0)
|
||||
throw new IllegalArgumentException("Table has no columns.");
|
||||
|
||||
if (columns[0].isEmpty())
|
||||
throw new IllegalArgumentException(
|
||||
format("Table's 1st column (%s) has no data.",
|
||||
columns[0].getName()));
|
||||
|
||||
IntStream.range(1, columns.length).forEachOrdered(colIndex -> {
|
||||
var c = columns[colIndex];
|
||||
|
||||
if (c.isEmpty())
|
||||
throw new IllegalStateException(
|
||||
format("Table column # %d (%s) does not have any data.",
|
||||
colIndex + 1,
|
||||
c.getName()));
|
||||
|
||||
if (this.rowCount != c.rowCount())
|
||||
throw new IllegalStateException(
|
||||
format("Table column # %d (%s) does not have same number of rows as 1st column.",
|
||||
colIndex + 1,
|
||||
c.getName()));
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.table.builder;
|
||||
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.table.Table;
|
||||
|
||||
/**
|
||||
* Abstract superclass for TableBuilder implementations.
|
||||
*/
|
||||
abstract class AbstractTableBuilder {
|
||||
|
||||
protected final Predicate<OfferInfo> isFiatOffer = (o) -> o.getBaseCurrencyCode().equals("BTC");
|
||||
|
||||
protected final TableType tableType;
|
||||
protected final List<?> protos;
|
||||
|
||||
AbstractTableBuilder(TableType tableType, List<?> protos) {
|
||||
this.tableType = tableType;
|
||||
this.protos = protos;
|
||||
if (protos.isEmpty())
|
||||
throw new IllegalArgumentException("cannot build a table without rows");
|
||||
}
|
||||
|
||||
public abstract Table build();
|
||||
}
|
@ -0,0 +1,255 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.table.builder;
|
||||
|
||||
import bisq.proto.grpc.ContractInfo;
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static bisq.cli.CurrencyFormat.formatSatoshis;
|
||||
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_BUYER_DEPOSIT;
|
||||
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_SELLER_DEPOSIT;
|
||||
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
|
||||
import static java.lang.String.format;
|
||||
import static protobuf.OfferDirection.SELL;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.table.column.Column;
|
||||
import bisq.cli.table.column.MixedTradeFeeColumn;
|
||||
|
||||
abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
|
||||
|
||||
protected final List<TradeInfo> trades;
|
||||
|
||||
protected final TradeTableColumnSupplier colSupplier;
|
||||
|
||||
protected final Column<String> colTradeId;
|
||||
@Nullable
|
||||
protected final Column<Long> colCreateDate;
|
||||
@Nullable
|
||||
protected final Column<String> colMarket;
|
||||
protected final Column<String> colPrice;
|
||||
@Nullable
|
||||
protected final Column<String> colPriceDeviation;
|
||||
@Nullable
|
||||
protected final Column<String> colCurrency;
|
||||
@Nullable
|
||||
protected final Column<Long> colAmount;
|
||||
@Nullable
|
||||
protected final Column<String> colMixedAmount;
|
||||
@Nullable
|
||||
protected final Column<Long> colMinerTxFee;
|
||||
@Nullable
|
||||
protected final MixedTradeFeeColumn colMixedTradeFee;
|
||||
@Nullable
|
||||
protected final Column<Long> colBuyerDeposit;
|
||||
@Nullable
|
||||
protected final Column<Long> colSellerDeposit;
|
||||
@Nullable
|
||||
protected final Column<String> colPaymentMethod;
|
||||
@Nullable
|
||||
protected final Column<String> colRole;
|
||||
@Nullable
|
||||
protected final Column<String> colOfferType;
|
||||
@Nullable
|
||||
protected final Column<String> colClosingStatus;
|
||||
|
||||
// Trade detail tbl specific columns
|
||||
|
||||
@Nullable
|
||||
protected final Column<Boolean> colIsDepositPublished;
|
||||
@Nullable
|
||||
protected final Column<Boolean> colIsDepositConfirmed;
|
||||
@Nullable
|
||||
protected final Column<Boolean> colIsPayoutPublished;
|
||||
@Nullable
|
||||
protected final Column<Boolean> colIsCompleted;
|
||||
@Nullable
|
||||
protected final Column<Long> colBisqTradeFee;
|
||||
@Nullable
|
||||
protected final Column<String> colTradeCost;
|
||||
@Nullable
|
||||
protected final Column<Boolean> colIsPaymentStartedMessageSent;
|
||||
@Nullable
|
||||
protected final Column<Boolean> colIsPaymentReceivedMessageSent;
|
||||
@Nullable
|
||||
protected final Column<String> colAltcoinReceiveAddressColumn;
|
||||
|
||||
AbstractTradeListBuilder(TableType tableType, List<?> protos) {
|
||||
super(tableType, protos);
|
||||
validate();
|
||||
|
||||
this.trades = protos.stream().map(p -> (TradeInfo) p).collect(Collectors.toList());
|
||||
this.colSupplier = new TradeTableColumnSupplier(tableType, trades);
|
||||
|
||||
this.colTradeId = colSupplier.tradeIdColumn.get();
|
||||
this.colCreateDate = colSupplier.createDateColumn.get();
|
||||
this.colMarket = colSupplier.marketColumn.get();
|
||||
this.colPrice = colSupplier.priceColumn.get();
|
||||
this.colPriceDeviation = colSupplier.priceDeviationColumn.get();
|
||||
this.colCurrency = colSupplier.currencyColumn.get();
|
||||
this.colAmount = colSupplier.amountColumn.get();
|
||||
this.colMixedAmount = colSupplier.mixedAmountColumn.get();
|
||||
this.colMinerTxFee = colSupplier.minerTxFeeColumn.get();
|
||||
this.colMixedTradeFee = colSupplier.mixedTradeFeeColumn.get();
|
||||
this.colBuyerDeposit = colSupplier.toSecurityDepositColumn.apply(COL_HEADER_BUYER_DEPOSIT);
|
||||
this.colSellerDeposit = colSupplier.toSecurityDepositColumn.apply(COL_HEADER_SELLER_DEPOSIT);
|
||||
this.colPaymentMethod = colSupplier.paymentMethodColumn.get();
|
||||
this.colRole = colSupplier.roleColumn.get();
|
||||
this.colOfferType = colSupplier.offerTypeColumn.get();
|
||||
this.colClosingStatus = colSupplier.statusDescriptionColumn.get();
|
||||
|
||||
// Trade detail specific columns, some in common with BSQ swap trades detail.
|
||||
|
||||
this.colIsDepositPublished = colSupplier.depositPublishedColumn.get();
|
||||
this.colIsDepositConfirmed = colSupplier.depositConfirmedColumn.get();
|
||||
this.colIsPayoutPublished = colSupplier.payoutPublishedColumn.get();
|
||||
this.colIsCompleted = colSupplier.fundsWithdrawnColumn.get();
|
||||
this.colBisqTradeFee = colSupplier.bisqTradeDetailFeeColumn.get();
|
||||
this.colTradeCost = colSupplier.tradeCostColumn.get();
|
||||
this.colIsPaymentStartedMessageSent = colSupplier.paymentStartedMessageSentColumn.get();
|
||||
this.colIsPaymentReceivedMessageSent = colSupplier.paymentReceivedMessageSentColumn.get();
|
||||
//noinspection ConstantConditions
|
||||
this.colAltcoinReceiveAddressColumn = colSupplier.altcoinReceiveAddressColumn.get();
|
||||
}
|
||||
|
||||
protected void validate() {
|
||||
if (isTradeDetailTblBuilder.get()) {
|
||||
if (protos.size() != 1)
|
||||
throw new IllegalArgumentException("trade detail tbl can have only one row");
|
||||
} else if (protos.isEmpty()) {
|
||||
throw new IllegalArgumentException("trade tbl has no rows");
|
||||
}
|
||||
}
|
||||
|
||||
// Helper Functions
|
||||
|
||||
private final Supplier<Boolean> isTradeDetailTblBuilder = () -> tableType.equals(TRADE_DETAIL_TBL);
|
||||
protected final Predicate<TradeInfo> isFiatTrade = (t) -> isFiatOffer.test(t.getOffer());
|
||||
protected final Predicate<TradeInfo> isMyOffer = (t) -> t.getOffer().getIsMyOffer();
|
||||
protected final Predicate<TradeInfo> isTaker = (t) -> t.getRole().toLowerCase().contains("taker");
|
||||
protected final Predicate<TradeInfo> isSellOffer = (t) -> t.getOffer().getDirection().equals(SELL.name());
|
||||
protected final Predicate<TradeInfo> isBtcSeller = (t) -> (isMyOffer.test(t) && isSellOffer.test(t))
|
||||
|| (!isMyOffer.test(t) && !isSellOffer.test(t));
|
||||
|
||||
|
||||
// Column Value Functions
|
||||
|
||||
// Altcoin volumes from server are string representations of decimals.
|
||||
// Converting them to longs ("sats") requires shifting the decimal points
|
||||
// to left: 2 for BSQ, 8 for other altcoins.
|
||||
protected final Function<TradeInfo, Long> toAltcoinTradeVolumeAsLong = (t) -> new BigDecimal(t.getTradeVolume()).movePointRight(8).longValue();
|
||||
|
||||
protected final Function<TradeInfo, String> toTradeVolumeAsString = (t) ->
|
||||
isFiatTrade.test(t)
|
||||
? t.getTradeVolume()
|
||||
: formatSatoshis(t.getAmountAsLong());
|
||||
|
||||
protected final Function<TradeInfo, Long> toTradeVolumeAsLong = (t) ->
|
||||
isFiatTrade.test(t)
|
||||
? Long.parseLong(t.getTradeVolume())
|
||||
: toAltcoinTradeVolumeAsLong.apply(t);
|
||||
|
||||
protected final Function<TradeInfo, Long> toTradeAmount = (t) ->
|
||||
isFiatTrade.test(t)
|
||||
? t.getAmountAsLong()
|
||||
: toTradeVolumeAsLong.apply(t);
|
||||
|
||||
protected final Function<TradeInfo, String> toMarket = (t) ->
|
||||
t.getOffer().getBaseCurrencyCode() + "/"
|
||||
+ t.getOffer().getCounterCurrencyCode();
|
||||
|
||||
protected final Function<TradeInfo, String> toPaymentCurrencyCode = (t) ->
|
||||
isFiatTrade.test(t)
|
||||
? t.getOffer().getCounterCurrencyCode()
|
||||
: t.getOffer().getBaseCurrencyCode();
|
||||
|
||||
protected final Function<TradeInfo, String> toPriceDeviation = (t) ->
|
||||
t.getOffer().getUseMarketBasedPrice()
|
||||
? format("%.2f%s", t.getOffer().getMarketPriceMarginPct(), "%")
|
||||
: "N/A";
|
||||
|
||||
protected final Function<TradeInfo, Long> toMyMinerTxFee = (t) -> {
|
||||
return isTaker.test(t)
|
||||
? t.getTxFeeAsLong()
|
||||
: t.getOffer().getTxFee();
|
||||
};
|
||||
|
||||
protected final Function<TradeInfo, Long> toTradeFeeBtc = (t) -> {
|
||||
var isMyOffer = t.getOffer().getIsMyOffer();
|
||||
if (isMyOffer) {
|
||||
return t.getOffer().getMakerFee();
|
||||
} else {
|
||||
return t.getTakerFeeAsLong();
|
||||
}
|
||||
};
|
||||
|
||||
protected final Function<TradeInfo, Long> toMyMakerOrTakerFee = (t) -> {
|
||||
return isTaker.test(t)
|
||||
? t.getTakerFeeAsLong()
|
||||
: t.getOffer().getMakerFee();
|
||||
};
|
||||
|
||||
protected final Function<TradeInfo, String> toOfferType = (t) -> {
|
||||
if (isFiatTrade.test(t)) {
|
||||
return t.getOffer().getDirection() + " " + t.getOffer().getBaseCurrencyCode();
|
||||
} else {
|
||||
if (t.getOffer().getDirection().equals("BUY")) {
|
||||
return "SELL " + t.getOffer().getBaseCurrencyCode();
|
||||
} else {
|
||||
return "BUY " + t.getOffer().getBaseCurrencyCode();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected final Predicate<TradeInfo> showAltCoinBuyerAddress = (t) -> {
|
||||
if (isFiatTrade.test(t)) {
|
||||
return false;
|
||||
} else {
|
||||
ContractInfo contract = t.getContract();
|
||||
boolean isBuyerMakerAndSellerTaker = contract.getIsBuyerMakerAndSellerTaker();
|
||||
if (isTaker.test(t)) {
|
||||
return !isBuyerMakerAndSellerTaker;
|
||||
} else {
|
||||
return isBuyerMakerAndSellerTaker;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected final Function<TradeInfo, String> toAltcoinReceiveAddress = (t) -> {
|
||||
if (showAltCoinBuyerAddress.test(t)) {
|
||||
ContractInfo contract = t.getContract();
|
||||
boolean isBuyerMakerAndSellerTaker = contract.getIsBuyerMakerAndSellerTaker();
|
||||
return isBuyerMakerAndSellerTaker // (is BTC buyer / maker)
|
||||
? contract.getTakerPaymentAccountPayload().getAddress()
|
||||
: contract.getMakerPaymentAccountPayload().getAddress();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.table.builder;
|
||||
|
||||
import bisq.proto.grpc.AddressBalanceInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_ADDRESS;
|
||||
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_AVAILABLE_BALANCE;
|
||||
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_CONFIRMATIONS;
|
||||
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_IS_USED_ADDRESS;
|
||||
import static bisq.cli.table.builder.TableType.ADDRESS_BALANCE_TBL;
|
||||
import static java.lang.String.format;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.table.Table;
|
||||
import bisq.cli.table.column.BooleanColumn;
|
||||
import bisq.cli.table.column.Column;
|
||||
import bisq.cli.table.column.LongColumn;
|
||||
import bisq.cli.table.column.SatoshiColumn;
|
||||
import bisq.cli.table.column.StringColumn;
|
||||
|
||||
/**
|
||||
* Builds a {@code bisq.cli.table.Table} from a List of
|
||||
* {@code bisq.proto.grpc.AddressBalanceInfo} objects.
|
||||
*/
|
||||
class AddressBalanceTableBuilder extends AbstractTableBuilder {
|
||||
|
||||
// Default columns not dynamically generated with address info.
|
||||
private final Column<String> colAddress;
|
||||
private final Column<Long> colAvailableBalance;
|
||||
private final Column<Long> colConfirmations;
|
||||
private final Column<Boolean> colIsUsed;
|
||||
|
||||
AddressBalanceTableBuilder(List<?> protos) {
|
||||
super(ADDRESS_BALANCE_TBL, protos);
|
||||
colAddress = new StringColumn(format(COL_HEADER_ADDRESS, "BTC"));
|
||||
this.colAvailableBalance = new SatoshiColumn(COL_HEADER_AVAILABLE_BALANCE);
|
||||
this.colConfirmations = new LongColumn(COL_HEADER_CONFIRMATIONS);
|
||||
this.colIsUsed = new BooleanColumn(COL_HEADER_IS_USED_ADDRESS);
|
||||
}
|
||||
|
||||
public Table build() {
|
||||
List<AddressBalanceInfo> addresses = protos.stream()
|
||||
.map(a -> (AddressBalanceInfo) a)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Populate columns with address info.
|
||||
//noinspection SimplifyStreamApiCallChains
|
||||
addresses.stream().forEachOrdered(a -> {
|
||||
colAddress.addRow(a.getAddress());
|
||||
colAvailableBalance.addRow(a.getBalance());
|
||||
colConfirmations.addRow(a.getNumConfirmations());
|
||||
colIsUsed.addRow(!a.getIsAddressUnused());
|
||||
});
|
||||
|
||||
// Define and return the table instance with populated columns.
|
||||
return new Table(colAddress,
|
||||
colAvailableBalance.asStringColumn(),
|
||||
colConfirmations.asStringColumn(),
|
||||
colIsUsed.asStringColumn());
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.table.builder;
|
||||
|
||||
import bisq.proto.grpc.BtcBalanceInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_AVAILABLE_BALANCE;
|
||||
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_LOCKED_BALANCE;
|
||||
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_RESERVED_BALANCE;
|
||||
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_TOTAL_AVAILABLE_BALANCE;
|
||||
import static bisq.cli.table.builder.TableType.BTC_BALANCE_TBL;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.table.Table;
|
||||
import bisq.cli.table.column.Column;
|
||||
import bisq.cli.table.column.SatoshiColumn;
|
||||
|
||||
/**
|
||||
* Builds a {@code bisq.cli.table.Table} from a
|
||||
* {@code bisq.proto.grpc.BtcBalanceInfo} object.
|
||||
*/
|
||||
class BtcBalanceTableBuilder extends AbstractTableBuilder {
|
||||
|
||||
// Default columns not dynamically generated with btc balance info.
|
||||
private final Column<Long> colAvailableBalance;
|
||||
private final Column<Long> colReservedBalance;
|
||||
private final Column<Long> colTotalAvailableBalance;
|
||||
private final Column<Long> colLockedBalance;
|
||||
|
||||
BtcBalanceTableBuilder(List<?> protos) {
|
||||
super(BTC_BALANCE_TBL, protos);
|
||||
this.colAvailableBalance = new SatoshiColumn(COL_HEADER_AVAILABLE_BALANCE);
|
||||
this.colReservedBalance = new SatoshiColumn(COL_HEADER_RESERVED_BALANCE);
|
||||
this.colTotalAvailableBalance = new SatoshiColumn(COL_HEADER_TOTAL_AVAILABLE_BALANCE);
|
||||
this.colLockedBalance = new SatoshiColumn(COL_HEADER_LOCKED_BALANCE);
|
||||
}
|
||||
|
||||
public Table build() {
|
||||
BtcBalanceInfo balance = (BtcBalanceInfo) protos.get(0);
|
||||
|
||||
// Populate columns with btc balance info.
|
||||
|
||||
colAvailableBalance.addRow(balance.getAvailableBalance());
|
||||
colReservedBalance.addRow(balance.getReservedBalance());
|
||||
colTotalAvailableBalance.addRow(balance.getTotalAvailableBalance());
|
||||
colLockedBalance.addRow(balance.getLockedBalance());
|
||||
|
||||
// Define and return the table instance with populated columns.
|
||||
|
||||
return new Table(colAvailableBalance.asStringColumn(),
|
||||
colReservedBalance.asStringColumn(),
|
||||
colTotalAvailableBalance.asStringColumn(),
|
||||
colLockedBalance.asStringColumn());
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.table.builder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static bisq.cli.table.builder.TableType.CLOSED_TRADES_TBL;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.table.Table;
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
class ClosedTradeTableBuilder extends AbstractTradeListBuilder {
|
||||
|
||||
ClosedTradeTableBuilder(List<?> protos) {
|
||||
super(CLOSED_TRADES_TBL, protos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Table build() {
|
||||
populateColumns();
|
||||
return new Table(colTradeId,
|
||||
colCreateDate.asStringColumn(),
|
||||
colMarket,
|
||||
colPrice.justify(),
|
||||
colPriceDeviation.justify(),
|
||||
colAmount.asStringColumn(),
|
||||
colMixedAmount.justify(),
|
||||
colCurrency,
|
||||
colMinerTxFee.asStringColumn(),
|
||||
colMixedTradeFee.asStringColumn(),
|
||||
colBuyerDeposit.asStringColumn(),
|
||||
colSellerDeposit.asStringColumn(),
|
||||
colOfferType,
|
||||
colClosingStatus);
|
||||
}
|
||||
|
||||
private void populateColumns() {
|
||||
trades.forEach(t -> {
|
||||
colTradeId.addRow(t.getTradeId());
|
||||
colCreateDate.addRow(t.getDate());
|
||||
colMarket.addRow(toMarket.apply(t));
|
||||
colPrice.addRow(t.getPrice());
|
||||
colPriceDeviation.addRow(toPriceDeviation.apply(t));
|
||||
colAmount.addRow(t.getAmountAsLong());
|
||||
colMixedAmount.addRow(t.getTradeVolume());
|
||||
colCurrency.addRow(toPaymentCurrencyCode.apply(t));
|
||||
colMinerTxFee.addRow(toMyMinerTxFee.apply(t));
|
||||
|
||||
colMixedTradeFee.addRow(toTradeFeeBtc.apply(t), false);
|
||||
|
||||
colBuyerDeposit.addRow(t.getOffer().getBuyerSecurityDeposit());
|
||||
colSellerDeposit.addRow(t.getOffer().getSellerSecurityDeposit());
|
||||
colOfferType.addRow(toOfferType.apply(t));
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.table.builder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static bisq.cli.table.builder.TableType.FAILED_TRADES_TBL;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.table.Table;
|
||||
|
||||
/**
|
||||
* Builds a {@code bisq.cli.table.Table} from a list of {@code bisq.proto.grpc.TradeInfo} objects.
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
class FailedTradeTableBuilder extends AbstractTradeListBuilder {
|
||||
|
||||
FailedTradeTableBuilder(List<?> protos) {
|
||||
super(FAILED_TRADES_TBL, protos);
|
||||
}
|
||||
|
||||
public Table build() {
|
||||
populateColumns();
|
||||
return new Table(colTradeId,
|
||||
colCreateDate.asStringColumn(),
|
||||
colMarket,
|
||||
colPrice.justify(),
|
||||
colAmount.asStringColumn(),
|
||||
colMixedAmount.justify(),
|
||||
colCurrency,
|
||||
colOfferType,
|
||||
colRole,
|
||||
colClosingStatus);
|
||||
}
|
||||
|
||||
private void populateColumns() {
|
||||
trades.forEach(t -> {
|
||||
colTradeId.addRow(t.getTradeId());
|
||||
colCreateDate.addRow(t.getDate());
|
||||
colMarket.addRow(toMarket.apply(t));
|
||||
colPrice.addRow(t.getPrice());
|
||||
colAmount.addRow(t.getAmountAsLong());
|
||||
colMixedAmount.addRow(t.getTradeVolume());
|
||||
colCurrency.addRow(toPaymentCurrencyCode.apply(t));
|
||||
colOfferType.addRow(toOfferType.apply(t));
|
||||
colRole.addRow(t.getRole());
|
||||
colClosingStatus.addRow("Failed");
|
||||
});
|
||||
}
|
||||
}
|
267
cli/src/main/java/bisq/cli/table/builder/OfferTableBuilder.java
Normal file
267
cli/src/main/java/bisq/cli/table/builder/OfferTableBuilder.java
Normal file
@ -0,0 +1,267 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.table.builder;
|
||||
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static bisq.cli.table.builder.TableBuilderConstants.*;
|
||||
import static bisq.cli.table.builder.TableType.OFFER_TBL;
|
||||
import static bisq.cli.table.column.Column.JUSTIFICATION.LEFT;
|
||||
import static bisq.cli.table.column.Column.JUSTIFICATION.NONE;
|
||||
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
|
||||
import static bisq.cli.table.column.ZippedStringColumns.DUPLICATION_MODE.EXCLUDE_DUPLICATES;
|
||||
import static java.lang.String.format;
|
||||
import static protobuf.OfferDirection.BUY;
|
||||
import static protobuf.OfferDirection.SELL;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.table.Table;
|
||||
import bisq.cli.table.column.Column;
|
||||
import bisq.cli.table.column.Iso8601DateTimeColumn;
|
||||
import bisq.cli.table.column.SatoshiColumn;
|
||||
import bisq.cli.table.column.StringColumn;
|
||||
import bisq.cli.table.column.ZippedStringColumns;
|
||||
|
||||
/**
|
||||
* Builds a {@code bisq.cli.table.Table} from a List of
|
||||
* {@code bisq.proto.grpc.OfferInfo} objects.
|
||||
*/
|
||||
class OfferTableBuilder extends AbstractTableBuilder {
|
||||
|
||||
// Columns common to both fiat and cryptocurrency offers.
|
||||
private final Column<String> colOfferId = new StringColumn(COL_HEADER_UUID, LEFT);
|
||||
private final Column<String> colDirection = new StringColumn(COL_HEADER_DIRECTION, LEFT);
|
||||
private final Column<Long> colAmount = new SatoshiColumn("Temp Amount", NONE);
|
||||
private final Column<Long> colMinAmount = new SatoshiColumn("Temp Min Amount", NONE);
|
||||
private final Column<String> colPaymentMethod = new StringColumn(COL_HEADER_PAYMENT_METHOD, LEFT);
|
||||
private final Column<Long> colCreateDate = new Iso8601DateTimeColumn(COL_HEADER_CREATION_DATE);
|
||||
|
||||
OfferTableBuilder(List<?> protos) {
|
||||
super(OFFER_TBL, protos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Table build() {
|
||||
List<OfferInfo> offers = protos.stream().map(p -> (OfferInfo) p).collect(Collectors.toList());
|
||||
return isShowingFiatOffers.get()
|
||||
? buildFiatOfferTable(offers)
|
||||
: buildCryptoCurrencyOfferTable(offers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public Table buildFiatOfferTable(List<OfferInfo> offers) {
|
||||
@Nullable
|
||||
Column<String> colEnabled = enabledColumn.get(); // Not boolean: "YES", "NO", or "PENDING"
|
||||
Column<String> colFiatPrice = new StringColumn(format(COL_HEADER_DETAILED_PRICE, fiatTradeCurrency.get()), RIGHT);
|
||||
Column<String> colVolume = new StringColumn(format("Temp Volume (%s)", fiatTradeCurrency.get()), NONE);
|
||||
Column<String> colMinVolume = new StringColumn(format("Temp Min Volume (%s)", fiatTradeCurrency.get()), NONE);
|
||||
@Nullable
|
||||
Column<String> colTriggerPrice = fiatTriggerPriceColumn.get();
|
||||
|
||||
// Populate columns with offer info.
|
||||
|
||||
offers.forEach(o -> {
|
||||
if (colEnabled != null)
|
||||
colEnabled.addRow(toEnabled.apply(o));
|
||||
|
||||
colDirection.addRow(o.getDirection());
|
||||
colFiatPrice.addRow(o.getPrice());
|
||||
colMinAmount.addRow(o.getMinAmount());
|
||||
colAmount.addRow(o.getAmount());
|
||||
colVolume.addRow(o.getVolume());
|
||||
colMinVolume.addRow(o.getMinVolume());
|
||||
|
||||
if (colTriggerPrice != null)
|
||||
colTriggerPrice.addRow(toBlankOrNonZeroValue.apply(o.getTriggerPrice()));
|
||||
|
||||
colPaymentMethod.addRow(o.getPaymentMethodShortName());
|
||||
colCreateDate.addRow(o.getDate());
|
||||
colOfferId.addRow(o.getId());
|
||||
});
|
||||
|
||||
ZippedStringColumns amountRange = zippedAmountRangeColumns.get();
|
||||
ZippedStringColumns volumeRange =
|
||||
new ZippedStringColumns(format(COL_HEADER_VOLUME_RANGE, fiatTradeCurrency.get()),
|
||||
RIGHT,
|
||||
" - ",
|
||||
colMinVolume.asStringColumn(),
|
||||
colVolume.asStringColumn());
|
||||
|
||||
// Define and return the table instance with populated columns.
|
||||
|
||||
if (isShowingMyOffers.get()) {
|
||||
return new Table(colEnabled.asStringColumn(),
|
||||
colDirection,
|
||||
colFiatPrice.justify(),
|
||||
amountRange.asStringColumn(EXCLUDE_DUPLICATES),
|
||||
volumeRange.asStringColumn(EXCLUDE_DUPLICATES),
|
||||
colTriggerPrice.justify(),
|
||||
colPaymentMethod,
|
||||
colCreateDate.asStringColumn(),
|
||||
colOfferId);
|
||||
} else {
|
||||
return new Table(colDirection,
|
||||
colFiatPrice.justify(),
|
||||
amountRange.asStringColumn(EXCLUDE_DUPLICATES),
|
||||
volumeRange.asStringColumn(EXCLUDE_DUPLICATES),
|
||||
colPaymentMethod,
|
||||
colCreateDate.asStringColumn(),
|
||||
colOfferId);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public Table buildCryptoCurrencyOfferTable(List<OfferInfo> offers) {
|
||||
@Nullable
|
||||
Column<String> colEnabled = enabledColumn.get(); // Not boolean: YES, NO, or PENDING
|
||||
Column<String> colBtcPrice = new StringColumn(format(COL_HEADER_DETAILED_PRICE_OF_ALTCOIN, altcoinTradeCurrency.get()), RIGHT);
|
||||
Column<String> colVolume = new StringColumn(format("Temp Volume (%s)", altcoinTradeCurrency.get()), NONE);
|
||||
Column<String> colMinVolume = new StringColumn(format("Temp Min Volume (%s)", altcoinTradeCurrency.get()), NONE);
|
||||
@Nullable
|
||||
Column<String> colTriggerPrice = altcoinTriggerPriceColumn.get();
|
||||
|
||||
// Populate columns with offer info.
|
||||
|
||||
offers.forEach(o -> {
|
||||
if (colEnabled != null)
|
||||
colEnabled.addRow(toEnabled.apply(o));
|
||||
|
||||
colDirection.addRow(directionFormat.apply(o));
|
||||
colBtcPrice.addRow(o.getPrice());
|
||||
colAmount.addRow(o.getAmount());
|
||||
colMinAmount.addRow(o.getMinAmount());
|
||||
colVolume.addRow(o.getVolume());
|
||||
colMinVolume.addRow(o.getMinVolume());
|
||||
|
||||
if (colTriggerPrice != null)
|
||||
colTriggerPrice.addRow(toBlankOrNonZeroValue.apply(o.getTriggerPrice()));
|
||||
|
||||
colPaymentMethod.addRow(o.getPaymentMethodShortName());
|
||||
colCreateDate.addRow(o.getDate());
|
||||
colOfferId.addRow(o.getId());
|
||||
});
|
||||
|
||||
ZippedStringColumns amountRange = zippedAmountRangeColumns.get();
|
||||
ZippedStringColumns volumeRange =
|
||||
new ZippedStringColumns(format(COL_HEADER_VOLUME_RANGE, altcoinTradeCurrency.get()),
|
||||
RIGHT,
|
||||
" - ",
|
||||
colMinVolume.asStringColumn(),
|
||||
colVolume.asStringColumn());
|
||||
|
||||
// Define and return the table instance with populated columns.
|
||||
|
||||
if (isShowingMyOffers.get()) {
|
||||
if (isShowingBsqOffers.get()) {
|
||||
return new Table(colEnabled.asStringColumn(),
|
||||
colDirection,
|
||||
colBtcPrice.justify(),
|
||||
amountRange.asStringColumn(EXCLUDE_DUPLICATES),
|
||||
volumeRange.asStringColumn(EXCLUDE_DUPLICATES),
|
||||
colPaymentMethod,
|
||||
colCreateDate.asStringColumn(),
|
||||
colOfferId);
|
||||
} else {
|
||||
return new Table(colEnabled.asStringColumn(),
|
||||
colDirection,
|
||||
colBtcPrice.justify(),
|
||||
amountRange.asStringColumn(EXCLUDE_DUPLICATES),
|
||||
volumeRange.asStringColumn(EXCLUDE_DUPLICATES),
|
||||
colTriggerPrice.justify(),
|
||||
colPaymentMethod,
|
||||
colCreateDate.asStringColumn(),
|
||||
colOfferId);
|
||||
}
|
||||
} else {
|
||||
return new Table(colDirection,
|
||||
colBtcPrice.justify(),
|
||||
amountRange.asStringColumn(EXCLUDE_DUPLICATES),
|
||||
volumeRange.asStringColumn(EXCLUDE_DUPLICATES),
|
||||
colPaymentMethod,
|
||||
colCreateDate.asStringColumn(),
|
||||
colOfferId);
|
||||
}
|
||||
}
|
||||
|
||||
private final Function<String, String> toBlankOrNonZeroValue = (s) -> s.trim().equals("0") ? "" : s;
|
||||
private final Supplier<OfferInfo> firstOfferInList = () -> (OfferInfo) protos.get(0);
|
||||
private final Supplier<Boolean> isShowingMyOffers = () -> firstOfferInList.get().getIsMyOffer();
|
||||
private final Supplier<Boolean> isShowingFiatOffers = () -> isFiatOffer.test(firstOfferInList.get());
|
||||
private final Supplier<String> fiatTradeCurrency = () -> firstOfferInList.get().getCounterCurrencyCode();
|
||||
private final Supplier<String> altcoinTradeCurrency = () -> firstOfferInList.get().getBaseCurrencyCode();
|
||||
private final Supplier<Boolean> isShowingBsqOffers = () ->
|
||||
!isFiatOffer.test(firstOfferInList.get()) && altcoinTradeCurrency.get().equals("BSQ");
|
||||
|
||||
@Nullable // Not a boolean column: YES, NO, or PENDING.
|
||||
private final Supplier<StringColumn> enabledColumn = () ->
|
||||
isShowingMyOffers.get()
|
||||
? new StringColumn(COL_HEADER_ENABLED, LEFT)
|
||||
: null;
|
||||
@Nullable
|
||||
private final Supplier<StringColumn> fiatTriggerPriceColumn = () ->
|
||||
isShowingMyOffers.get()
|
||||
? new StringColumn(format(COL_HEADER_TRIGGER_PRICE, fiatTradeCurrency.get()), RIGHT)
|
||||
: null;
|
||||
@Nullable
|
||||
private final Supplier<StringColumn> altcoinTriggerPriceColumn = () ->
|
||||
isShowingMyOffers.get() && !isShowingBsqOffers.get()
|
||||
? new StringColumn(format(COL_HEADER_TRIGGER_PRICE, altcoinTradeCurrency.get()), RIGHT)
|
||||
: null;
|
||||
|
||||
private final Function<OfferInfo, String> toEnabled = (o) -> {
|
||||
return o.getIsActivated() ? "YES" : "NO";
|
||||
};
|
||||
|
||||
private final Function<String, String> toMirroredDirection = (d) ->
|
||||
d.equalsIgnoreCase(BUY.name()) ? SELL.name() : BUY.name();
|
||||
|
||||
private final Function<OfferInfo, String> directionFormat = (o) -> {
|
||||
if (isFiatOffer.test(o)) {
|
||||
return o.getBaseCurrencyCode();
|
||||
} else {
|
||||
// Return "Sell BSQ (Buy BTC)", or "Buy BSQ (Sell BTC)".
|
||||
String direction = o.getDirection();
|
||||
String mirroredDirection = toMirroredDirection.apply(direction);
|
||||
Function<String, String> mixedCase = (word) -> word.charAt(0) + word.substring(1).toLowerCase();
|
||||
return format("%s %s (%s %s)",
|
||||
mixedCase.apply(mirroredDirection),
|
||||
o.getBaseCurrencyCode(),
|
||||
mixedCase.apply(direction),
|
||||
o.getCounterCurrencyCode());
|
||||
}
|
||||
};
|
||||
|
||||
private final Supplier<ZippedStringColumns> zippedAmountRangeColumns = () -> {
|
||||
if (colMinAmount.isEmpty() || colAmount.isEmpty())
|
||||
throw new IllegalStateException("amount columns must have data");
|
||||
|
||||
return new ZippedStringColumns(COL_HEADER_AMOUNT_RANGE,
|
||||
RIGHT,
|
||||
" - ",
|
||||
colMinAmount.asStringColumn(),
|
||||
colAmount.asStringColumn());
|
||||
};
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.table.builder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static bisq.cli.table.builder.TableType.OPEN_TRADES_TBL;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.table.Table;
|
||||
|
||||
/**
|
||||
* Builds a {@code bisq.cli.table.Table} from a list of {@code bisq.proto.grpc.TradeInfo} objects.
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
class OpenTradeTableBuilder extends AbstractTradeListBuilder {
|
||||
|
||||
OpenTradeTableBuilder(List<?> protos) {
|
||||
super(OPEN_TRADES_TBL, protos);
|
||||
}
|
||||
|
||||
public Table build() {
|
||||
populateColumns();
|
||||
return new Table(colTradeId,
|
||||
colCreateDate.asStringColumn(),
|
||||
colMarket,
|
||||
colPrice.justify(),
|
||||
colAmount.asStringColumn(),
|
||||
colMixedAmount.justify(),
|
||||
colCurrency,
|
||||
colPaymentMethod,
|
||||
colRole);
|
||||
}
|
||||
|
||||
private void populateColumns() {
|
||||
trades.forEach(t -> {
|
||||
colTradeId.addRow(t.getTradeId());
|
||||
colCreateDate.addRow(t.getDate());
|
||||
colMarket.addRow(toMarket.apply(t));
|
||||
colPrice.addRow(t.getPrice());
|
||||
colAmount.addRow(t.getAmountAsLong());
|
||||
colMixedAmount.addRow(t.getTradeVolume());
|
||||
colCurrency.addRow(toPaymentCurrencyCode.apply(t));
|
||||
colPaymentMethod.addRow(t.getOffer().getPaymentMethodShortName());
|
||||
colRole.addRow(t.getRole());
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.table.builder;
|
||||
|
||||
import protobuf.PaymentAccount;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_CURRENCY;
|
||||
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_NAME;
|
||||
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_PAYMENT_METHOD;
|
||||
import static bisq.cli.table.builder.TableBuilderConstants.COL_HEADER_UUID;
|
||||
import static bisq.cli.table.builder.TableType.PAYMENT_ACCOUNT_TBL;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.table.Table;
|
||||
import bisq.cli.table.column.Column;
|
||||
import bisq.cli.table.column.StringColumn;
|
||||
|
||||
/**
|
||||
* Builds a {@code bisq.cli.table.Table} from a List of
|
||||
* {@code protobuf.PaymentAccount} objects.
|
||||
*/
|
||||
class PaymentAccountTableBuilder extends AbstractTableBuilder {
|
||||
|
||||
// Default columns not dynamically generated with payment account info.
|
||||
private final Column<String> colName;
|
||||
private final Column<String> colCurrency;
|
||||
private final Column<String> colPaymentMethod;
|
||||
private final Column<String> colId;
|
||||
|
||||
PaymentAccountTableBuilder(List<?> protos) {
|
||||
super(PAYMENT_ACCOUNT_TBL, protos);
|
||||
this.colName = new StringColumn(COL_HEADER_NAME);
|
||||
this.colCurrency = new StringColumn(COL_HEADER_CURRENCY);
|
||||
this.colPaymentMethod = new StringColumn(COL_HEADER_PAYMENT_METHOD);
|
||||
this.colId = new StringColumn(COL_HEADER_UUID);
|
||||
}
|
||||
|
||||
public Table build() {
|
||||
List<PaymentAccount> paymentAccounts = protos.stream()
|
||||
.map(a -> (PaymentAccount) a)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Populate columns with payment account info.
|
||||
//noinspection SimplifyStreamApiCallChains
|
||||
paymentAccounts.stream().forEachOrdered(a -> {
|
||||
colName.addRow(a.getAccountName());
|
||||
colCurrency.addRow(a.getSelectedTradeCurrency().getCode());
|
||||
colPaymentMethod.addRow(a.getPaymentMethod().getId());
|
||||
colId.addRow(a.getId());
|
||||
});
|
||||
|
||||
// Define and return the table instance with populated columns.
|
||||
return new Table(colName, colCurrency, colPaymentMethod, colId);
|
||||
}
|
||||
}
|
69
cli/src/main/java/bisq/cli/table/builder/TableBuilder.java
Normal file
69
cli/src/main/java/bisq/cli/table/builder/TableBuilder.java
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.table.builder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.table.Table;
|
||||
|
||||
/**
|
||||
* Table builder factory. It is not conventionally named TableBuilderFactory because
|
||||
* it has no static factory methods. The number of static fields and methods in the
|
||||
* {@code bisq.cli.table} are kept to a minimum in an effort o reduce class load time
|
||||
* in the session-less CLI.
|
||||
*/
|
||||
public class TableBuilder extends AbstractTableBuilder {
|
||||
|
||||
public TableBuilder(TableType tableType, Object proto) {
|
||||
this(tableType, singletonList(proto));
|
||||
}
|
||||
|
||||
public TableBuilder(TableType tableType, List<?> protos) {
|
||||
super(tableType, protos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Table build() {
|
||||
switch (tableType) {
|
||||
case ADDRESS_BALANCE_TBL:
|
||||
return new AddressBalanceTableBuilder(protos).build();
|
||||
case BTC_BALANCE_TBL:
|
||||
return new BtcBalanceTableBuilder(protos).build();
|
||||
case CLOSED_TRADES_TBL:
|
||||
return new ClosedTradeTableBuilder(protos).build();
|
||||
case FAILED_TRADES_TBL:
|
||||
return new FailedTradeTableBuilder(protos).build();
|
||||
case OFFER_TBL:
|
||||
return new OfferTableBuilder(protos).build();
|
||||
case OPEN_TRADES_TBL:
|
||||
return new OpenTradeTableBuilder(protos).build();
|
||||
case PAYMENT_ACCOUNT_TBL:
|
||||
return new PaymentAccountTableBuilder(protos).build();
|
||||
case TRADE_DETAIL_TBL:
|
||||
return new TradeDetailTableBuilder(protos).build();
|
||||
case TRANSACTION_TBL:
|
||||
return new TransactionTableBuilder(protos).build();
|
||||
default:
|
||||
throw new IllegalArgumentException("invalid cli table type " + tableType.name());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.table.builder;
|
||||
|
||||
/**
|
||||
* Table column name constants.
|
||||
*/
|
||||
class TableBuilderConstants {
|
||||
static final String COL_HEADER_ADDRESS = "%-3s Address";
|
||||
static final String COL_HEADER_AMOUNT = "Amount";
|
||||
static final String COL_HEADER_AMOUNT_IN_BTC = "Amount in BTC";
|
||||
static final String COL_HEADER_AMOUNT_RANGE = "BTC(min - max)";
|
||||
static final String COL_HEADER_AVAILABLE_BALANCE = "Available Balance";
|
||||
static final String COL_HEADER_AVAILABLE_CONFIRMED_BALANCE = "Available Confirmed Balance";
|
||||
static final String COL_HEADER_UNCONFIRMED_CHANGE_BALANCE = "Unconfirmed Change Balance";
|
||||
static final String COL_HEADER_RESERVED_BALANCE = "Reserved Balance";
|
||||
static final String COL_HEADER_TOTAL_AVAILABLE_BALANCE = "Total Available Balance";
|
||||
static final String COL_HEADER_LOCKED_BALANCE = "Locked Balance";
|
||||
static final String COL_HEADER_LOCKED_FOR_VOTING_BALANCE = "Locked For Voting Balance";
|
||||
static final String COL_HEADER_LOCKUP_BONDS_BALANCE = "Lockup Bonds Balance";
|
||||
static final String COL_HEADER_UNLOCKING_BONDS_BALANCE = "Unlocking Bonds Balance";
|
||||
static final String COL_HEADER_UNVERIFIED_BALANCE = "Unverified Balance";
|
||||
static final String COL_HEADER_BSQ_SWAP_TRADE_ROLE = "My BSQ Swap Role";
|
||||
static final String COL_HEADER_BUYER_DEPOSIT = "Buyer Deposit (BTC)";
|
||||
static final String COL_HEADER_SELLER_DEPOSIT = "Seller Deposit (BTC)";
|
||||
static final String COL_HEADER_CONFIRMATIONS = "Confirmations";
|
||||
static final String COL_HEADER_DEVIATION = "Deviation";
|
||||
static final String COL_HEADER_IS_USED_ADDRESS = "Is Used";
|
||||
static final String COL_HEADER_CREATION_DATE = "Creation Date (UTC)";
|
||||
static final String COL_HEADER_CURRENCY = "Currency";
|
||||
static final String COL_HEADER_DATE_TIME = "Date/Time (UTC)";
|
||||
static final String COL_HEADER_DETAILED_AMOUNT = "Amount(%-3s)";
|
||||
static final String COL_HEADER_DETAILED_PRICE = "Price in %-3s for 1 BTC";
|
||||
static final String COL_HEADER_DETAILED_PRICE_OF_ALTCOIN = "Price in BTC for 1 %-3s";
|
||||
static final String COL_HEADER_DIRECTION = "Buy/Sell";
|
||||
static final String COL_HEADER_ENABLED = "Enabled";
|
||||
static final String COL_HEADER_MARKET = "Market";
|
||||
static final String COL_HEADER_NAME = "Name";
|
||||
static final String COL_HEADER_OFFER_TYPE = "Offer Type";
|
||||
static final String COL_HEADER_PAYMENT_METHOD = "Payment Method";
|
||||
static final String COL_HEADER_PRICE = "Price";
|
||||
static final String COL_HEADER_STATUS = "Status";
|
||||
static final String COL_HEADER_TRADE_ALTCOIN_BUYER_ADDRESS = "%-3s Buyer Address";
|
||||
static final String COL_HEADER_TRADE_BUYER_COST = "Buyer Cost(%-3s)";
|
||||
static final String COL_HEADER_TRADE_DEPOSIT_CONFIRMED = "Deposit Confirmed";
|
||||
static final String COL_HEADER_TRADE_DEPOSIT_PUBLISHED = "Deposit Published";
|
||||
static final String COL_HEADER_TRADE_PAYMENT_SENT = "%-3s Sent";
|
||||
static final String COL_HEADER_TRADE_PAYMENT_RECEIVED = "%-3s Received";
|
||||
static final String COL_HEADER_TRADE_PAYOUT_PUBLISHED = "Payout Published";
|
||||
static final String COL_HEADER_TRADE_WITHDRAWN = "Withdrawn";
|
||||
static final String COL_HEADER_TRADE_ID = "Trade ID";
|
||||
static final String COL_HEADER_TRADE_ROLE = "My Role";
|
||||
static final String COL_HEADER_TRADE_SHORT_ID = "ID";
|
||||
static final String COL_HEADER_TRADE_MAKER_FEE = "Maker Fee(%-3s)";
|
||||
static final String COL_HEADER_TRADE_TAKER_FEE = "Taker Fee(%-3s)";
|
||||
static final String COL_HEADER_TRADE_FEE = "Trade Fee";
|
||||
static final String COL_HEADER_TRIGGER_PRICE = "Trigger Price(%-3s)";
|
||||
static final String COL_HEADER_TX_ID = "Tx ID";
|
||||
static final String COL_HEADER_TX_INPUT_SUM = "Tx Inputs (BTC)";
|
||||
static final String COL_HEADER_TX_OUTPUT_SUM = "Tx Outputs (BTC)";
|
||||
static final String COL_HEADER_TX_FEE = "Tx Fee (BTC)";
|
||||
static final String COL_HEADER_TX_SIZE = "Tx Size (Bytes)";
|
||||
static final String COL_HEADER_TX_IS_CONFIRMED = "Is Confirmed";
|
||||
static final String COL_HEADER_TX_MEMO = "Memo";
|
||||
static final String COL_HEADER_VOLUME_RANGE = "%-3s(min - max)";
|
||||
static final String COL_HEADER_UUID = "ID";
|
||||
}
|
34
cli/src/main/java/bisq/cli/table/builder/TableType.java
Normal file
34
cli/src/main/java/bisq/cli/table/builder/TableType.java
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.table.builder;
|
||||
|
||||
/**
|
||||
* Used as param in TableBuilder constructor instead of inspecting
|
||||
* protos to find out what kind of CLI output table should be built.
|
||||
*/
|
||||
public enum TableType {
|
||||
ADDRESS_BALANCE_TBL,
|
||||
BTC_BALANCE_TBL,
|
||||
CLOSED_TRADES_TBL,
|
||||
FAILED_TRADES_TBL,
|
||||
OFFER_TBL,
|
||||
OPEN_TRADES_TBL,
|
||||
PAYMENT_ACCOUNT_TBL,
|
||||
TRADE_DETAIL_TBL,
|
||||
TRANSACTION_TBL
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.table.builder;
|
||||
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
|
||||
import static java.lang.String.format;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.table.Table;
|
||||
import bisq.cli.table.column.Column;
|
||||
|
||||
/**
|
||||
* Builds a {@code bisq.cli.table.Table} from a {@code bisq.proto.grpc.TradeInfo} object.
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
class TradeDetailTableBuilder extends AbstractTradeListBuilder {
|
||||
|
||||
TradeDetailTableBuilder(List<?> protos) {
|
||||
super(TRADE_DETAIL_TBL, protos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a single row trade detail table.
|
||||
* @return Table containing one row
|
||||
*/
|
||||
@Override
|
||||
public Table build() {
|
||||
// A trade detail table only has one row.
|
||||
var trade = trades.get(0);
|
||||
populateColumns(trade);
|
||||
List<Column<?>> columns = defineColumnList(trade);
|
||||
return new Table(columns.toArray(new Column<?>[0]));
|
||||
}
|
||||
|
||||
private void populateColumns(TradeInfo trade) {
|
||||
populateBisqV1TradeColumns(trade);
|
||||
}
|
||||
|
||||
private void populateBisqV1TradeColumns(TradeInfo trade) {
|
||||
colTradeId.addRow(trade.getShortId());
|
||||
colRole.addRow(trade.getRole());
|
||||
colPrice.addRow(trade.getPrice());
|
||||
colAmount.addRow(toTradeAmount.apply(trade));
|
||||
colMinerTxFee.addRow(toMyMinerTxFee.apply(trade));
|
||||
colBisqTradeFee.addRow(toMyMakerOrTakerFee.apply(trade));
|
||||
colIsDepositPublished.addRow(trade.getIsDepositPublished());
|
||||
colIsDepositConfirmed.addRow(trade.getIsDepositUnlocked());
|
||||
colTradeCost.addRow(toTradeVolumeAsString.apply(trade));
|
||||
colIsPaymentStartedMessageSent.addRow(trade.getIsPaymentSent());
|
||||
colIsPaymentReceivedMessageSent.addRow(trade.getIsPaymentReceived());
|
||||
colIsPayoutPublished.addRow(trade.getIsPayoutPublished());
|
||||
colIsCompleted.addRow(trade.getIsCompleted());
|
||||
if (colAltcoinReceiveAddressColumn != null)
|
||||
colAltcoinReceiveAddressColumn.addRow(toAltcoinReceiveAddress.apply(trade));
|
||||
}
|
||||
|
||||
private List<Column<?>> defineColumnList(TradeInfo trade) {
|
||||
return getBisqV1TradeColumnList();
|
||||
}
|
||||
|
||||
private List<Column<?>> getBisqV1TradeColumnList() {
|
||||
List<Column<?>> columns = new ArrayList<>() {{
|
||||
add(colTradeId);
|
||||
add(colRole);
|
||||
add(colPrice.justify());
|
||||
add(colAmount.asStringColumn());
|
||||
add(colMinerTxFee.asStringColumn());
|
||||
add(colBisqTradeFee.asStringColumn());
|
||||
add(colIsDepositPublished.asStringColumn());
|
||||
add(colIsDepositConfirmed.asStringColumn());
|
||||
add(colTradeCost.justify());
|
||||
add(colIsPaymentStartedMessageSent.asStringColumn());
|
||||
add(colIsPaymentReceivedMessageSent.asStringColumn());
|
||||
add(colIsPayoutPublished.asStringColumn());
|
||||
add(colIsCompleted.asStringColumn());
|
||||
}};
|
||||
|
||||
if (colAltcoinReceiveAddressColumn != null)
|
||||
columns.add(colAltcoinReceiveAddressColumn);
|
||||
|
||||
return columns;
|
||||
}
|
||||
}
|
@ -0,0 +1,267 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.table.builder;
|
||||
|
||||
import bisq.proto.grpc.ContractInfo;
|
||||
import bisq.proto.grpc.OfferInfo;
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static bisq.cli.table.builder.TableBuilderConstants.*;
|
||||
import static bisq.cli.table.builder.TableType.CLOSED_TRADES_TBL;
|
||||
import static bisq.cli.table.builder.TableType.FAILED_TRADES_TBL;
|
||||
import static bisq.cli.table.builder.TableType.OPEN_TRADES_TBL;
|
||||
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
|
||||
import static bisq.cli.table.column.AltcoinVolumeColumn.DISPLAY_MODE.ALTCOIN_VOLUME;
|
||||
import static bisq.cli.table.column.AltcoinVolumeColumn.DISPLAY_MODE.BSQ_VOLUME;
|
||||
import static bisq.cli.table.column.Column.JUSTIFICATION.LEFT;
|
||||
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
|
||||
import static java.lang.String.format;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.table.column.AltcoinVolumeColumn;
|
||||
import bisq.cli.table.column.BooleanColumn;
|
||||
import bisq.cli.table.column.BtcColumn;
|
||||
import bisq.cli.table.column.Column;
|
||||
import bisq.cli.table.column.Iso8601DateTimeColumn;
|
||||
import bisq.cli.table.column.MixedTradeFeeColumn;
|
||||
import bisq.cli.table.column.SatoshiColumn;
|
||||
import bisq.cli.table.column.StringColumn;
|
||||
|
||||
/**
|
||||
* Convenience for supplying column definitions to
|
||||
* open/closed/failed/detail trade table builders.
|
||||
*/
|
||||
@Slf4j
|
||||
class TradeTableColumnSupplier {
|
||||
|
||||
@Getter
|
||||
private final TableType tableType;
|
||||
@Getter
|
||||
private final List<TradeInfo> trades;
|
||||
|
||||
public TradeTableColumnSupplier(TableType tableType, List<TradeInfo> trades) {
|
||||
this.tableType = tableType;
|
||||
this.trades = trades;
|
||||
}
|
||||
|
||||
private final Supplier<Boolean> isTradeDetailTblBuilder = () -> getTableType().equals(TRADE_DETAIL_TBL);
|
||||
private final Supplier<Boolean> isOpenTradeTblBuilder = () -> getTableType().equals(OPEN_TRADES_TBL);
|
||||
private final Supplier<Boolean> isClosedTradeTblBuilder = () -> getTableType().equals(CLOSED_TRADES_TBL);
|
||||
private final Supplier<Boolean> isFailedTradeTblBuilder = () -> getTableType().equals(FAILED_TRADES_TBL);
|
||||
private final Supplier<TradeInfo> firstRow = () -> getTrades().get(0);
|
||||
private final Predicate<OfferInfo> isFiatOffer = (o) -> o.getBaseCurrencyCode().equals("BTC");
|
||||
private final Predicate<TradeInfo> isFiatTrade = (t) -> isFiatOffer.test(t.getOffer());
|
||||
private final Predicate<TradeInfo> isTaker = (t) -> t.getRole().toLowerCase().contains("taker");
|
||||
|
||||
final Supplier<StringColumn> tradeIdColumn = () -> isTradeDetailTblBuilder.get()
|
||||
? new StringColumn(COL_HEADER_TRADE_SHORT_ID)
|
||||
: new StringColumn(COL_HEADER_TRADE_ID);
|
||||
|
||||
final Supplier<Iso8601DateTimeColumn> createDateColumn = () -> isTradeDetailTblBuilder.get()
|
||||
? null
|
||||
: new Iso8601DateTimeColumn(COL_HEADER_DATE_TIME);
|
||||
|
||||
final Supplier<StringColumn> marketColumn = () -> isTradeDetailTblBuilder.get()
|
||||
? null
|
||||
: new StringColumn(COL_HEADER_MARKET);
|
||||
|
||||
private final Function<TradeInfo, Column<String>> toDetailedPriceColumn = (t) -> {
|
||||
String colHeader = isFiatTrade.test(t)
|
||||
? format(COL_HEADER_DETAILED_PRICE, t.getOffer().getCounterCurrencyCode())
|
||||
: format(COL_HEADER_DETAILED_PRICE_OF_ALTCOIN, t.getOffer().getBaseCurrencyCode());
|
||||
return new StringColumn(colHeader, RIGHT);
|
||||
};
|
||||
|
||||
final Supplier<Column<String>> priceColumn = () -> isTradeDetailTblBuilder.get()
|
||||
? toDetailedPriceColumn.apply(firstRow.get())
|
||||
: new StringColumn(COL_HEADER_PRICE, RIGHT);
|
||||
|
||||
final Supplier<Column<String>> priceDeviationColumn = () -> isTradeDetailTblBuilder.get()
|
||||
? null
|
||||
: new StringColumn(COL_HEADER_DEVIATION, RIGHT);
|
||||
|
||||
final Supplier<StringColumn> currencyColumn = () -> isTradeDetailTblBuilder.get()
|
||||
? null
|
||||
: new StringColumn(COL_HEADER_CURRENCY);
|
||||
|
||||
private final Function<TradeInfo, Column<Long>> toDetailedAmountColumn = (t) -> {
|
||||
String headerCurrencyCode = t.getOffer().getBaseCurrencyCode();
|
||||
String colHeader = format(COL_HEADER_DETAILED_AMOUNT, headerCurrencyCode);
|
||||
AltcoinVolumeColumn.DISPLAY_MODE displayMode = headerCurrencyCode.equals("BSQ") ? BSQ_VOLUME : ALTCOIN_VOLUME;
|
||||
return isFiatTrade.test(t)
|
||||
? new SatoshiColumn(colHeader)
|
||||
: new AltcoinVolumeColumn(colHeader, displayMode);
|
||||
};
|
||||
|
||||
// Can be fiat, btc or altcoin amount represented as longs. Placing the decimal
|
||||
// in the displayed string representation is done in the Column implementation.
|
||||
final Supplier<Column<Long>> amountColumn = () -> isTradeDetailTblBuilder.get()
|
||||
? toDetailedAmountColumn.apply(firstRow.get())
|
||||
: new BtcColumn(COL_HEADER_AMOUNT_IN_BTC);
|
||||
|
||||
final Supplier<StringColumn> mixedAmountColumn = () -> isTradeDetailTblBuilder.get()
|
||||
? null
|
||||
: new StringColumn(COL_HEADER_AMOUNT, RIGHT);
|
||||
|
||||
final Supplier<Column<Long>> minerTxFeeColumn = () -> isTradeDetailTblBuilder.get() || isClosedTradeTblBuilder.get()
|
||||
? new SatoshiColumn(COL_HEADER_TX_FEE)
|
||||
: null;
|
||||
|
||||
final Supplier<MixedTradeFeeColumn> mixedTradeFeeColumn = () -> isTradeDetailTblBuilder.get()
|
||||
? null
|
||||
: new MixedTradeFeeColumn(COL_HEADER_TRADE_FEE);
|
||||
|
||||
final Supplier<StringColumn> paymentMethodColumn = () -> isTradeDetailTblBuilder.get() || isClosedTradeTblBuilder.get()
|
||||
? null
|
||||
: new StringColumn(COL_HEADER_PAYMENT_METHOD, LEFT);
|
||||
|
||||
final Supplier<StringColumn> roleColumn = () -> {
|
||||
return isTradeDetailTblBuilder.get() || isOpenTradeTblBuilder.get() || isFailedTradeTblBuilder.get()
|
||||
? new StringColumn(COL_HEADER_TRADE_ROLE)
|
||||
: null;
|
||||
};
|
||||
|
||||
final Function<String, Column<Long>> toSecurityDepositColumn = (name) -> isClosedTradeTblBuilder.get()
|
||||
? new SatoshiColumn(name)
|
||||
: null;
|
||||
|
||||
final Supplier<StringColumn> offerTypeColumn = () -> isTradeDetailTblBuilder.get()
|
||||
? null
|
||||
: new StringColumn(COL_HEADER_OFFER_TYPE);
|
||||
|
||||
final Supplier<StringColumn> statusDescriptionColumn = () -> isTradeDetailTblBuilder.get()
|
||||
? null
|
||||
: new StringColumn(COL_HEADER_STATUS);
|
||||
|
||||
private final Function<String, Column<Boolean>> toBooleanColumn = BooleanColumn::new;
|
||||
|
||||
final Supplier<Column<Boolean>> depositPublishedColumn = () -> {
|
||||
return isTradeDetailTblBuilder.get()
|
||||
? toBooleanColumn.apply(COL_HEADER_TRADE_DEPOSIT_PUBLISHED)
|
||||
: null;
|
||||
};
|
||||
|
||||
final Supplier<Column<Boolean>> depositConfirmedColumn = () -> {
|
||||
return isTradeDetailTblBuilder.get()
|
||||
? toBooleanColumn.apply(COL_HEADER_TRADE_DEPOSIT_CONFIRMED)
|
||||
: null;
|
||||
|
||||
};
|
||||
|
||||
final Supplier<Column<Boolean>> payoutPublishedColumn = () -> {
|
||||
return isTradeDetailTblBuilder.get()
|
||||
? toBooleanColumn.apply(COL_HEADER_TRADE_PAYOUT_PUBLISHED)
|
||||
: null;
|
||||
};
|
||||
|
||||
final Supplier<Column<Boolean>> fundsWithdrawnColumn = () -> {
|
||||
return isTradeDetailTblBuilder.get()
|
||||
? toBooleanColumn.apply(COL_HEADER_TRADE_WITHDRAWN)
|
||||
: null;
|
||||
};
|
||||
|
||||
final Supplier<Column<Long>> bisqTradeDetailFeeColumn = () -> {
|
||||
if (isTradeDetailTblBuilder.get()) {
|
||||
TradeInfo t = firstRow.get();
|
||||
String headerCurrencyCode = "XMR";
|
||||
String colHeader = isTaker.test(t)
|
||||
? format(COL_HEADER_TRADE_TAKER_FEE, headerCurrencyCode)
|
||||
: format(COL_HEADER_TRADE_MAKER_FEE, headerCurrencyCode);
|
||||
return new SatoshiColumn(colHeader, false);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
final Function<TradeInfo, String> toPaymentCurrencyCode = (t) ->
|
||||
isFiatTrade.test(t)
|
||||
? t.getOffer().getCounterCurrencyCode()
|
||||
: t.getOffer().getBaseCurrencyCode();
|
||||
|
||||
final Supplier<Column<Boolean>> paymentStartedMessageSentColumn = () -> {
|
||||
if (isTradeDetailTblBuilder.get()) {
|
||||
String headerCurrencyCode = toPaymentCurrencyCode.apply(firstRow.get());
|
||||
String colHeader = format(COL_HEADER_TRADE_PAYMENT_SENT, headerCurrencyCode);
|
||||
return new BooleanColumn(colHeader);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
final Supplier<Column<Boolean>> paymentReceivedMessageSentColumn = () -> {
|
||||
if (isTradeDetailTblBuilder.get()) {
|
||||
String headerCurrencyCode = toPaymentCurrencyCode.apply(firstRow.get());
|
||||
String colHeader = format(COL_HEADER_TRADE_PAYMENT_RECEIVED, headerCurrencyCode);
|
||||
return new BooleanColumn(colHeader);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
final Supplier<Column<String>> tradeCostColumn = () -> {
|
||||
if (isTradeDetailTblBuilder.get()) {
|
||||
TradeInfo t = firstRow.get();
|
||||
String headerCurrencyCode = t.getOffer().getCounterCurrencyCode();
|
||||
String colHeader = format(COL_HEADER_TRADE_BUYER_COST, headerCurrencyCode);
|
||||
return new StringColumn(colHeader, RIGHT);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
final Predicate<TradeInfo> showAltCoinBuyerAddress = (t) -> {
|
||||
if (isFiatTrade.test(t)) {
|
||||
return false;
|
||||
} else {
|
||||
ContractInfo contract = t.getContract();
|
||||
boolean isBuyerMakerAndSellerTaker = contract.getIsBuyerMakerAndSellerTaker();
|
||||
if (isTaker.test(t)) {
|
||||
return !isBuyerMakerAndSellerTaker;
|
||||
} else {
|
||||
return isBuyerMakerAndSellerTaker;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Nullable
|
||||
final Supplier<Column<String>> altcoinReceiveAddressColumn = () -> {
|
||||
if (isTradeDetailTblBuilder.get()) {
|
||||
TradeInfo t = firstRow.get();
|
||||
if (showAltCoinBuyerAddress.test(t)) {
|
||||
String headerCurrencyCode = toPaymentCurrencyCode.apply(t);
|
||||
String colHeader = format(COL_HEADER_TRADE_ALTCOIN_BUYER_ADDRESS, headerCurrencyCode);
|
||||
return new StringColumn(colHeader);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.table.builder;
|
||||
|
||||
import bisq.proto.grpc.TxInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static bisq.cli.table.builder.TableBuilderConstants.*;
|
||||
import static bisq.cli.table.builder.TableType.TRANSACTION_TBL;
|
||||
|
||||
|
||||
|
||||
import bisq.cli.table.Table;
|
||||
import bisq.cli.table.column.BooleanColumn;
|
||||
import bisq.cli.table.column.Column;
|
||||
import bisq.cli.table.column.LongColumn;
|
||||
import bisq.cli.table.column.SatoshiColumn;
|
||||
import bisq.cli.table.column.StringColumn;
|
||||
|
||||
/**
|
||||
* Builds a {@code bisq.cli.table.Table} from a {@code bisq.proto.grpc.TxInfo} object.
|
||||
*/
|
||||
class TransactionTableBuilder extends AbstractTableBuilder {
|
||||
|
||||
// Default columns not dynamically generated with tx info.
|
||||
private final Column<String> colTxId;
|
||||
private final Column<Boolean> colIsConfirmed;
|
||||
private final Column<Long> colInputSum;
|
||||
private final Column<Long> colOutputSum;
|
||||
private final Column<Long> colTxFee;
|
||||
private final Column<Long> colTxSize;
|
||||
|
||||
TransactionTableBuilder(List<?> protos) {
|
||||
super(TRANSACTION_TBL, protos);
|
||||
this.colTxId = new StringColumn(COL_HEADER_TX_ID);
|
||||
this.colIsConfirmed = new BooleanColumn(COL_HEADER_TX_IS_CONFIRMED);
|
||||
this.colInputSum = new SatoshiColumn(COL_HEADER_TX_INPUT_SUM);
|
||||
this.colOutputSum = new SatoshiColumn(COL_HEADER_TX_OUTPUT_SUM);
|
||||
this.colTxFee = new SatoshiColumn(COL_HEADER_TX_FEE);
|
||||
this.colTxSize = new LongColumn(COL_HEADER_TX_SIZE);
|
||||
}
|
||||
|
||||
public Table build() {
|
||||
// TODO Add 'gettransactions' api method & show multiple tx in the console.
|
||||
// For now, a tx tbl is only one row.
|
||||
TxInfo tx = (TxInfo) protos.get(0);
|
||||
|
||||
// Declare the columns derived from tx info.
|
||||
|
||||
@Nullable
|
||||
Column<String> colMemo = tx.getMemo().isEmpty()
|
||||
? null
|
||||
: new StringColumn(COL_HEADER_TX_MEMO);
|
||||
|
||||
// Populate columns with tx info.
|
||||
|
||||
colTxId.addRow(tx.getTxId());
|
||||
colIsConfirmed.addRow(!tx.getIsPending());
|
||||
colInputSum.addRow(tx.getInputSum());
|
||||
colOutputSum.addRow(tx.getOutputSum());
|
||||
colTxFee.addRow(tx.getFee());
|
||||
colTxSize.addRow((long) tx.getSize());
|
||||
if (colMemo != null)
|
||||
colMemo.addRow(tx.getMemo());
|
||||
|
||||
// Define and return the table instance with populated columns.
|
||||
|
||||
if (colMemo != null) {
|
||||
return new Table(colTxId,
|
||||
colIsConfirmed.asStringColumn(),
|
||||
colInputSum.asStringColumn(),
|
||||
colOutputSum.asStringColumn(),
|
||||
colTxFee.asStringColumn(),
|
||||
colTxSize.asStringColumn(),
|
||||
colMemo);
|
||||
} else {
|
||||
return new Table(colTxId,
|
||||
colIsConfirmed.asStringColumn(),
|
||||
colInputSum.asStringColumn(),
|
||||
colOutputSum.asStringColumn(),
|
||||
colTxFee.asStringColumn(),
|
||||
colTxSize.asStringColumn());
|
||||
}
|
||||
}
|
||||
}
|
86
cli/src/main/java/bisq/cli/table/column/AbstractColumn.java
Normal file
86
cli/src/main/java/bisq/cli/table/column/AbstractColumn.java
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.table.column;
|
||||
|
||||
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
|
||||
import static com.google.common.base.Strings.padEnd;
|
||||
import static com.google.common.base.Strings.padStart;
|
||||
|
||||
/**
|
||||
* Partial implementation of the {@link Column} interface.
|
||||
*/
|
||||
abstract class AbstractColumn<C extends Column<T>, T> implements Column<T> {
|
||||
|
||||
// We create an encapsulated StringColumn up front to populate with formatted
|
||||
// strings in each this.addRow(Long value) call. But we will not know how
|
||||
// to justify the cached, formatted string until the column is fully populated.
|
||||
protected final StringColumn stringColumn;
|
||||
|
||||
// The name field is not final, so it can be re-set for column alignment.
|
||||
protected String name;
|
||||
protected final JUSTIFICATION justification;
|
||||
// The max width is not known until after column is fully populated.
|
||||
protected int maxWidth;
|
||||
|
||||
public AbstractColumn(String name, JUSTIFICATION justification) {
|
||||
this.name = name;
|
||||
this.justification = justification;
|
||||
this.stringColumn = this instanceof StringColumn ? null : new StringColumn(name, justification);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return maxWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JUSTIFICATION getJustification() {
|
||||
return this.justification;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Column<T> justify() {
|
||||
if (this instanceof StringColumn && this.justification.equals(RIGHT))
|
||||
return this.justify();
|
||||
else
|
||||
return this; // no-op
|
||||
}
|
||||
|
||||
protected final String toJustifiedString(String s) {
|
||||
switch (justification) {
|
||||
case LEFT:
|
||||
return padEnd(s, maxWidth, ' ');
|
||||
case RIGHT:
|
||||
return padStart(s, maxWidth, ' ');
|
||||
case NONE:
|
||||
default:
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.table.column;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static bisq.cli.table.column.Column.JUSTIFICATION.RIGHT;
|
||||
|
||||
/**
|
||||
* For displaying altcoin volume with appropriate precision.
|
||||
*/
|
||||
public class AltcoinVolumeColumn extends LongColumn {
|
||||
|
||||
public enum DISPLAY_MODE {
|
||||
ALTCOIN_VOLUME,
|
||||
BSQ_VOLUME,
|
||||
}
|
||||
|
||||
private final DISPLAY_MODE displayMode;
|
||||
|
||||
// The default AltcoinVolumeColumn JUSTIFICATION is RIGHT.
|
||||
public AltcoinVolumeColumn(String name, DISPLAY_MODE displayMode) {
|
||||
this(name, RIGHT, displayMode);
|
||||
}
|
||||
|
||||
public AltcoinVolumeColumn(String name,
|
||||
JUSTIFICATION justification,
|
||||
DISPLAY_MODE displayMode) {
|
||||
super(name, justification);
|
||||
this.displayMode = displayMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRow(Long value) {
|
||||
rows.add(value);
|
||||
|
||||
String s = toFormattedString.apply(value, displayMode);
|
||||
stringColumn.addRow(s);
|
||||
|
||||
if (isNewMaxWidth.test(s))
|
||||
maxWidth = s.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRowAsFormattedString(int rowIndex) {
|
||||
return toFormattedString.apply(getRow(rowIndex), displayMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringColumn asStringColumn() {
|
||||
// We cached the formatted altcoin value strings, but we did
|
||||
// not know how much padding each string needed until now.
|
||||
IntStream.range(0, stringColumn.getRows().size()).forEach(rowIndex -> {
|
||||
String unjustified = stringColumn.getRow(rowIndex);
|
||||
String justified = stringColumn.toJustifiedString(unjustified);
|
||||
stringColumn.updateRow(rowIndex, justified);
|
||||
});
|
||||
return this.stringColumn;
|
||||
}
|
||||
|
||||
private final BiFunction<Long, DISPLAY_MODE, String> toFormattedString = (value, displayMode) -> {
|
||||
switch (displayMode) {
|
||||
case ALTCOIN_VOLUME:
|
||||
return value > 0 ? new BigDecimal(value).movePointLeft(8).toString() : "";
|
||||
case BSQ_VOLUME:
|
||||
return value > 0 ? new BigDecimal(value).movePointLeft(2).toString() : "";
|
||||
default:
|
||||
throw new IllegalStateException("invalid display mode: " + displayMode);
|
||||
}
|
||||
};
|
||||
}
|
131
cli/src/main/java/bisq/cli/table/column/BooleanColumn.java
Normal file
131
cli/src/main/java/bisq/cli/table/column/BooleanColumn.java
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.cli.table.column;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static bisq.cli.table.column.Column.JUSTIFICATION.LEFT;
|
||||
|
||||
/**
|
||||
* For displaying boolean values as YES, NO, or user's choice for 'true' and 'false'.
|
||||
*/
|
||||
public class BooleanColumn extends AbstractColumn<BooleanColumn, Boolean> {
|
||||
|
||||
private static final String DEFAULT_TRUE_AS_STRING = "YES";
|
||||
private static final String DEFAULT_FALSE_AS_STRING = "NO";
|
||||
|
||||
private final List<Boolean> rows = new ArrayList<>();
|
||||
|
||||
private final Predicate<String> isNewMaxWidth = (s) -> s != null && !s.isEmpty() && s.length() > maxWidth;
|
||||
|
||||
private final String trueAsString;
|
||||
private final String falseAsString;
|
||||
|
||||
// The default BooleanColumn JUSTIFICATION is LEFT.
|
||||
// The default BooleanColumn True AsString value is YES.
|
||||
// The default BooleanColumn False AsString value is NO.
|
||||
public BooleanColumn(String name) {
|
||||
this(name, LEFT, DEFAULT_TRUE_AS_STRING, DEFAULT_FALSE_AS_STRING);
|
||||
}
|
||||
|
||||
// Use this constructor to override default LEFT justification.
|
||||
@SuppressWarnings("unused")
|
||||
public BooleanColumn(String name, JUSTIFICATION justification) {
|
||||
this(name, justification, DEFAULT_TRUE_AS_STRING, DEFAULT_FALSE_AS_STRING);
|
||||
}
|
||||
|
||||
// Use this constructor to override default true/false as string defaults.
|
||||
public BooleanColumn(String name, String trueAsString, String falseAsString) {
|
||||
this(name, LEFT, trueAsString, falseAsString);
|
||||
}
|
||||
|
||||
// Use this constructor to override default LEFT justification.
|
||||
public BooleanColumn(String name,
|
||||
JUSTIFICATION justification,
|
||||
String trueAsString,
|
||||
String falseAsString) {
|
||||
super(name, justification);
|
||||
this.trueAsString = trueAsString;
|
||||
this.falseAsString = falseAsString;
|
||||
this.maxWidth = name.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRow(Boolean value) {
|
||||
rows.add(value);
|
||||
|
||||
// We do not know how much padding each StringColumn value needs until it has all the values.
|
||||
String s = asString(value);
|
||||
stringColumn.addRow(s);
|
||||
|
||||
if (isNewMaxWidth.test(s))
|
||||
maxWidth = s.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Boolean> getRows() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int rowCount() {
|
||||
return rows.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return rows.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getRow(int rowIndex) {
|
||||
return rows.get(rowIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRow(int rowIndex, Boolean newValue) {
|
||||
rows.set(rowIndex, newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRowAsFormattedString(int rowIndex) {
|
||||
return getRow(rowIndex)
|
||||
? trueAsString
|
||||
: falseAsString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringColumn asStringColumn() {
|
||||
// We cached the formatted satoshi strings, but we did
|
||||
// not know how much padding each string needed until now.
|
||||
IntStream.range(0, stringColumn.getRows().size()).forEach(rowIndex -> {
|
||||
String unjustified = stringColumn.getRow(rowIndex);
|
||||
String justified = stringColumn.toJustifiedString(unjustified);
|
||||
stringColumn.updateRow(rowIndex, justified);
|
||||
});
|
||||
return stringColumn;
|
||||
}
|
||||
|
||||
private String asString(boolean value) {
|
||||
return value ? trueAsString : falseAsString;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user