mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-05-02 22:55:09 -04:00
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
539 changed files with 27629 additions and 8178 deletions
|
@ -19,7 +19,7 @@ option adjustments to compensate.
|
||||||
|
|
||||||
**Java SDK**: Version 10, 11, or 12
|
**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**
|
**Git Client**
|
||||||
|
|
||||||
|
@ -252,9 +252,9 @@ To remove a custom withdrawal transaction fee rate preference, and revert to the
|
||||||
$ ./bisq-cli --password=xyz unsettxfeerate
|
$ ./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
|
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`):
|
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
|
$ ./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
|
### Creating Offers
|
||||||
|
|
||||||
The createoffer command is the Api's most complex command (so far), but CLI posix-style options are self-explanatory,
|
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
|
#### Examples
|
||||||
|
|
||||||
The `trade-simulation.sh` script described above is an easy way to figure out how to use this command.
|
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,
|
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
|
Alice had already created an EUR face-to-face payment account with id
|
||||||
`f3c1ec8b-9761-458d-b13d-9039c6892413`, and used this `createoffer` command:
|
`f3c1ec8b-9761-458d-b13d-9039c6892413`, and used this `createoffer` command:
|
||||||
```
|
```
|
||||||
$ ./bisq-cli --password=xyz --port=9998 createoffer \
|
$ ./bisq-cli --password=xyz --port=9998 createoffer \
|
||||||
--payment-account=f3c1ec8b-9761-458d-b13d-9039c6892413 \
|
--payment-account-id=f3c1ec8b-9761-458d-b13d-9039c6892413 \
|
||||||
--direction=BUY \
|
--direction=BUY \
|
||||||
--currency-code=EUR \
|
--currency-code=EUR \
|
||||||
--amount=0.125 \
|
--amount=0.125 \
|
||||||
--fixed-price=30800 \
|
--fixed-price=30800 \
|
||||||
--security-deposit=15.0 \
|
--security-deposit=15.0
|
||||||
--fee-currency=BTC
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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,
|
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:
|
putting up a 15% security deposit, the `createoffer` command to do that would be:
|
||||||
```
|
```
|
||||||
$ ./bisq-cli --password=xyz --port=9998 createoffer \
|
$ ./bisq-cli --password=xyz --port=9998 createoffer \
|
||||||
--payment-account=f3c1ec8b-9761-458d-b13d-9039c6892413 \
|
--payment-account-id=f3c1ec8b-9761-458d-b13d-9039c6892413 \
|
||||||
--direction=SELL \
|
--direction=SELL \
|
||||||
--currency-code=JPY \
|
--currency-code=JPY \
|
||||||
--amount=0.125 \
|
--amount=0.125 \
|
||||||
--market-price-margin=0.5 \
|
--market-price-margin=0.5 \
|
||||||
--security-deposit=15.0 \
|
--security-deposit=15.0
|
||||||
--fee-currency=BTC
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The `trade-simulation.sh` script options that would generate the previous `createoffer` example is:
|
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:
|
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
|
### 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 an Existing Offer
|
||||||
|
|
||||||
Editing existing offers is not yet supported. You can cancel and re-create an offer, but paid transaction fees
|
Offers you create can be edited in various ways:
|
||||||
for the canceled offer will be forfeited.
|
|
||||||
|
- 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
|
### 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
|
$ ./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:
|
with the `takeoffer` command:
|
||||||
```
|
```
|
||||||
$ ./bisq-cli --password=xyz --port=9998 takeoffer \
|
$ ./bisq-cli --password=xyz --port=9998 takeoffer \
|
||||||
--offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
--offer-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea \
|
||||||
--payment-account=fe20cdbd-22be-4b8a-a4b6-d2608ff09d6e \
|
--payment-account-id=fe20cdbd-22be-4b8a-a4b6-d2608ff09d6e
|
||||||
--fee-currency=btc
|
|
||||||
```
|
```
|
||||||
The taken offer will be used to create a trade contract. The next section describes how to use the Api to execute
|
The next section describes how to use the Api to execute a trade.
|
||||||
the trade.
|
|
||||||
|
|
||||||
### Completing Trade Protocol
|
### 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.
|
confirmpaymentstarted Buyer sends seller a message confirming payment has been sent.
|
||||||
confirmpaymentreceived Seller sends buyer a message confirming payment has been received.
|
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
|
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
|
The last two mutually exclusive commands (`closetrade` 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
|
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.
|
or the other command.
|
||||||
|
|
||||||
Each of the CLI commands above takes one argument: `--trade-id=<trade-id>`:
|
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=9998 confirmpaymentstarted --trade-id=<trade-id>
|
||||||
$ ./bisq-cli --password=xyz --port=9999 confirmpaymentreceived --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">]
|
$ ./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
|
## 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:
|
and build with gradle:
|
||||||
|
|
||||||
$ ./gradlew clean build
|
$ ./gradlew clean build
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#
|
#
|
||||||
# Prerequisites:
|
# 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.
|
# - Bisq must be fully built with apitest dao setup files installed.
|
||||||
# Build command: `./gradlew clean build :apitest:installDaoSetup`
|
# Build command: `./gradlew clean build :apitest:installDaoSetup`
|
||||||
|
@ -134,7 +134,7 @@ sleeptraced 3
|
||||||
|
|
||||||
# Show Alice's new offer.
|
# Show Alice's new offer.
|
||||||
printdate "ALICE: Looking at her new $DIRECTION $CURRENCY_CODE 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"
|
printdate "ALICE CLI: $CMD"
|
||||||
OFFER=$($CMD)
|
OFFER=$($CMD)
|
||||||
exitoncommandalert $?
|
exitoncommandalert $?
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# Prerequisites:
|
# 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.
|
# - Bisq must be fully built with apitest dao setup files installed.
|
||||||
# Build command: `./gradlew clean build :apitest:installDaoSetup`
|
# Build command: `./gradlew clean build :apitest:installDaoSetup`
|
||||||
|
@ -94,7 +94,7 @@ while : ; do
|
||||||
|
|
||||||
# Show Alice's new offer.
|
# Show Alice's new offer.
|
||||||
printdate "ALICE $ALICE_ROLE: Looking at her new $DIRECTION $CURRENCY_CODE 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"
|
printdate "ALICE CLI: $CMD"
|
||||||
OFFER=$($CMD)
|
OFFER=$($CMD)
|
||||||
exitoncommandalert $?
|
exitoncommandalert $?
|
||||||
|
|
|
@ -193,7 +193,6 @@ gencreateoffercommand() {
|
||||||
CMD+=" --market-price-margin=$MKT_PRICE_MARGIN"
|
CMD+=" --market-price-margin=$MKT_PRICE_MARGIN"
|
||||||
fi
|
fi
|
||||||
CMD+=" --security-deposit=15.0"
|
CMD+=" --security-deposit=15.0"
|
||||||
CMD+=" --fee-currency=BTC"
|
|
||||||
echo "$CMD"
|
echo "$CMD"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,7 +367,7 @@ waitfortradepaymentsent() {
|
||||||
|
|
||||||
IS_TRADE_PAYMENT_SENT=$(istradepaymentsent "$TRADE_DETAIL")
|
IS_TRADE_PAYMENT_SENT=$(istradepaymentsent "$TRADE_DETAIL")
|
||||||
exitoncommandalert $?
|
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" ]
|
if [ "$IS_TRADE_PAYMENT_SENT" = "YES" ]
|
||||||
then
|
then
|
||||||
DONE=1
|
DONE=1
|
||||||
|
@ -407,7 +406,7 @@ waitfortradepaymentreceived() {
|
||||||
# but we do not need to simulate that in this regtest script.
|
# but we do not need to simulate that in this regtest script.
|
||||||
IS_TRADE_PAYMENT_SENT=$(istradepaymentreceived "$TRADE_DETAIL")
|
IS_TRADE_PAYMENT_SENT=$(istradepaymentreceived "$TRADE_DETAIL")
|
||||||
exitoncommandalert $?
|
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" ]
|
if [ "$IS_TRADE_PAYMENT_SENT" = "YES" ]
|
||||||
then
|
then
|
||||||
DONE=1
|
DONE=1
|
||||||
|
@ -427,7 +426,7 @@ delayconfirmpaymentstarted() {
|
||||||
PORT="$2"
|
PORT="$2"
|
||||||
OFFER_ID="$3"
|
OFFER_ID="$3"
|
||||||
RANDOM_WAIT=$(echo $[$RANDOM % 5 + 1])
|
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"
|
sleeptraced "$RANDOM_WAIT"
|
||||||
CMD="$CLI_BASE --port=$PORT confirmpaymentstarted --trade-id=$OFFER_ID"
|
CMD="$CLI_BASE --port=$PORT confirmpaymentstarted --trade-id=$OFFER_ID"
|
||||||
printdate "$PAYER_CLI: $CMD"
|
printdate "$PAYER_CLI: $CMD"
|
||||||
|
@ -446,7 +445,7 @@ delayconfirmpaymentreceived() {
|
||||||
PORT="$2"
|
PORT="$2"
|
||||||
OFFER_ID="$3"
|
OFFER_ID="$3"
|
||||||
RANDOM_WAIT=$(echo $[$RANDOM % 5 + 1])
|
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"
|
sleeptraced "$RANDOM_WAIT"
|
||||||
CMD="$CLI_BASE --port=$PORT confirmpaymentreceived --trade-id=$OFFER_ID"
|
CMD="$CLI_BASE --port=$PORT confirmpaymentreceived --trade-id=$OFFER_ID"
|
||||||
printdate "$PAYEE_CLI: $CMD"
|
printdate "$PAYEE_CLI: $CMD"
|
||||||
|
@ -457,11 +456,10 @@ delayconfirmpaymentreceived() {
|
||||||
printbreak
|
printbreak
|
||||||
}
|
}
|
||||||
|
|
||||||
# This is a large function that should be broken up if it ever makes sense to not treat a trade
|
# This is a large function that might be split into smaller functions. But we are not testing
|
||||||
# execution simulation as an atomic operation. But we are not testing api methods here, just
|
# api methods here, just demonstrating how to use them to get through the V1 trade protocol with
|
||||||
# demonstrating how to use them to get through the trade protocol. It should work for any trade
|
# the CLI. It should work for any trade between Bob & Alice, as long as Alice is maker, Bob is
|
||||||
# between Bob & Alice, as long as Alice is maker, Bob is taker, and the offer to be taken is the
|
# taker, and the offer to be taken is the first displayed in Bob's getoffers command output.
|
||||||
# first displayed in Bob's getoffers command output.
|
|
||||||
executetrade() {
|
executetrade() {
|
||||||
# Bob list available offers.
|
# Bob list available offers.
|
||||||
printdate "BOB $BOB_ROLE: Looking at $DIRECTION $CURRENCY_CODE offers."
|
printdate "BOB $BOB_ROLE: Looking at $DIRECTION $CURRENCY_CODE offers."
|
||||||
|
@ -532,24 +530,27 @@ executetrade() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Generate some btc blocks
|
# Generate some btc blocks
|
||||||
printdate "Generating btc blocks after fiat transfer."
|
printdate "Generating btc blocks after payment."
|
||||||
genbtcblocks 2 2
|
genbtcblocks 2 2
|
||||||
printbreak
|
printbreak
|
||||||
|
|
||||||
# Complete the trade on the seller side.
|
# Complete the trade on both sides
|
||||||
if [ "$DIRECTION" = "BUY" ]
|
printdate "BOB $BOB_ROLE: Closing trade and keeping funds in Bisq wallet."
|
||||||
then
|
CMD="$CLI_BASE --port=$BOB_PORT closetrade --trade-id=$OFFER_ID"
|
||||||
printdate "BOB $BOB_ROLE: Closing trade by keeping funds in Bisq wallet."
|
printdate "BOB CLI: $CMD"
|
||||||
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
|
|
||||||
KEEP_FUNDS_MSG=$($CMD)
|
KEEP_FUNDS_MSG=$($CMD)
|
||||||
commandalert $? "Could close trade with keepfunds command."
|
commandalert $? "Closed trade with closetrade command."
|
||||||
# Print the keepfunds command's console output.
|
# 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"
|
printdate "$KEEP_FUNDS_MSG"
|
||||||
sleeptraced 3
|
sleeptraced 3
|
||||||
printbreak
|
printbreak
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
#! /bin/bash
|
#! /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
|
# 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.
|
# trade, and the maker's face to face payment account's currency code is used when creating the offer.
|
||||||
#
|
#
|
||||||
# Prerequisites:
|
# 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.
|
# - Bisq must be fully built with apitest dao setup files installed.
|
||||||
# Build command: `./gradlew clean build :apitest:installDaoSetup`
|
# 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`
|
# `$ 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:
|
# 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`
|
# `$ 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:
|
# payment account:
|
||||||
#
|
#
|
||||||
# `$ apitest/scripts/trade-simulation.sh -d sell -c fr -f 38000 -a 0.125`
|
# `$ 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
|
printscriptparams
|
||||||
printbreak
|
printbreak
|
||||||
|
|
||||||
registerdisputeagents
|
|
||||||
|
|
||||||
# Demonstrate how to create a country based, face to face account.
|
# Demonstrate how to create a country based, face to face account.
|
||||||
showcreatepaymentacctsteps "Alice" "$ALICE_PORT"
|
showcreatepaymentacctsteps "Alice" "$ALICE_PORT"
|
||||||
|
|
||||||
|
@ -96,7 +95,7 @@ sleeptraced 3
|
||||||
|
|
||||||
# Show Alice's new offer.
|
# Show Alice's new offer.
|
||||||
printdate "ALICE $ALICE_ROLE: Looking at her new $DIRECTION $CURRENCY_CODE 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"
|
printdate "ALICE CLI: $CMD"
|
||||||
OFFER=$($CMD)
|
OFFER=$($CMD)
|
||||||
exitoncommandalert $?
|
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;
|
package bisq.apitest;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import static bisq.apitest.Scaffold.EXIT_FAILURE;
|
import static bisq.apitest.Scaffold.EXIT_FAILURE;
|
||||||
import static bisq.apitest.Scaffold.EXIT_SUCCESS;
|
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.err;
|
||||||
import static java.lang.System.exit;
|
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
|
* ApiTestMain is a placeholder for the gradle build file, which requires a valid
|
||||||
* 'mainClassName' property in the :apitest subproject configuration.
|
* '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.
|
* 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:
|
* It can be used to run the regtest environment for release testing:
|
||||||
* bisq-test --shutdownAfterTests=false
|
* 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
|
@Slf4j
|
||||||
public class ApiTestMain {
|
public class ApiTestMain {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
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 {
|
try {
|
||||||
|
log.info("Configuring test harness with options:\n\t{}", String.join("\n\t", args));
|
||||||
Scaffold scaffold = new Scaffold(args).setUp();
|
Scaffold scaffold = new Scaffold(args).setUp();
|
||||||
ApiTestConfig config = scaffold.config;
|
ApiTestConfig config = scaffold.config;
|
||||||
|
|
||||||
|
@ -77,4 +86,9 @@ public class ApiTestMain {
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String[] getAppendedArgs(String[] args) {
|
||||||
|
File rateMeterInterceptorConfig = getTestRateMeterInterceptorConfig();
|
||||||
|
return appendCallRateMeteringConfigPathOpt(args, rateMeterInterceptorConfig);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,6 +160,7 @@ public class Scaffold {
|
||||||
try {
|
try {
|
||||||
log.info("Shutting down executor service ...");
|
log.info("Shutting down executor service ...");
|
||||||
executor.shutdownNow();
|
executor.shutdownNow();
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
executor.awaitTermination(config.supportingApps.size() * 2000L, MILLISECONDS);
|
executor.awaitTermination(config.supportingApps.size() * 2000L, MILLISECONDS);
|
||||||
|
|
||||||
SetupTask[] orderedTasks = new SetupTask[]{
|
SetupTask[] orderedTasks = new SetupTask[]{
|
||||||
|
@ -189,7 +190,7 @@ public class Scaffold {
|
||||||
MILLISECONDS.sleep(1000);
|
MILLISECONDS.sleep(1000);
|
||||||
if (p.hasShutdownExceptions()) {
|
if (p.hasShutdownExceptions()) {
|
||||||
// We log shutdown exceptions, but do not throw any from here
|
// 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);
|
p.logExceptions(p.getShutdownExceptions(), log);
|
||||||
|
|
||||||
// We cache only the 1st shutdown exception and move on to the
|
// 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 {
|
private void installCallRateMeteringConfiguration(String dataDir) throws IOException, InterruptedException {
|
||||||
|
if (config.callRateMeteringConfigPath.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
File testRateMeteringFile = new File(config.callRateMeteringConfigPath);
|
File testRateMeteringFile = new File(config.callRateMeteringConfigPath);
|
||||||
if (!testRateMeteringFile.exists())
|
if (!testRateMeteringFile.exists())
|
||||||
throw new FileNotFoundException(
|
throw new FileNotFoundException(
|
||||||
|
@ -289,49 +293,49 @@ public class Scaffold {
|
||||||
startBisqApp(bobdesktop, executor, countdownLatch);
|
startBisqApp(bobdesktop, executor, countdownLatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startBisqApp(HavenoAppConfig havenoAppConfig,
|
private void startBisqApp(HavenoAppConfig HavenoAppConfig,
|
||||||
ExecutorService executor,
|
ExecutorService executor,
|
||||||
CountDownLatch countdownLatch)
|
CountDownLatch countdownLatch)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
|
|
||||||
HavenoProcess bisqProcess = createBisqProcess(havenoAppConfig);
|
HavenoProcess HavenoProcess = createHavenoProcess(HavenoAppConfig);
|
||||||
switch (havenoAppConfig) {
|
switch (HavenoAppConfig) {
|
||||||
case seednode:
|
case seednode:
|
||||||
seedNodeTask = new SetupTask(bisqProcess, countdownLatch);
|
seedNodeTask = new SetupTask(HavenoProcess, countdownLatch);
|
||||||
seedNodeTaskFuture = executor.submit(seedNodeTask);
|
seedNodeTaskFuture = executor.submit(seedNodeTask);
|
||||||
break;
|
break;
|
||||||
case arbdaemon:
|
case arbdaemon:
|
||||||
case arbdesktop:
|
case arbdesktop:
|
||||||
arbNodeTask = new SetupTask(bisqProcess, countdownLatch);
|
arbNodeTask = new SetupTask(HavenoProcess, countdownLatch);
|
||||||
arbNodeTaskFuture = executor.submit(arbNodeTask);
|
arbNodeTaskFuture = executor.submit(arbNodeTask);
|
||||||
break;
|
break;
|
||||||
case alicedaemon:
|
case alicedaemon:
|
||||||
case alicedesktop:
|
case alicedesktop:
|
||||||
aliceNodeTask = new SetupTask(bisqProcess, countdownLatch);
|
aliceNodeTask = new SetupTask(HavenoProcess, countdownLatch);
|
||||||
aliceNodeTaskFuture = executor.submit(aliceNodeTask);
|
aliceNodeTaskFuture = executor.submit(aliceNodeTask);
|
||||||
break;
|
break;
|
||||||
case bobdaemon:
|
case bobdaemon:
|
||||||
case bobdesktop:
|
case bobdesktop:
|
||||||
bobNodeTask = new SetupTask(bisqProcess, countdownLatch);
|
bobNodeTask = new SetupTask(HavenoProcess, countdownLatch);
|
||||||
bobNodeTaskFuture = executor.submit(bobNodeTask);
|
bobNodeTaskFuture = executor.submit(bobNodeTask);
|
||||||
break;
|
break;
|
||||||
default:
|
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);
|
MILLISECONDS.sleep(config.bisqAppInitTime);
|
||||||
if (bisqProcess.hasStartupExceptions()) {
|
if (HavenoProcess.hasStartupExceptions()) {
|
||||||
bisqProcess.logExceptions(bisqProcess.getStartupExceptions(), log);
|
HavenoProcess.logExceptions(HavenoProcess.getStartupExceptions(), log);
|
||||||
throw new IllegalStateException(bisqProcess.getStartupExceptions().get(0));
|
throw new IllegalStateException(HavenoProcess.getStartupExceptions().get(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private HavenoProcess createBisqProcess(HavenoAppConfig havenoAppConfig)
|
private HavenoProcess createHavenoProcess(HavenoAppConfig HavenoAppConfig)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
HavenoProcess bisqProcess = new HavenoProcess(havenoAppConfig, config);
|
HavenoProcess HavenoProcess = new HavenoProcess(HavenoAppConfig, config);
|
||||||
bisqProcess.verifyAppNotRunning();
|
HavenoProcess.verifyAppNotRunning();
|
||||||
bisqProcess.verifyAppDataDirInstalled();
|
HavenoProcess.verifyAppDataDirInstalled();
|
||||||
return bisqProcess;
|
return HavenoProcess;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyStartupCompleted()
|
private void verifyStartupCompleted()
|
||||||
|
|
|
@ -56,6 +56,8 @@ public class ApiTestConfig {
|
||||||
|
|
||||||
// Global constants
|
// Global constants
|
||||||
public static final String BTC = "BTC";
|
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 XMR = "XMR";
|
||||||
public static final String ARBITRATOR = "arbitrator";
|
public static final String ARBITRATOR = "arbitrator";
|
||||||
public static final String MEDIATOR = "mediator";
|
public static final String MEDIATOR = "mediator";
|
||||||
|
@ -149,7 +151,7 @@ public class ApiTestConfig {
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<String> configFileOpt =
|
ArgumentAcceptingOptionSpec<String> configFileOpt =
|
||||||
parser.accepts(CONFIG_FILE, format("Specify configuration file. " +
|
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()
|
.withRequiredArg()
|
||||||
.ofType(String.class)
|
.ofType(String.class)
|
||||||
.defaultsTo(DEFAULT_CONFIG_FILE_NAME);
|
.defaultsTo(DEFAULT_CONFIG_FILE_NAME);
|
||||||
|
@ -206,55 +208,55 @@ public class ApiTestConfig {
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<Boolean> runSubprojectJarsOpt =
|
ArgumentAcceptingOptionSpec<Boolean> runSubprojectJarsOpt =
|
||||||
parser.accepts(RUN_SUBPROJECT_JARS,
|
parser.accepts(RUN_SUBPROJECT_JARS,
|
||||||
"Run subproject build jars instead of full build jars")
|
"Run subproject build jars instead of full build jars")
|
||||||
.withRequiredArg()
|
.withRequiredArg()
|
||||||
.ofType(Boolean.class)
|
.ofType(Boolean.class)
|
||||||
.defaultsTo(false);
|
.defaultsTo(false);
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<Long> bisqAppInitTimeOpt =
|
ArgumentAcceptingOptionSpec<Long> bisqAppInitTimeOpt =
|
||||||
parser.accepts(BISQ_APP_INIT_TIME,
|
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()
|
.withRequiredArg()
|
||||||
.ofType(Long.class)
|
.ofType(Long.class)
|
||||||
.defaultsTo(5000L);
|
.defaultsTo(5000L);
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<Boolean> skipTestsOpt =
|
ArgumentAcceptingOptionSpec<Boolean> skipTestsOpt =
|
||||||
parser.accepts(SKIP_TESTS,
|
parser.accepts(SKIP_TESTS,
|
||||||
"Start apps, but skip tests")
|
"Start apps, but skip tests")
|
||||||
.withRequiredArg()
|
.withRequiredArg()
|
||||||
.ofType(Boolean.class)
|
.ofType(Boolean.class)
|
||||||
.defaultsTo(false);
|
.defaultsTo(false);
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<Boolean> shutdownAfterTestsOpt =
|
ArgumentAcceptingOptionSpec<Boolean> shutdownAfterTestsOpt =
|
||||||
parser.accepts(SHUTDOWN_AFTER_TESTS,
|
parser.accepts(SHUTDOWN_AFTER_TESTS,
|
||||||
"Terminate all processes after tests")
|
"Terminate all processes after tests")
|
||||||
.withRequiredArg()
|
.withRequiredArg()
|
||||||
.ofType(Boolean.class)
|
.ofType(Boolean.class)
|
||||||
.defaultsTo(true);
|
.defaultsTo(true);
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<String> supportingAppsOpt =
|
ArgumentAcceptingOptionSpec<String> supportingAppsOpt =
|
||||||
parser.accepts(SUPPORTING_APPS,
|
parser.accepts(SUPPORTING_APPS,
|
||||||
"Comma delimited list of supporting apps (bitcoind,seednode,arbdaemon,...")
|
"Comma delimited list of supporting apps (bitcoind,seednode,arbdaemon,...")
|
||||||
.withRequiredArg()
|
.withRequiredArg()
|
||||||
.ofType(String.class)
|
.ofType(String.class)
|
||||||
.defaultsTo("bitcoind,seednode,arbdaemon,alicedaemon,bobdaemon");
|
.defaultsTo("bitcoind,seednode,arbdaemon,alicedaemon,bobdaemon");
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<String> callRateMeteringConfigPathOpt =
|
ArgumentAcceptingOptionSpec<String> callRateMeteringConfigPathOpt =
|
||||||
parser.accepts(CALL_RATE_METERING_CONFIG_PATH,
|
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()
|
.withRequiredArg()
|
||||||
.defaultsTo(EMPTY);
|
.defaultsTo(EMPTY);
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<Boolean> enableBisqDebuggingOpt =
|
ArgumentAcceptingOptionSpec<Boolean> enableBisqDebuggingOpt =
|
||||||
parser.accepts(ENABLE_BISQ_DEBUGGING,
|
parser.accepts(ENABLE_BISQ_DEBUGGING,
|
||||||
"Start Bisq apps with remote debug options")
|
"Start Bisq apps with remote debug options")
|
||||||
.withRequiredArg()
|
.withRequiredArg()
|
||||||
.ofType(Boolean.class)
|
.ofType(Boolean.class)
|
||||||
.defaultsTo(false);
|
.defaultsTo(false);
|
||||||
|
|
||||||
ArgumentAcceptingOptionSpec<Boolean> registerDisputeAgentsOpt =
|
ArgumentAcceptingOptionSpec<Boolean> registerDisputeAgentsOpt =
|
||||||
parser.accepts(REGISTER_DISPUTE_AGENTS,
|
parser.accepts(REGISTER_DISPUTE_AGENTS,
|
||||||
"Register dispute agents in arbitration daemon")
|
"Register dispute agents in arbitration daemon")
|
||||||
.withRequiredArg()
|
.withRequiredArg()
|
||||||
.ofType(Boolean.class)
|
.ofType(Boolean.class)
|
||||||
.defaultsTo(true);
|
.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();
|
File bitcoindExecutable = Paths.get(config.bitcoinPath, "bitcoind").toFile();
|
||||||
if (!bitcoindExecutable.exists() || !bitcoindExecutable.canExecute())
|
if (!bitcoindExecutable.exists() || !bitcoindExecutable.canExecute())
|
||||||
throw new IllegalStateException(format("'%s' cannot be found or executed.%n"
|
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'",
|
" and the 'bitcoinPath' must be configured in 'apitest.properties'",
|
||||||
bitcoindExecutable.getAbsolutePath()));
|
bitcoindExecutable.getAbsolutePath()));
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ import java.util.List;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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 bisq.apitest.config.ApiTestConfig.BASH_PATH_VALUE;
|
||||||
import static java.lang.management.ManagementFactory.getRuntimeMXBean;
|
import static java.lang.management.ManagementFactory.getRuntimeMXBean;
|
||||||
|
@ -33,7 +33,9 @@ import static java.lang.management.ManagementFactory.getRuntimeMXBean;
|
||||||
public class BashCommand {
|
public class BashCommand {
|
||||||
|
|
||||||
private int exitStatus = -1;
|
private int exitStatus = -1;
|
||||||
|
@Nullable
|
||||||
private String output;
|
private String output;
|
||||||
|
@Nullable
|
||||||
private String error;
|
private String error;
|
||||||
|
|
||||||
private final String command;
|
private final String command;
|
||||||
|
@ -92,6 +94,7 @@ public class BashCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO return Optional<String>
|
// TODO return Optional<String>
|
||||||
|
@Nullable
|
||||||
public String getOutput() {
|
public String getOutput() {
|
||||||
return this.output;
|
return this.output;
|
||||||
}
|
}
|
||||||
|
@ -101,7 +104,6 @@ public class BashCommand {
|
||||||
return this.error;
|
return this.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private List<String> tokenizeSystemCommand() {
|
private List<String> tokenizeSystemCommand() {
|
||||||
return new ArrayList<>() {{
|
return new ArrayList<>() {{
|
||||||
add(BASH_PATH_VALUE);
|
add(BASH_PATH_VALUE);
|
||||||
|
|
|
@ -57,15 +57,13 @@ class SystemCommandExecutor {
|
||||||
private ThreadedStreamHandler errorStreamHandler;
|
private ThreadedStreamHandler errorStreamHandler;
|
||||||
|
|
||||||
public SystemCommandExecutor(final List<String> cmdOptions) {
|
public SystemCommandExecutor(final List<String> cmdOptions) {
|
||||||
if (log.isDebugEnabled())
|
|
||||||
log.debug("cmd options {}", cmdOptions.toString());
|
|
||||||
|
|
||||||
if (cmdOptions.isEmpty())
|
if (cmdOptions.isEmpty())
|
||||||
throw new IllegalStateException("No command params specified.");
|
throw new IllegalStateException("No command params specified.");
|
||||||
|
|
||||||
if (cmdOptions.contains("sudo"))
|
if (cmdOptions.contains("sudo"))
|
||||||
throw new IllegalStateException("'sudo' commands are prohibited.");
|
throw new IllegalStateException("'sudo' commands are prohibited.");
|
||||||
|
|
||||||
|
log.trace("System cmd options {}", cmdOptions);
|
||||||
this.cmdOptions = 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;
|
package bisq.apitest;
|
||||||
|
|
||||||
import java.io.File;
|
import java.time.Duration;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
@ -29,23 +30,19 @@ import javax.annotation.Nullable;
|
||||||
|
|
||||||
import org.junit.jupiter.api.TestInfo;
|
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.alicedaemon;
|
||||||
import static bisq.apitest.config.HavenoAppConfig.arbdaemon;
|
import static bisq.apitest.config.HavenoAppConfig.arbdaemon;
|
||||||
import static bisq.apitest.config.HavenoAppConfig.bobdaemon;
|
import static bisq.apitest.config.HavenoAppConfig.bobdaemon;
|
||||||
import static bisq.proto.grpc.DisputeAgentsGrpc.getRegisterDisputeAgentMethod;
|
import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly;
|
||||||
import static bisq.proto.grpc.GetVersionGrpc.getGetVersionMethod;
|
|
||||||
import static java.net.InetAddress.getLoopbackAddress;
|
import static java.net.InetAddress.getLoopbackAddress;
|
||||||
import static java.util.Arrays.stream;
|
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.config.ApiTestConfig;
|
||||||
import bisq.apitest.method.BitcoinCliHelper;
|
import bisq.apitest.method.BitcoinCliHelper;
|
||||||
import bisq.cli.GrpcClient;
|
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'.
|
* Base class for all test types: 'method', 'scenario' and 'e2e'.
|
||||||
|
@ -90,7 +87,7 @@ public class ApiTestCase {
|
||||||
throws InterruptedException, ExecutionException, IOException {
|
throws InterruptedException, ExecutionException, IOException {
|
||||||
String[] params = new String[]{
|
String[] params = new String[]{
|
||||||
"--supportingApps", stream(supportingApps).map(Enum::name).collect(Collectors.joining(",")),
|
"--supportingApps", stream(supportingApps).map(Enum::name).collect(Collectors.joining(",")),
|
||||||
"--callRateMeteringConfigPath", defaultRateMeterInterceptorConfig().getAbsolutePath(),
|
"--callRateMeteringConfigPath", getTestRateMeterInterceptorConfig().getAbsolutePath(),
|
||||||
"--enableBisqDebugging", "false"
|
"--enableBisqDebugging", "false"
|
||||||
};
|
};
|
||||||
setUpScaffold(params);
|
setUpScaffold(params);
|
||||||
|
@ -136,11 +133,7 @@ public class ApiTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void sleep(long ms) {
|
protected static void sleep(long ms) {
|
||||||
try {
|
sleepUninterruptibly(Duration.ofMillis(ms));
|
||||||
MILLISECONDS.sleep(ms);
|
|
||||||
} catch (InterruptedException ignored) {
|
|
||||||
// empty
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final String testName(TestInfo testInfo) {
|
protected final String testName(TestInfo testInfo) {
|
||||||
|
@ -148,37 +141,4 @@ public class ApiTestCase {
|
||||||
? testInfo.getTestMethod().get().getName()
|
? testInfo.getTestMethod().get().getName()
|
||||||
: "unknown test name";
|
: "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.api.model.PaymentAccountForm;
|
||||||
import bisq.core.payment.F2FAccount;
|
import bisq.core.payment.F2FAccount;
|
||||||
|
import bisq.core.payment.NationalBankAccount;
|
||||||
import bisq.core.proto.CoreProtoResolver;
|
import bisq.core.proto.CoreProtoResolver;
|
||||||
|
|
||||||
import bisq.common.util.Utilities;
|
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.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
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.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static java.util.Arrays.stream;
|
import static java.util.Arrays.stream;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
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.ApiTestCase;
|
||||||
|
import bisq.apitest.linux.BashCommand;
|
||||||
import bisq.cli.GrpcClient;
|
import bisq.cli.GrpcClient;
|
||||||
|
import bisq.cli.table.builder.TableBuilder;
|
||||||
|
|
||||||
public class MethodTest extends ApiTestCase {
|
public class MethodTest extends ApiTestCase {
|
||||||
|
|
||||||
|
@ -46,15 +66,6 @@ public class MethodTest extends ApiTestCase {
|
||||||
private static final Function<Enum<?>[], String> toNameList = (enums) ->
|
private static final Function<Enum<?>[], String> toNameList = (enums) ->
|
||||||
stream(enums).map(Enum::name).collect(Collectors.joining(","));
|
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,
|
public static void startSupportingApps(File callRateMeteringConfigFile,
|
||||||
boolean generateBtcBlock,
|
boolean generateBtcBlock,
|
||||||
boolean startSupportingAppsInDebugMode,
|
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,
|
public static void startSupportingApps(boolean generateBtcBlock,
|
||||||
boolean startSupportingAppsInDebugMode,
|
boolean startSupportingAppsInDebugMode,
|
||||||
Enum<?>... supportingApps) {
|
Enum<?>... supportingApps) {
|
||||||
try {
|
try {
|
||||||
// Disable call rate metering where there is no callRateMeteringConfigFile.
|
// Disable call rate metering where there is no callRateMeteringConfigFile.
|
||||||
File callRateMeteringConfigFile = defaultRateMeterInterceptorConfig();
|
File callRateMeteringConfigFile = getTestRateMeterInterceptorConfig();
|
||||||
setUpScaffold(new String[]{
|
setUpScaffold(new String[]{
|
||||||
"--supportingApps", toNameList.apply(supportingApps),
|
"--supportingApps", toNameList.apply(supportingApps),
|
||||||
"--callRateMeteringConfigPath", callRateMeteringConfigFile.getAbsolutePath(),
|
"--callRateMeteringConfigPath", callRateMeteringConfigFile.getAbsolutePath(),
|
||||||
|
@ -133,17 +137,94 @@ public class MethodTest extends ApiTestCase {
|
||||||
return f2FAccount;
|
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) {
|
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
|
// 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
|
// case we need a bisq.core.payment.PaymentAccount so it can be cast to its
|
||||||
// sub type.
|
// sub-type.
|
||||||
var paymentAccount = grpcClient.createPaymentAccount(jsonString);
|
var paymentAccount = grpcClient.createPaymentAccount(jsonString);
|
||||||
return bisq.core.payment.PaymentAccount.fromProto(paymentAccount, CORE_PROTO_RESOLVER);
|
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) {
|
protected static String encodeToHex(String s) {
|
||||||
return Utilities.bytesAsHexString(s.getBytes(UTF_8));
|
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() {
|
public void testRegisterArbitratorShouldThrowException() {
|
||||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||||
arbClient.registerDisputeAgent(ARBITRATOR, DEV_PRIVILEGE_PRIV_KEY));
|
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());
|
exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,13 +17,15 @@
|
||||||
|
|
||||||
package bisq.apitest.method.offer;
|
package bisq.apitest.method.offer;
|
||||||
|
|
||||||
import bisq.core.monetary.Altcoin;
|
import bisq.proto.grpc.OfferInfo;
|
||||||
|
|
||||||
import protobuf.PaymentAccount;
|
import protobuf.PaymentAccount;
|
||||||
|
|
||||||
import org.bitcoinj.utils.Fiat;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.math.MathContext;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
@ -32,49 +34,96 @@ import org.junit.jupiter.api.AfterAll;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
|
||||||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
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.alicedaemon;
|
||||||
import static bisq.apitest.config.HavenoAppConfig.arbdaemon;
|
import static bisq.apitest.config.HavenoAppConfig.arbdaemon;
|
||||||
import static bisq.apitest.config.HavenoAppConfig.bobdaemon;
|
import static bisq.apitest.config.HavenoAppConfig.bobdaemon;
|
||||||
import static bisq.apitest.config.HavenoAppConfig.seednode;
|
import static bisq.apitest.config.HavenoAppConfig.seednode;
|
||||||
import static bisq.common.util.MathUtils.roundDouble;
|
import static bisq.cli.table.builder.TableType.OFFER_TBL;
|
||||||
import static bisq.common.util.MathUtils.scaleDownByPowerOf10;
|
import static java.lang.String.format;
|
||||||
import static bisq.core.locale.CurrencyUtil.isCryptoCurrency;
|
import static java.lang.System.out;
|
||||||
import static java.math.RoundingMode.HALF_UP;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import bisq.apitest.method.MethodTest;
|
import bisq.apitest.method.MethodTest;
|
||||||
|
import bisq.cli.CliMain;
|
||||||
|
import bisq.cli.table.builder.TableBuilder;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public abstract class AbstractOfferTest extends MethodTest {
|
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
|
@Setter
|
||||||
protected static boolean isLongRunningTest;
|
protected static boolean isLongRunningTest;
|
||||||
|
|
||||||
|
protected static PaymentAccount alicesBtcAcct;
|
||||||
|
protected static PaymentAccount bobsBtcAcct;
|
||||||
|
|
||||||
|
protected static PaymentAccount alicesXmrAcct;
|
||||||
|
protected static PaymentAccount bobsXmrAcct;
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
public static void setUp() {
|
public static void setUp() {
|
||||||
|
setUp(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setUp(boolean startSupportingAppsInDebugMode) {
|
||||||
startSupportingApps(true,
|
startSupportingApps(true,
|
||||||
false,
|
startSupportingAppsInDebugMode,
|
||||||
bitcoind,
|
bitcoind,
|
||||||
seednode,
|
seednode,
|
||||||
arbdaemon,
|
arbdaemon,
|
||||||
alicedaemon,
|
alicedaemon,
|
||||||
bobdaemon);
|
bobdaemon);
|
||||||
|
initPaymentAccounts();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected double getScaledOfferPrice(double offerPrice, String currencyCode) {
|
protected static final Function<OfferInfo, String> toOfferTable = (offer) ->
|
||||||
int precision = isCryptoCurrency(currencyCode) ? Altcoin.SMALLEST_UNIT_EXPONENT : Fiat.SMALLEST_UNIT_EXPONENT;
|
new TableBuilder(OFFER_TBL, offer).build().toString();
|
||||||
return scaleDownByPowerOf10(offerPrice, precision);
|
|
||||||
|
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) {
|
@SuppressWarnings("ConstantConditions")
|
||||||
return BigDecimal.valueOf(roundDouble((1 - (price1 / price2)), 5))
|
public static void initPaymentAccounts() {
|
||||||
.setScale(4, HALF_UP)
|
alicesBtcAcct = aliceClient.getPaymentAccount("BTC");
|
||||||
.doubleValue();
|
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
|
@AfterAll
|
||||||
public static void tearDown() {
|
public static void tearDown() {
|
||||||
tearDownScaffold();
|
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.Test;
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
import static protobuf.OfferDirection.BUY;
|
||||||
|
|
||||||
@Disabled
|
@Disabled
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -51,8 +50,9 @@ public class CancelOfferTest extends AbstractOfferTest {
|
||||||
10000000L,
|
10000000L,
|
||||||
10000000L,
|
10000000L,
|
||||||
0.00,
|
0.00,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
defaultBuyerSecurityDepositPct.get(),
|
||||||
paymentAccountId);
|
paymentAccountId,
|
||||||
|
NO_TRIGGER_PRICE);
|
||||||
};
|
};
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -28,14 +28,14 @@ import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.XMR;
|
import static bisq.apitest.config.ApiTestConfig.XMR;
|
||||||
import static bisq.cli.TableFormat.formatOfferTable;
|
import static bisq.apitest.config.ApiTestConfig.EUR;
|
||||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
import static bisq.apitest.config.ApiTestConfig.USD;
|
||||||
import static java.util.Collections.singletonList;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static protobuf.OfferPayload.Direction.SELL;
|
import static protobuf.OfferDirection.BUY;
|
||||||
|
import static protobuf.OfferDirection.SELL;
|
||||||
|
|
||||||
@Disabled
|
@Disabled
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -44,35 +44,44 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(1)
|
@Order(1)
|
||||||
public void testCreateAUDXMRBuyOfferUsingFixedPrice16000() {
|
public void testCreateAUDBTCBuyOfferUsingFixedPrice16000() {
|
||||||
PaymentAccount audAccount = createDummyF2FAccount(aliceClient, "AU");
|
PaymentAccount audAccount = createDummyF2FAccount(aliceClient, "AU");
|
||||||
var newOffer = aliceClient.createFixedPricedOffer(BUY.name(),
|
var newOffer = aliceClient.createFixedPricedOffer(BUY.name(),
|
||||||
"aud",
|
"aud",
|
||||||
10_000_000L,
|
10_000_000L,
|
||||||
10_000_000L,
|
10_000_000L,
|
||||||
"36000",
|
"36000",
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
defaultBuyerSecurityDepositPct.get(),
|
||||||
audAccount.getId());
|
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();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
assertEquals(BUY.name(), newOffer.getDirection());
|
||||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
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.getAmount());
|
||||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||||
|
assertEquals("3600", newOffer.getVolume());
|
||||||
|
assertEquals("3600", newOffer.getMinVolume());
|
||||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||||
assertEquals(audAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(audAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("AUD", newOffer.getCounterCurrencyCode());
|
assertEquals("AUD", newOffer.getCounterCurrencyCode());
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getOffer(newOfferId);
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertTrue(newOffer.getIsActivated());
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
assertEquals(BUY.name(), newOffer.getDirection());
|
||||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
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.getAmount());
|
||||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||||
|
assertEquals("3600", newOffer.getVolume());
|
||||||
|
assertEquals("3600", newOffer.getMinVolume());
|
||||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||||
assertEquals(audAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(audAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||||
|
@ -81,75 +90,93 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(2)
|
@Order(2)
|
||||||
public void testCreateUSDXMRBuyOfferUsingFixedPrice100001234() {
|
public void testCreateUSDBTCBuyOfferUsingFixedPrice100001234() {
|
||||||
PaymentAccount usdAccount = createDummyF2FAccount(aliceClient, "US");
|
PaymentAccount usdAccount = createDummyF2FAccount(aliceClient, "US");
|
||||||
var newOffer = aliceClient.createFixedPricedOffer(BUY.name(),
|
var newOffer = aliceClient.createFixedPricedOffer(BUY.name(),
|
||||||
"usd",
|
"usd",
|
||||||
10_000_000L,
|
10_000_000L,
|
||||||
10_000_000L,
|
10_000_000L,
|
||||||
"30000.1234",
|
"30000.1234",
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
defaultBuyerSecurityDepositPct.get(),
|
||||||
usdAccount.getId());
|
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();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
assertEquals(BUY.name(), newOffer.getDirection());
|
||||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
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.getAmount());
|
||||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||||
|
assertEquals("3000", newOffer.getVolume());
|
||||||
|
assertEquals("3000", newOffer.getMinVolume());
|
||||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||||
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
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(newOfferId, newOffer.getId());
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
assertEquals(BUY.name(), newOffer.getDirection());
|
||||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
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.getAmount());
|
||||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||||
|
assertEquals("3000", newOffer.getVolume());
|
||||||
|
assertEquals("3000", newOffer.getMinVolume());
|
||||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||||
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
assertEquals(USD, newOffer.getCounterCurrencyCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(3)
|
@Order(3)
|
||||||
public void testCreateEURXMRSellOfferUsingFixedPrice95001234() {
|
public void testCreateEURBTCSellOfferUsingFixedPrice95001234() {
|
||||||
PaymentAccount eurAccount = createDummyF2FAccount(aliceClient, "FR");
|
PaymentAccount eurAccount = createDummyF2FAccount(aliceClient, "FR");
|
||||||
var newOffer = aliceClient.createFixedPricedOffer(SELL.name(),
|
var newOffer = aliceClient.createFixedPricedOffer(SELL.name(),
|
||||||
"eur",
|
"eur",
|
||||||
10_000_000L,
|
10_000_000L,
|
||||||
5_000_000L,
|
5_000_000L,
|
||||||
"29500.1234",
|
"29500.1234",
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
defaultBuyerSecurityDepositPct.get(),
|
||||||
eurAccount.getId());
|
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();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
assertEquals(SELL.name(), newOffer.getDirection());
|
assertEquals(SELL.name(), newOffer.getDirection());
|
||||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||||
assertEquals(295_001_234, newOffer.getPrice());
|
assertEquals("29500.1234", newOffer.getPrice());
|
||||||
assertEquals(10_000_000, newOffer.getAmount());
|
assertEquals(10_000_000, newOffer.getAmount());
|
||||||
assertEquals(5_000_000, newOffer.getMinAmount());
|
assertEquals(5_000_000, newOffer.getMinAmount());
|
||||||
|
assertEquals("2950", newOffer.getVolume());
|
||||||
|
assertEquals("1475", newOffer.getMinVolume());
|
||||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||||
assertEquals(eurAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(eurAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
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(newOfferId, newOffer.getId());
|
||||||
assertEquals(SELL.name(), newOffer.getDirection());
|
assertEquals(SELL.name(), newOffer.getDirection());
|
||||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||||
assertEquals(295_001_234, newOffer.getPrice());
|
assertEquals("29500.1234", newOffer.getPrice());
|
||||||
assertEquals(10_000_000, newOffer.getAmount());
|
assertEquals(10_000_000, newOffer.getAmount());
|
||||||
assertEquals(5_000_000, newOffer.getMinAmount());
|
assertEquals(5_000_000, newOffer.getMinAmount());
|
||||||
|
assertEquals("2950", newOffer.getVolume());
|
||||||
|
assertEquals("1475", newOffer.getMinVolume());
|
||||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||||
assertEquals(eurAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(eurAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
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.text.DecimalFormat;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
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.Test;
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.XMR;
|
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||||
import static bisq.cli.TableFormat.formatOfferTable;
|
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.scaleDownByPowerOf10;
|
||||||
import static bisq.common.util.MathUtils.scaleUpByPowerOf10;
|
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.Math.abs;
|
||||||
import static java.lang.String.format;
|
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.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
import static protobuf.OfferDirection.BUY;
|
||||||
import static protobuf.OfferPayload.Direction.SELL;
|
import static protobuf.OfferDirection.SELL;
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Disabled
|
@Disabled
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
@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_ERROR_TOLERANCE = 0.0050; // 0.50%
|
||||||
private static final double MKT_PRICE_MARGIN_WARNING_TOLERANCE = 0.0001; // 0.01%
|
private static final double MKT_PRICE_MARGIN_WARNING_TOLERANCE = 0.0001; // 0.01%
|
||||||
|
|
||||||
|
private static final String MAKER_FEE_CURRENCY_CODE = BTC;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(1)
|
@Order(1)
|
||||||
public void testCreateUSDXMRBuyOffer5PctPriceMargin() {
|
public void testCreateUSDBTCBuyOffer5PctPriceMargin() {
|
||||||
PaymentAccount usdAccount = createDummyF2FAccount(aliceClient, "US");
|
PaymentAccount usdAccount = createDummyF2FAccount(aliceClient, "US");
|
||||||
double priceMarginPctInput = 5.00;
|
double priceMarginPctInput = 5.00d;
|
||||||
var newOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
|
var newOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
|
||||||
"usd",
|
"usd",
|
||||||
10_000_000L,
|
10_000_000L,
|
||||||
10_000_000L,
|
10_000_000L,
|
||||||
priceMarginPctInput,
|
priceMarginPctInput,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
defaultBuyerSecurityDepositPct.get(),
|
||||||
usdAccount.getId());
|
usdAccount.getId(),
|
||||||
log.info("OFFER #1:\n{}", formatOfferTable(singletonList(newOffer), "usd"));
|
NO_TRIGGER_PRICE);
|
||||||
|
log.debug("Offer #1:\n{}", toOfferTable.apply(newOffer));
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertFalse(newOffer.getIsActivated());
|
||||||
|
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
assertEquals(BUY.name(), newOffer.getDirection());
|
||||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||||
|
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
|
||||||
assertEquals(10_000_000, newOffer.getAmount());
|
assertEquals(10_000_000, newOffer.getAmount());
|
||||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||||
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(BTC, 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(newOfferId, newOffer.getId());
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
assertEquals(BUY.name(), newOffer.getDirection());
|
||||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||||
|
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
|
||||||
assertEquals(10_000_000, newOffer.getAmount());
|
assertEquals(10_000_000, newOffer.getAmount());
|
||||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||||
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(usdAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("USD", newOffer.getCounterCurrencyCode());
|
assertEquals(USD, newOffer.getCounterCurrencyCode());
|
||||||
|
|
||||||
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(2)
|
@Order(2)
|
||||||
public void testCreateNZDXMRBuyOfferMinus2PctPriceMargin() {
|
public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() {
|
||||||
PaymentAccount nzdAccount = createDummyF2FAccount(aliceClient, "NZ");
|
PaymentAccount nzdAccount = createDummyF2FAccount(aliceClient, "NZ");
|
||||||
double priceMarginPctInput = -2.00;
|
double priceMarginPctInput = -2.00d; // -2%
|
||||||
var newOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
|
var newOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
|
||||||
"nzd",
|
"nzd",
|
||||||
10_000_000L,
|
10_000_000L,
|
||||||
10_000_000L,
|
10_000_000L,
|
||||||
priceMarginPctInput,
|
priceMarginPctInput,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
defaultBuyerSecurityDepositPct.get(),
|
||||||
nzdAccount.getId());
|
nzdAccount.getId(),
|
||||||
log.info("OFFER #2:\n{}", formatOfferTable(singletonList(newOffer), "nzd"));
|
NO_TRIGGER_PRICE);
|
||||||
|
log.debug("Offer #2:\n{}", toOfferTable.apply(newOffer));
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertFalse(newOffer.getIsActivated());
|
||||||
|
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
assertEquals(BUY.name(), newOffer.getDirection());
|
||||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||||
|
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
|
||||||
assertEquals(10_000_000, newOffer.getAmount());
|
assertEquals(10_000_000, newOffer.getAmount());
|
||||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||||
assertEquals(nzdAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(nzdAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("NZD", newOffer.getCounterCurrencyCode());
|
assertEquals("NZD", newOffer.getCounterCurrencyCode());
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getOffer(newOfferId);
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertTrue(newOffer.getIsActivated());
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
assertEquals(BUY.name(), newOffer.getDirection());
|
assertEquals(BUY.name(), newOffer.getDirection());
|
||||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||||
|
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
|
||||||
assertEquals(10_000_000, newOffer.getAmount());
|
assertEquals(10_000_000, newOffer.getAmount());
|
||||||
assertEquals(10_000_000, newOffer.getMinAmount());
|
assertEquals(10_000_000, newOffer.getMinAmount());
|
||||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||||
assertEquals(nzdAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(nzdAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("NZD", newOffer.getCounterCurrencyCode());
|
assertEquals("NZD", newOffer.getCounterCurrencyCode());
|
||||||
|
|
||||||
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
||||||
|
@ -132,7 +154,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(3)
|
@Order(3)
|
||||||
public void testCreateGBPXMRSellOfferMinus1Point5PctPriceMargin() {
|
public void testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin() {
|
||||||
PaymentAccount gbpAccount = createDummyF2FAccount(aliceClient, "GB");
|
PaymentAccount gbpAccount = createDummyF2FAccount(aliceClient, "GB");
|
||||||
double priceMarginPctInput = -1.5;
|
double priceMarginPctInput = -1.5;
|
||||||
var newOffer = aliceClient.createMarketBasedPricedOffer(SELL.name(),
|
var newOffer = aliceClient.createMarketBasedPricedOffer(SELL.name(),
|
||||||
|
@ -140,29 +162,37 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
10_000_000L,
|
10_000_000L,
|
||||||
5_000_000L,
|
5_000_000L,
|
||||||
priceMarginPctInput,
|
priceMarginPctInput,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
defaultBuyerSecurityDepositPct.get(),
|
||||||
gbpAccount.getId());
|
gbpAccount.getId(),
|
||||||
log.info("OFFER #3:\n{}", formatOfferTable(singletonList(newOffer), "gbp"));
|
NO_TRIGGER_PRICE);
|
||||||
|
log.debug("Offer #3:\n{}", toOfferTable.apply(newOffer));
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertFalse(newOffer.getIsActivated());
|
||||||
|
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
assertEquals(SELL.name(), newOffer.getDirection());
|
assertEquals(SELL.name(), newOffer.getDirection());
|
||||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||||
|
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
|
||||||
assertEquals(10_000_000, newOffer.getAmount());
|
assertEquals(10_000_000, newOffer.getAmount());
|
||||||
assertEquals(5_000_000, newOffer.getMinAmount());
|
assertEquals(5_000_000, newOffer.getMinAmount());
|
||||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||||
assertEquals(gbpAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(gbpAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("GBP", newOffer.getCounterCurrencyCode());
|
assertEquals("GBP", newOffer.getCounterCurrencyCode());
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getOffer(newOfferId);
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertTrue(newOffer.getIsActivated());
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
assertEquals(SELL.name(), newOffer.getDirection());
|
assertEquals(SELL.name(), newOffer.getDirection());
|
||||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||||
|
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
|
||||||
assertEquals(10_000_000, newOffer.getAmount());
|
assertEquals(10_000_000, newOffer.getAmount());
|
||||||
assertEquals(5_000_000, newOffer.getMinAmount());
|
assertEquals(5_000_000, newOffer.getMinAmount());
|
||||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||||
assertEquals(gbpAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(gbpAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("GBP", newOffer.getCounterCurrencyCode());
|
assertEquals("GBP", newOffer.getCounterCurrencyCode());
|
||||||
|
|
||||||
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
||||||
|
@ -170,7 +200,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(4)
|
@Order(4)
|
||||||
public void testCreateBRLXMRSellOffer6Point55PctPriceMargin() {
|
public void testCreateBRLBTCSellOffer6Point55PctPriceMargin() {
|
||||||
PaymentAccount brlAccount = createDummyF2FAccount(aliceClient, "BR");
|
PaymentAccount brlAccount = createDummyF2FAccount(aliceClient, "BR");
|
||||||
double priceMarginPctInput = 6.55;
|
double priceMarginPctInput = 6.55;
|
||||||
var newOffer = aliceClient.createMarketBasedPricedOffer(SELL.name(),
|
var newOffer = aliceClient.createMarketBasedPricedOffer(SELL.name(),
|
||||||
|
@ -178,53 +208,92 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
10_000_000L,
|
10_000_000L,
|
||||||
5_000_000L,
|
5_000_000L,
|
||||||
priceMarginPctInput,
|
priceMarginPctInput,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
defaultBuyerSecurityDepositPct.get(),
|
||||||
brlAccount.getId());
|
brlAccount.getId(),
|
||||||
log.info("OFFER #4:\n{}", formatOfferTable(singletonList(newOffer), "brl"));
|
NO_TRIGGER_PRICE);
|
||||||
|
log.debug("Offer #4:\n{}", toOfferTable.apply(newOffer));
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertFalse(newOffer.getIsActivated());
|
||||||
|
|
||||||
String newOfferId = newOffer.getId();
|
String newOfferId = newOffer.getId();
|
||||||
assertNotEquals("", newOfferId);
|
assertNotEquals("", newOfferId);
|
||||||
assertEquals(SELL.name(), newOffer.getDirection());
|
assertEquals(SELL.name(), newOffer.getDirection());
|
||||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||||
|
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
|
||||||
assertEquals(10_000_000, newOffer.getAmount());
|
assertEquals(10_000_000, newOffer.getAmount());
|
||||||
assertEquals(5_000_000, newOffer.getMinAmount());
|
assertEquals(5_000_000, newOffer.getMinAmount());
|
||||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||||
assertEquals(brlAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(brlAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("BRL", newOffer.getCounterCurrencyCode());
|
assertEquals("BRL", newOffer.getCounterCurrencyCode());
|
||||||
|
|
||||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
newOffer = aliceClient.getOffer(newOfferId);
|
||||||
|
assertTrue(newOffer.getIsMyOffer());
|
||||||
|
assertTrue(newOffer.getIsActivated());
|
||||||
assertEquals(newOfferId, newOffer.getId());
|
assertEquals(newOfferId, newOffer.getId());
|
||||||
assertEquals(SELL.name(), newOffer.getDirection());
|
assertEquals(SELL.name(), newOffer.getDirection());
|
||||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||||
|
assertEquals(priceMarginPctInput, newOffer.getMarketPriceMarginPct());
|
||||||
assertEquals(10_000_000, newOffer.getAmount());
|
assertEquals(10_000_000, newOffer.getAmount());
|
||||||
assertEquals(5_000_000, newOffer.getMinAmount());
|
assertEquals(5_000_000, newOffer.getMinAmount());
|
||||||
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
assertEquals(1_500_000, newOffer.getBuyerSecurityDeposit());
|
||||||
assertEquals(brlAccount.getId(), newOffer.getPaymentAccountId());
|
assertEquals(brlAccount.getId(), newOffer.getPaymentAccountId());
|
||||||
assertEquals(XMR, newOffer.getBaseCurrencyCode());
|
assertEquals(BTC, newOffer.getBaseCurrencyCode());
|
||||||
assertEquals("BRL", newOffer.getCounterCurrencyCode());
|
assertEquals("BRL", newOffer.getCounterCurrencyCode());
|
||||||
|
|
||||||
assertCalculatedPriceIsCorrect(newOffer, priceMarginPctInput);
|
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) {
|
private void assertCalculatedPriceIsCorrect(OfferInfo offer, double priceMarginPctInput) {
|
||||||
assertTrue(() -> {
|
assertTrue(() -> {
|
||||||
String counterCurrencyCode = offer.getCounterCurrencyCode();
|
String counterCurrencyCode = offer.getCounterCurrencyCode();
|
||||||
double mktPrice = aliceClient.getBtcPrice(counterCurrencyCode);
|
double mktPrice = aliceClient.getBtcPrice(counterCurrencyCode);
|
||||||
double scaledOfferPrice = getScaledOfferPrice(offer.getPrice(), counterCurrencyCode);
|
double priceAsDouble = Double.parseDouble(offer.getPrice());
|
||||||
double expectedDiffPct = scaleDownByPowerOf10(priceMarginPctInput, 2);
|
double expectedDiffPct = scaleDownByPowerOf10(priceMarginPctInput, 2);
|
||||||
double actualDiffPct = offer.getDirection().equals(BUY.name())
|
double actualDiffPct = offer.getDirection().equals(BUY.name())
|
||||||
? getPercentageDifference(scaledOfferPrice, mktPrice)
|
? getPercentageDifference(priceAsDouble, mktPrice)
|
||||||
: getPercentageDifference(mktPrice, scaledOfferPrice);
|
: getPercentageDifference(mktPrice, priceAsDouble);
|
||||||
double pctDiffDelta = abs(expectedDiffPct) - abs(actualDiffPct);
|
double pctDiffDelta = abs(expectedDiffPct) - abs(actualDiffPct);
|
||||||
return isCalculatedPriceWithinErrorTolerance(pctDiffDelta,
|
return isCalculatedPriceWithinErrorTolerance(pctDiffDelta,
|
||||||
expectedDiffPct,
|
expectedDiffPct,
|
||||||
actualDiffPct,
|
actualDiffPct,
|
||||||
mktPrice,
|
mktPrice,
|
||||||
scaledOfferPrice,
|
priceAsDouble,
|
||||||
offer);
|
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,
|
private boolean isCalculatedPriceWithinErrorTolerance(double delta,
|
||||||
double expectedDiffPct,
|
double expectedDiffPct,
|
||||||
double actualDiffPct,
|
double actualDiffPct,
|
||||||
|
@ -245,7 +314,7 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||||
actualDiffPct,
|
actualDiffPct,
|
||||||
mktPrice,
|
mktPrice,
|
||||||
scaledOfferPrice);
|
scaledOfferPrice);
|
||||||
log.warn(offer.toString());
|
log.trace(offer.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
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 org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
import static protobuf.OfferDirection.BUY;
|
||||||
|
|
||||||
@Disabled
|
@Disabled
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -52,7 +51,7 @@ public class ValidateCreateOfferTest extends AbstractOfferTest {
|
||||||
100000000000L, // exceeds amount limit
|
100000000000L, // exceeds amount limit
|
||||||
100000000000L,
|
100000000000L,
|
||||||
"10000.0000",
|
"10000.0000",
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
defaultBuyerSecurityDepositPct.get(),
|
||||||
usdAccount.getId()));
|
usdAccount.getId()));
|
||||||
assertEquals("UNKNOWN: An error occurred at task: ValidateOffer", exception.getMessage());
|
assertEquals("UNKNOWN: An error occurred at task: ValidateOffer", exception.getMessage());
|
||||||
}
|
}
|
||||||
|
@ -68,7 +67,7 @@ public class ValidateCreateOfferTest extends AbstractOfferTest {
|
||||||
10000000L,
|
10000000L,
|
||||||
10000000L,
|
10000000L,
|
||||||
"40000.0000",
|
"40000.0000",
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
defaultBuyerSecurityDepositPct.get(),
|
||||||
chfAccount.getId()));
|
chfAccount.getId()));
|
||||||
String expectedError = format("UNKNOWN: cannot create EUR offer with payment account %s", chfAccount.getId());
|
String expectedError = format("UNKNOWN: cannot create EUR offer with payment account %s", chfAccount.getId());
|
||||||
assertEquals(expectedError, exception.getMessage());
|
assertEquals(expectedError, exception.getMessage());
|
||||||
|
@ -85,7 +84,7 @@ public class ValidateCreateOfferTest extends AbstractOfferTest {
|
||||||
10000000L,
|
10000000L,
|
||||||
10000000L,
|
10000000L,
|
||||||
"63000.0000",
|
"63000.0000",
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
defaultBuyerSecurityDepositPct.get(),
|
||||||
audAccount.getId()));
|
audAccount.getId()));
|
||||||
String expectedError = format("UNKNOWN: cannot create CAD offer with payment account %s", audAccount.getId());
|
String expectedError = format("UNKNOWN: cannot create CAD offer with payment account %s", audAccount.getId());
|
||||||
assertEquals(expectedError, exception.getMessage());
|
assertEquals(expectedError, exception.getMessage());
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package bisq.apitest.method.payment;
|
package bisq.apitest.method.payment;
|
||||||
|
|
||||||
import bisq.core.api.model.PaymentAccountForm;
|
import bisq.core.api.model.PaymentAccountForm;
|
||||||
|
import bisq.core.locale.FiatCurrency;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.locale.TradeCurrency;
|
import bisq.core.locale.TradeCurrency;
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
|
@ -17,10 +18,13 @@ import java.io.IOException;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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_NAME = "bankAccountName";
|
||||||
static final String PROPERTY_NAME_BANK_ACCOUNT_NUMBER = "bankAccountNumber";
|
static final String PROPERTY_NAME_BANK_ACCOUNT_NUMBER = "bankAccountNumber";
|
||||||
static final String PROPERTY_NAME_BANK_ACCOUNT_TYPE = "bankAccountType";
|
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_CODE = "bankBranchCode";
|
||||||
static final String PROPERTY_NAME_BANK_BRANCH_NAME = "bankBranchName";
|
static final String PROPERTY_NAME_BANK_BRANCH_NAME = "bankBranchName";
|
||||||
static final String PROPERTY_NAME_BANK_CODE = "bankCode";
|
static final String PROPERTY_NAME_BANK_CODE = "bankCode";
|
||||||
|
static final String PROPERTY_NAME_BANK_COUNTRY_CODE = "bankCountryCode";
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
static final String PROPERTY_NAME_BANK_ID = "bankId";
|
static final String PROPERTY_NAME_BANK_ID = "bankId";
|
||||||
static final String PROPERTY_NAME_BANK_NAME = "bankName";
|
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_BRANCH_ID = "branchId";
|
||||||
static final String PROPERTY_NAME_BIC = "bic";
|
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_COUNTRY = "country";
|
||||||
static final String PROPERTY_NAME_CITY = "city";
|
static final String PROPERTY_NAME_CITY = "city";
|
||||||
static final String PROPERTY_NAME_CONTACT = "contact";
|
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_NAME = "holderName";
|
||||||
static final String PROPERTY_NAME_HOLDER_TAX_ID = "holderTaxId";
|
static final String PROPERTY_NAME_HOLDER_TAX_ID = "holderTaxId";
|
||||||
static final String PROPERTY_NAME_IBAN = "iban";
|
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_MOBILE_NR = "mobileNr";
|
||||||
static final String PROPERTY_NAME_NATIONAL_ACCOUNT_ID = "nationalAccountId";
|
static final String PROPERTY_NAME_NATIONAL_ACCOUNT_ID = "nationalAccountId";
|
||||||
static final String PROPERTY_NAME_PAY_ID = "payid";
|
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_QUESTION = "question";
|
||||||
static final String PROPERTY_NAME_REQUIREMENTS = "requirements";
|
static final String PROPERTY_NAME_REQUIREMENTS = "requirements";
|
||||||
static final String PROPERTY_NAME_SALT = "salt";
|
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_SORT_CODE = "sortCode";
|
||||||
|
static final String PROPERTY_NAME_SPECIAL_INSTRUCTIONS = "specialInstructions";
|
||||||
static final String PROPERTY_NAME_STATE = "state";
|
static final String PROPERTY_NAME_STATE = "state";
|
||||||
static final String PROPERTY_NAME_TRADE_CURRENCIES = "tradeCurrencies";
|
static final String PROPERTY_NAME_TRADE_CURRENCIES = "tradeCurrencies";
|
||||||
static final String PROPERTY_NAME_USERNAME = "userName";
|
static final String PROPERTY_NAME_USERNAME = "userName";
|
||||||
|
@ -110,7 +130,7 @@ public class AbstractPaymentAccountTest extends MethodTest {
|
||||||
COMPLETED_FORM_MAP.clear();
|
COMPLETED_FORM_MAP.clear();
|
||||||
|
|
||||||
File emptyForm = getPaymentAccountForm(aliceClient, paymentMethodId);
|
File emptyForm = getPaymentAccountForm(aliceClient, paymentMethodId);
|
||||||
// A short cut over the API:
|
// A shortcut over the API:
|
||||||
// File emptyForm = PAYMENT_ACCOUNT_FORM.getPaymentAccountForm(paymentMethodId);
|
// File emptyForm = PAYMENT_ACCOUNT_FORM.getPaymentAccountForm(paymentMethodId);
|
||||||
log.debug("{} Empty form saved to {}",
|
log.debug("{} Empty form saved to {}",
|
||||||
testName(testInfo),
|
testName(testInfo),
|
||||||
|
@ -125,7 +145,13 @@ public class AbstractPaymentAccountTest extends MethodTest {
|
||||||
PAYMENT_ACCOUNT_FORM.toJsonString(jsonForm),
|
PAYMENT_ACCOUNT_FORM.toJsonString(jsonForm),
|
||||||
Object.class);
|
Object.class);
|
||||||
assertNotNull(emptyForm);
|
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(paymentMethodId, emptyForm.get(PROPERTY_NAME_PAYMENT_METHOD_ID));
|
||||||
assertEquals("your accountname", emptyForm.get(PROPERTY_NAME_ACCOUNT_NAME));
|
assertEquals("your accountname", emptyForm.get(PROPERTY_NAME_ACCOUNT_NAME));
|
||||||
for (String field : fields) {
|
for (String field : fields) {
|
||||||
|
@ -149,6 +175,15 @@ public class AbstractPaymentAccountTest extends MethodTest {
|
||||||
assertEquals(expectedCurrencyCode, paymentAccount.getSingleTradeCurrency().getCode());
|
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,
|
protected final void verifyAccountTradeCurrencies(List<TradeCurrency> expectedTradeCurrencies,
|
||||||
PaymentAccount paymentAccount) {
|
PaymentAccount paymentAccount) {
|
||||||
assertNotNull(paymentAccount.getTradeCurrencies());
|
assertNotNull(paymentAccount.getTradeCurrencies());
|
||||||
|
@ -164,14 +199,36 @@ public class AbstractPaymentAccountTest extends MethodTest {
|
||||||
assertTrue(paymentAccount.isPresent());
|
assertTrue(paymentAccount.isPresent());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final String getCompletedFormAsJsonString() {
|
protected final String getCompletedFormAsJsonString(List<String> comments) {
|
||||||
File completedForm = fillPaymentAccountForm();
|
File completedForm = fillPaymentAccountForm(comments);
|
||||||
String jsonString = PAYMENT_ACCOUNT_FORM.toJsonString(completedForm);
|
String jsonString = PAYMENT_ACCOUNT_FORM.toJsonString(completedForm);
|
||||||
log.debug("Completed form: {}", jsonString);
|
log.debug("Completed form: {}", jsonString);
|
||||||
return 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;
|
File tmpJsonForm = null;
|
||||||
try {
|
try {
|
||||||
tmpJsonForm = File.createTempFile("temp_acct_form_",
|
tmpJsonForm = File.createTempFile("temp_acct_form_",
|
||||||
|
@ -182,7 +239,7 @@ public class AbstractPaymentAccountTest extends MethodTest {
|
||||||
|
|
||||||
writer.name(PROPERTY_NAME_JSON_COMMENTS);
|
writer.name(PROPERTY_NAME_JSON_COMMENTS);
|
||||||
writer.beginArray();
|
writer.beginArray();
|
||||||
for (String s : PROPERTY_VALUE_JSON_COMMENTS) {
|
for (String s : comments) {
|
||||||
writer.value(s);
|
writer.value(s);
|
||||||
}
|
}
|
||||||
writer.endArray();
|
writer.endArray();
|
||||||
|
|
|
@ -17,12 +17,13 @@
|
||||||
|
|
||||||
package bisq.apitest.method.payment;
|
package bisq.apitest.method.payment;
|
||||||
|
|
||||||
|
import bisq.core.locale.FiatCurrency;
|
||||||
import bisq.core.locale.TradeCurrency;
|
import bisq.core.locale.TradeCurrency;
|
||||||
import bisq.core.payment.AdvancedCashAccount;
|
import bisq.core.payment.AdvancedCashAccount;
|
||||||
import bisq.core.payment.AliPayAccount;
|
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.CashDepositAccount;
|
||||||
import bisq.core.payment.ChaseQuickPayAccount;
|
|
||||||
import bisq.core.payment.ClearXchangeAccount;
|
import bisq.core.payment.ClearXchangeAccount;
|
||||||
import bisq.core.payment.F2FAccount;
|
import bisq.core.payment.F2FAccount;
|
||||||
import bisq.core.payment.FasterPaymentsAccount;
|
import bisq.core.payment.FasterPaymentsAccount;
|
||||||
|
@ -32,7 +33,9 @@ import bisq.core.payment.JapanBankAccount;
|
||||||
import bisq.core.payment.MoneyBeamAccount;
|
import bisq.core.payment.MoneyBeamAccount;
|
||||||
import bisq.core.payment.MoneyGramAccount;
|
import bisq.core.payment.MoneyGramAccount;
|
||||||
import bisq.core.payment.NationalBankAccount;
|
import bisq.core.payment.NationalBankAccount;
|
||||||
|
import bisq.core.payment.PaxumAccount;
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
|
import bisq.core.payment.PayseraAccount;
|
||||||
import bisq.core.payment.PerfectMoneyAccount;
|
import bisq.core.payment.PerfectMoneyAccount;
|
||||||
import bisq.core.payment.PopmoneyAccount;
|
import bisq.core.payment.PopmoneyAccount;
|
||||||
import bisq.core.payment.PromptPayAccount;
|
import bisq.core.payment.PromptPayAccount;
|
||||||
|
@ -41,6 +44,7 @@ import bisq.core.payment.SameBankAccount;
|
||||||
import bisq.core.payment.SepaAccount;
|
import bisq.core.payment.SepaAccount;
|
||||||
import bisq.core.payment.SepaInstantAccount;
|
import bisq.core.payment.SepaInstantAccount;
|
||||||
import bisq.core.payment.SpecificBanksAccount;
|
import bisq.core.payment.SpecificBanksAccount;
|
||||||
|
import bisq.core.payment.SwiftAccount;
|
||||||
import bisq.core.payment.SwishAccount;
|
import bisq.core.payment.SwishAccount;
|
||||||
import bisq.core.payment.TransferwiseAccount;
|
import bisq.core.payment.TransferwiseAccount;
|
||||||
import bisq.core.payment.USPostalMoneyOrderAccount;
|
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.CashDepositAccountPayload;
|
||||||
import bisq.core.payment.payload.SameBankAccountPayload;
|
import bisq.core.payment.payload.SameBankAccountPayload;
|
||||||
import bisq.core.payment.payload.SpecificBanksAccountPayload;
|
import bisq.core.payment.payload.SpecificBanksAccountPayload;
|
||||||
|
import bisq.core.payment.payload.SwiftAccountPayload;
|
||||||
|
|
||||||
import io.grpc.StatusRuntimeException;
|
import io.grpc.StatusRuntimeException;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@ -70,16 +77,23 @@ import org.junit.jupiter.api.TestInfo;
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
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.apitest.config.HavenoAppConfig.alicedaemon;
|
||||||
import static bisq.cli.TableFormat.formatPaymentAcctTbl;
|
import static bisq.cli.table.builder.TableType.PAYMENT_ACCOUNT_TBL;
|
||||||
import static bisq.core.locale.CurrencyUtil.*;
|
import static bisq.core.locale.CurrencyUtil.getAllSortedFiatCurrencies;
|
||||||
|
import static bisq.core.locale.CurrencyUtil.getTradeCurrency;
|
||||||
import static bisq.core.payment.payload.PaymentMethod.*;
|
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.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import bisq.cli.table.builder.TableBuilder;
|
||||||
|
|
||||||
@SuppressWarnings({"OptionalGetWithoutIsPresent", "ConstantConditions"})
|
@SuppressWarnings({"OptionalGetWithoutIsPresent", "ConstantConditions"})
|
||||||
@Disabled
|
@Disabled
|
||||||
@Slf4j
|
@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_PAYMENT_METHOD_ID, ADVANCED_CASH_ID);
|
||||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Advanced Cash Acct");
|
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_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"));
|
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Advanced Cash Acct Salt"));
|
||||||
String jsonString = getCompletedFormAsJsonString();
|
String jsonString = getCompletedFormAsJsonString();
|
||||||
AdvancedCashAccount paymentAccount = (AdvancedCashAccount) createPaymentAccount(aliceClient, jsonString);
|
AdvancedCashAccount paymentAccount = (AdvancedCashAccount) createPaymentAccount(aliceClient, jsonString);
|
||||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
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);
|
verifyCommonFormEntries(paymentAccount);
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
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_BANK_ACCOUNT_NAME, "Credit Union Australia");
|
||||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Australia Pay ID Acct Salt"));
|
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Australia Pay ID Acct Salt"));
|
||||||
String jsonString = getCompletedFormAsJsonString();
|
String jsonString = getCompletedFormAsJsonString();
|
||||||
AustraliaPayid paymentAccount = (AustraliaPayid) createPaymentAccount(aliceClient, jsonString);
|
AustraliaPayidAccount paymentAccount = (AustraliaPayidAccount) createPaymentAccount(aliceClient, jsonString);
|
||||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||||
verifyAccountSingleTradeCurrency("AUD", paymentAccount);
|
verifyAccountSingleTradeCurrency("AUD", paymentAccount);
|
||||||
verifyCommonFormEntries(paymentAccount);
|
verifyCommonFormEntries(paymentAccount);
|
||||||
|
@ -156,6 +177,33 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
print(paymentAccount);
|
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
|
@Test
|
||||||
public void testCreateCashDepositAccount(TestInfo testInfo) {
|
public void testCreateCashDepositAccount(TestInfo testInfo) {
|
||||||
File emptyForm = getEmptyForm(testInfo, CASH_DEPOSIT_ID);
|
File emptyForm = getEmptyForm(testInfo, CASH_DEPOSIT_ID);
|
||||||
|
@ -189,7 +237,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
String jsonString = getCompletedFormAsJsonString();
|
String jsonString = getCompletedFormAsJsonString();
|
||||||
CashDepositAccount paymentAccount = (CashDepositAccount) createPaymentAccount(aliceClient, jsonString);
|
CashDepositAccount paymentAccount = (CashDepositAccount) createPaymentAccount(aliceClient, jsonString);
|
||||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||||
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
|
verifyAccountSingleTradeCurrency(EUR, paymentAccount);
|
||||||
verifyCommonFormEntries(paymentAccount);
|
verifyCommonFormEntries(paymentAccount);
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||||
Objects.requireNonNull(paymentAccount.getCountry()).code);
|
Objects.requireNonNull(paymentAccount.getCountry()).code);
|
||||||
|
@ -253,28 +301,6 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
print(paymentAccount);
|
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
|
@Test
|
||||||
public void testCreateClearXChangeAccount(TestInfo testInfo) {
|
public void testCreateClearXChangeAccount(TestInfo testInfo) {
|
||||||
File emptyForm = getEmptyForm(testInfo, CLEAR_X_CHANGE_ID);
|
File emptyForm = getEmptyForm(testInfo, CLEAR_X_CHANGE_ID);
|
||||||
|
@ -290,7 +316,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
String jsonString = getCompletedFormAsJsonString();
|
String jsonString = getCompletedFormAsJsonString();
|
||||||
ClearXchangeAccount paymentAccount = (ClearXchangeAccount) createPaymentAccount(aliceClient, jsonString);
|
ClearXchangeAccount paymentAccount = (ClearXchangeAccount) createPaymentAccount(aliceClient, jsonString);
|
||||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
verifyAccountSingleTradeCurrency(USD, paymentAccount);
|
||||||
verifyCommonFormEntries(paymentAccount);
|
verifyCommonFormEntries(paymentAccount);
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL_OR_MOBILE_NR), paymentAccount.getEmailOrMobileNr());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL_OR_MOBILE_NR), paymentAccount.getEmailOrMobileNr());
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
||||||
|
@ -363,7 +389,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
String jsonString = getCompletedFormAsJsonString();
|
String jsonString = getCompletedFormAsJsonString();
|
||||||
HalCashAccount paymentAccount = (HalCashAccount) createPaymentAccount(aliceClient, jsonString);
|
HalCashAccount paymentAccount = (HalCashAccount) createPaymentAccount(aliceClient, jsonString);
|
||||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||||
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
|
verifyAccountSingleTradeCurrency(EUR, paymentAccount);
|
||||||
verifyCommonFormEntries(paymentAccount);
|
verifyCommonFormEntries(paymentAccount);
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_MOBILE_NR), paymentAccount.getMobileNr());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_MOBILE_NR), paymentAccount.getMobileNr());
|
||||||
print(paymentAccount);
|
print(paymentAccount);
|
||||||
|
@ -448,7 +474,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
String jsonString = getCompletedFormAsJsonString();
|
String jsonString = getCompletedFormAsJsonString();
|
||||||
MoneyBeamAccount paymentAccount = (MoneyBeamAccount) createPaymentAccount(aliceClient, jsonString);
|
MoneyBeamAccount paymentAccount = (MoneyBeamAccount) createPaymentAccount(aliceClient, jsonString);
|
||||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||||
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
|
verifyAccountSingleTradeCurrency(EUR, paymentAccount);
|
||||||
verifyCommonFormEntries(paymentAccount);
|
verifyCommonFormEntries(paymentAccount);
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_ID), paymentAccount.getAccountId());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_ID), paymentAccount.getAccountId());
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||||
|
@ -466,6 +492,11 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
PROPERTY_NAME_STATE);
|
PROPERTY_NAME_STATE);
|
||||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, MONEY_GRAM_ID);
|
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_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_HOLDER_NAME, "John Doe");
|
||||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "john@doe.info");
|
COMPLETED_FORM_MAP.put(PROPERTY_NAME_EMAIL, "john@doe.info");
|
||||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "US");
|
COMPLETED_FORM_MAP.put(PROPERTY_NAME_COUNTRY, "US");
|
||||||
|
@ -474,7 +505,9 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
String jsonString = getCompletedFormAsJsonString();
|
String jsonString = getCompletedFormAsJsonString();
|
||||||
MoneyGramAccount paymentAccount = (MoneyGramAccount) createPaymentAccount(aliceClient, jsonString);
|
MoneyGramAccount paymentAccount = (MoneyGramAccount) createPaymentAccount(aliceClient, jsonString);
|
||||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
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);
|
verifyCommonFormEntries(paymentAccount);
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getFullName());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getFullName());
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
|
||||||
|
@ -497,13 +530,65 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
String jsonString = getCompletedFormAsJsonString();
|
String jsonString = getCompletedFormAsJsonString();
|
||||||
PerfectMoneyAccount paymentAccount = (PerfectMoneyAccount) createPaymentAccount(aliceClient, jsonString);
|
PerfectMoneyAccount paymentAccount = (PerfectMoneyAccount) createPaymentAccount(aliceClient, jsonString);
|
||||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
verifyAccountSingleTradeCurrency(USD, paymentAccount);
|
||||||
verifyCommonFormEntries(paymentAccount);
|
verifyCommonFormEntries(paymentAccount);
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_NR), paymentAccount.getAccountNr());
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||||
print(paymentAccount);
|
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
|
@Test
|
||||||
public void testCreatePopmoneyAccount(TestInfo testInfo) {
|
public void testCreatePopmoneyAccount(TestInfo testInfo) {
|
||||||
File emptyForm = getEmptyForm(testInfo, POPMONEY_ID);
|
File emptyForm = getEmptyForm(testInfo, POPMONEY_ID);
|
||||||
|
@ -519,7 +604,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
String jsonString = getCompletedFormAsJsonString();
|
String jsonString = getCompletedFormAsJsonString();
|
||||||
PopmoneyAccount paymentAccount = (PopmoneyAccount) createPaymentAccount(aliceClient, jsonString);
|
PopmoneyAccount paymentAccount = (PopmoneyAccount) createPaymentAccount(aliceClient, jsonString);
|
||||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
verifyAccountSingleTradeCurrency(USD, paymentAccount);
|
||||||
verifyCommonFormEntries(paymentAccount);
|
verifyCommonFormEntries(paymentAccount);
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_ID), paymentAccount.getAccountId());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_ID), paymentAccount.getAccountId());
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
||||||
|
@ -554,12 +639,19 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
PROPERTY_NAME_USERNAME);
|
PROPERTY_NAME_USERNAME);
|
||||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, REVOLUT_ID);
|
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_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_USERNAME, "revolut123");
|
||||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||||
String jsonString = getCompletedFormAsJsonString();
|
String jsonString = getCompletedFormAsJsonString();
|
||||||
RevolutAccount paymentAccount = (RevolutAccount) createPaymentAccount(aliceClient, jsonString);
|
RevolutAccount paymentAccount = (RevolutAccount) createPaymentAccount(aliceClient, jsonString);
|
||||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
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);
|
verifyCommonFormEntries(paymentAccount);
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_USERNAME), paymentAccount.getUserName());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_USERNAME), paymentAccount.getUserName());
|
||||||
print(paymentAccount);
|
print(paymentAccount);
|
||||||
|
@ -631,7 +723,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||||
Objects.requireNonNull(paymentAccount.getCountry()).code);
|
Objects.requireNonNull(paymentAccount.getCountry()).code);
|
||||||
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
|
verifyAccountSingleTradeCurrency(EUR, paymentAccount);
|
||||||
verifyCommonFormEntries(paymentAccount);
|
verifyCommonFormEntries(paymentAccount);
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_IBAN), paymentAccount.getIban());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_IBAN), paymentAccount.getIban());
|
||||||
|
@ -662,7 +754,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_COUNTRY),
|
||||||
Objects.requireNonNull(paymentAccount.getCountry()).code);
|
Objects.requireNonNull(paymentAccount.getCountry()).code);
|
||||||
verifyAccountSingleTradeCurrency("EUR", paymentAccount);
|
verifyAccountSingleTradeCurrency(EUR, paymentAccount);
|
||||||
verifyCommonFormEntries(paymentAccount);
|
verifyCommonFormEntries(paymentAccount);
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_IBAN), paymentAccount.getIban());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_IBAN), paymentAccount.getIban());
|
||||||
|
@ -720,6 +812,64 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
print(paymentAccount);
|
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
|
@Test
|
||||||
public void testCreateSwishAccount(TestInfo testInfo) {
|
public void testCreateSwishAccount(TestInfo testInfo) {
|
||||||
File emptyForm = getEmptyForm(testInfo, SWISH_ID);
|
File emptyForm = getEmptyForm(testInfo, SWISH_ID);
|
||||||
|
@ -751,17 +901,16 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
PROPERTY_NAME_EMAIL);
|
PROPERTY_NAME_EMAIL);
|
||||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, TRANSFERWISE_ID);
|
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_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_EMAIL, "jane@doe.info");
|
||||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||||
String jsonString = getCompletedFormAsJsonString();
|
String jsonString = getCompletedFormAsJsonString();
|
||||||
TransferwiseAccount paymentAccount = (TransferwiseAccount) createPaymentAccount(aliceClient, jsonString);
|
TransferwiseAccount paymentAccount = (TransferwiseAccount) createPaymentAccount(aliceClient, jsonString);
|
||||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||||
assertEquals(1, paymentAccount.getTradeCurrencies().size());
|
assertEquals(1, paymentAccount.getTradeCurrencies().size());
|
||||||
TradeCurrency expectedCurrency = getTradeCurrency("EUR").get();
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
|
||||||
assertEquals(expectedCurrency, paymentAccount.getSelectedTradeCurrency());
|
paymentAccount.getSelectedTradeCurrency().getCode());
|
||||||
List<TradeCurrency> expectedTradeCurrencies = singletonList(expectedCurrency);
|
|
||||||
verifyAccountTradeCurrencies(expectedTradeCurrencies, paymentAccount);
|
|
||||||
verifyCommonFormEntries(paymentAccount);
|
verifyCommonFormEntries(paymentAccount);
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
|
||||||
print(paymentAccount);
|
print(paymentAccount);
|
||||||
|
@ -775,7 +924,8 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
PROPERTY_NAME_EMAIL);
|
PROPERTY_NAME_EMAIL);
|
||||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_PAYMENT_METHOD_ID, TRANSFERWISE_ID);
|
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_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_EMAIL, "jane@doe.info");
|
||||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, "");
|
||||||
String jsonString = getCompletedFormAsJsonString();
|
String jsonString = getCompletedFormAsJsonString();
|
||||||
|
@ -787,7 +937,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
add(getTradeCurrency("CAD").get());
|
add(getTradeCurrency("CAD").get());
|
||||||
add(getTradeCurrency("HRK").get());
|
add(getTradeCurrency("HRK").get());
|
||||||
add(getTradeCurrency("CZK").get());
|
add(getTradeCurrency("CZK").get());
|
||||||
add(getTradeCurrency("EUR").get());
|
add(getTradeCurrency(EUR).get());
|
||||||
add(getTradeCurrency("HKD").get());
|
add(getTradeCurrency("HKD").get());
|
||||||
add(getTradeCurrency("IDR").get());
|
add(getTradeCurrency("IDR").get());
|
||||||
add(getTradeCurrency("JPY").get());
|
add(getTradeCurrency("JPY").get());
|
||||||
|
@ -795,8 +945,34 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
add(getTradeCurrency("NZD").get());
|
add(getTradeCurrency("NZD").get());
|
||||||
}};
|
}};
|
||||||
verifyAccountTradeCurrencies(expectedTradeCurrencies, paymentAccount);
|
verifyAccountTradeCurrencies(expectedTradeCurrencies, paymentAccount);
|
||||||
TradeCurrency expectedSelectedCurrency = expectedTradeCurrencies.get(0);
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SELECTED_TRADE_CURRENCY),
|
||||||
assertEquals(expectedSelectedCurrency, paymentAccount.getSelectedTradeCurrency());
|
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);
|
verifyCommonFormEntries(paymentAccount);
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_EMAIL), paymentAccount.getEmail());
|
||||||
print(paymentAccount);
|
print(paymentAccount);
|
||||||
|
@ -836,7 +1012,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
|
|
||||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||||
createPaymentAccount(aliceClient, jsonString));
|
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());
|
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_PAYMENT_METHOD_ID, UPHOLD_ID);
|
||||||
COMPLETED_FORM_MAP.put(PROPERTY_NAME_ACCOUNT_NAME, "Uphold Acct");
|
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_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"));
|
COMPLETED_FORM_MAP.put(PROPERTY_NAME_SALT, encodeToHex("Restored Uphold Acct Salt"));
|
||||||
String jsonString = getCompletedFormAsJsonString();
|
String jsonString = getCompletedFormAsJsonString();
|
||||||
UpholdAccount paymentAccount = (UpholdAccount) createPaymentAccount(aliceClient, jsonString);
|
UpholdAccount paymentAccount = (UpholdAccount) createPaymentAccount(aliceClient, jsonString);
|
||||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
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);
|
verifyCommonFormEntries(paymentAccount);
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_ID), paymentAccount.getAccountId());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_ACCOUNT_ID), paymentAccount.getAccountId());
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_SALT), paymentAccount.getSaltAsHex());
|
||||||
|
@ -875,7 +1058,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
String jsonString = getCompletedFormAsJsonString();
|
String jsonString = getCompletedFormAsJsonString();
|
||||||
USPostalMoneyOrderAccount paymentAccount = (USPostalMoneyOrderAccount) createPaymentAccount(aliceClient, jsonString);
|
USPostalMoneyOrderAccount paymentAccount = (USPostalMoneyOrderAccount) createPaymentAccount(aliceClient, jsonString);
|
||||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
verifyAccountSingleTradeCurrency(USD, paymentAccount);
|
||||||
verifyCommonFormEntries(paymentAccount);
|
verifyCommonFormEntries(paymentAccount);
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getHolderName());
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_POSTAL_ADDRESS), paymentAccount.getPostalAddress());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_POSTAL_ADDRESS), paymentAccount.getPostalAddress());
|
||||||
|
@ -923,7 +1106,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
String jsonString = getCompletedFormAsJsonString();
|
String jsonString = getCompletedFormAsJsonString();
|
||||||
WesternUnionAccount paymentAccount = (WesternUnionAccount) createPaymentAccount(aliceClient, jsonString);
|
WesternUnionAccount paymentAccount = (WesternUnionAccount) createPaymentAccount(aliceClient, jsonString);
|
||||||
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
verifyUserPayloadHasPaymentAccountWithId(aliceClient, paymentAccount.getId());
|
||||||
verifyAccountSingleTradeCurrency("USD", paymentAccount);
|
verifyAccountSingleTradeCurrency(USD, paymentAccount);
|
||||||
verifyCommonFormEntries(paymentAccount);
|
verifyCommonFormEntries(paymentAccount);
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getFullName());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_HOLDER_NAME), paymentAccount.getFullName());
|
||||||
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_CITY), paymentAccount.getCity());
|
assertEquals(COMPLETED_FORM_MAP.get(PROPERTY_NAME_CITY), paymentAccount.getCity());
|
||||||
|
@ -942,7 +1125,7 @@ public class CreatePaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
private void print(PaymentAccount paymentAccount) {
|
private void print(PaymentAccount paymentAccount) {
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("Deserialized {}: {}", paymentAccount.getClass().getSimpleName(), paymentAccount);
|
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 bisq.proto.grpc.TradeInfo;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.TestInfo;
|
import org.junit.jupiter.api.TestInfo;
|
||||||
|
|
||||||
import static bisq.cli.TradeFormat.format;
|
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static bisq.core.trade.Trade.Phase.DEPOSIT_UNLOCKED;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
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.apitest.method.offer.AbstractOfferTest;
|
||||||
|
import bisq.cli.CliMain;
|
||||||
|
import bisq.cli.GrpcClient;
|
||||||
|
import bisq.cli.table.builder.TableBuilder;
|
||||||
|
|
||||||
public class AbstractTradeTest extends AbstractOfferTest {
|
public class AbstractTradeTest extends AbstractOfferTest {
|
||||||
|
|
||||||
public static final ExpectedProtocolStatus EXPECTED_PROTOCOL_STATUS = new ExpectedProtocolStatus();
|
public static final ExpectedProtocolStatus EXPECTED_PROTOCOL_STATUS = new ExpectedProtocolStatus();
|
||||||
|
|
||||||
// A Trade ID cache for use in @Test sequences.
|
// A Trade ID cache for use in @Test sequences.
|
||||||
|
@Getter
|
||||||
protected static String tradeId;
|
protected static String tradeId;
|
||||||
|
|
||||||
protected final Supplier<Integer> maxTradeStateAndPhaseChecks = () -> isLongRunningTest ? 10 : 2;
|
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
|
@BeforeAll
|
||||||
public static void initStaticFixtures() {
|
public static void initStaticFixtures() {
|
||||||
|
@ -32,13 +51,129 @@ public class AbstractTradeTest extends AbstractOfferTest {
|
||||||
|
|
||||||
protected final TradeInfo takeAlicesOffer(String offerId,
|
protected final TradeInfo takeAlicesOffer(String offerId,
|
||||||
String paymentAccountId) {
|
String paymentAccountId) {
|
||||||
return bobClient.takeOffer(offerId, paymentAccountId);
|
return takeAlicesOffer(offerId,
|
||||||
|
paymentAccountId,
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
protected final TradeInfo takeAlicesOffer(String offerId,
|
||||||
protected final TradeInfo takeBobsOffer(String offerId,
|
String paymentAccountId,
|
||||||
String paymentAccountId) {
|
boolean generateBtcBlock) {
|
||||||
return aliceClient.takeOffer(offerId, paymentAccountId);
|
@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) {
|
protected final void verifyExpectedProtocolStatus(TradeInfo trade) {
|
||||||
|
@ -49,35 +184,54 @@ public class AbstractTradeTest extends AbstractOfferTest {
|
||||||
if (!isLongRunningTest)
|
if (!isLongRunningTest)
|
||||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isDepositPublished, trade.getIsDepositPublished());
|
assertEquals(EXPECTED_PROTOCOL_STATUS.isDepositPublished, trade.getIsDepositPublished());
|
||||||
|
|
||||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isDepositUnlocked, trade.getIsDepositUnlocked());
|
assertEquals(EXPECTED_PROTOCOL_STATUS.isDepositConfirmed, trade.getIsDepositUnlocked());
|
||||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isPaymentSent, trade.getIsPaymentSent());
|
assertEquals(EXPECTED_PROTOCOL_STATUS.isPaymentStartedMessageSent, trade.getIsPaymentSent());
|
||||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isPaymentReceived, trade.getIsPaymentReceived());
|
assertEquals(EXPECTED_PROTOCOL_STATUS.isPaymentReceivedMessageSent, trade.getIsPaymentReceived());
|
||||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isPayoutPublished, trade.getIsPayoutPublished());
|
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,
|
protected final void logTrade(Logger log,
|
||||||
TestInfo testInfo,
|
TestInfo testInfo,
|
||||||
String description,
|
String description,
|
||||||
TradeInfo trade) {
|
TradeInfo trade) {
|
||||||
logTrade(log, testInfo, description, trade, false);
|
if (log.isDebugEnabled()) {
|
||||||
}
|
log.debug(format("%s %s%n%s",
|
||||||
|
|
||||||
protected final void logTrade(Logger log,
|
|
||||||
TestInfo testInfo,
|
|
||||||
String description,
|
|
||||||
TradeInfo trade,
|
|
||||||
boolean force) {
|
|
||||||
if (force)
|
|
||||||
log.info(String.format("%s %s%n%s",
|
|
||||||
testName(testInfo),
|
testName(testInfo),
|
||||||
description.toUpperCase(),
|
description,
|
||||||
format(trade)));
|
new TableBuilder(TRADE_DETAIL_TBL, trade).build()));
|
||||||
else if (log.isDebugEnabled()) {
|
|
||||||
log.debug(String.format("%s %s%n%s",
|
|
||||||
testName(testInfo),
|
|
||||||
description.toUpperCase(),
|
|
||||||
format(trade)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.State state;
|
||||||
Trade.Phase phase;
|
Trade.Phase phase;
|
||||||
boolean isDepositPublished;
|
boolean isDepositPublished;
|
||||||
boolean isDepositUnlocked;
|
boolean isDepositConfirmed;
|
||||||
boolean isPaymentSent;
|
boolean isPaymentStartedMessageSent;
|
||||||
boolean isPaymentReceived;
|
boolean isPaymentReceivedMessageSent;
|
||||||
boolean isPayoutPublished;
|
boolean isPayoutPublished;
|
||||||
boolean isWithdrawn;
|
boolean isCompleted;
|
||||||
|
|
||||||
public ExpectedProtocolStatus setState(Trade.State state) {
|
public ExpectedProtocolStatus setState(Trade.State state) {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
|
@ -31,18 +31,18 @@ public class ExpectedProtocolStatus {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExpectedProtocolStatus setDepositUnlocked(boolean depositUnlocked) {
|
public ExpectedProtocolStatus setDepositConfirmed(boolean depositConfirmed) {
|
||||||
isDepositUnlocked = depositUnlocked;
|
isDepositConfirmed = depositConfirmed;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExpectedProtocolStatus setFiatSent(boolean paymentSent) {
|
public ExpectedProtocolStatus setPaymentStartedMessageSent(boolean paymentStartedMessageSent) {
|
||||||
isPaymentSent = paymentSent;
|
isPaymentStartedMessageSent = paymentStartedMessageSent;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExpectedProtocolStatus setFiatReceived(boolean paymentReceived) {
|
public ExpectedProtocolStatus setPaymentReceivedMessageSent(boolean paymentReceivedMessageSent) {
|
||||||
isPaymentReceived = paymentReceived;
|
isPaymentReceivedMessageSent = paymentReceivedMessageSent;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,8 +51,8 @@ public class ExpectedProtocolStatus {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExpectedProtocolStatus setWithdrawn(boolean withdrawn) {
|
public ExpectedProtocolStatus setCompleted(boolean completed) {
|
||||||
isWithdrawn = withdrawn;
|
isCompleted = completed;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,10 +60,10 @@ public class ExpectedProtocolStatus {
|
||||||
state = null;
|
state = null;
|
||||||
phase = null;
|
phase = null;
|
||||||
isDepositPublished = false;
|
isDepositPublished = false;
|
||||||
isDepositUnlocked = false;
|
isDepositConfirmed = false;
|
||||||
isPaymentSent = false;
|
isPaymentStartedMessageSent = false;
|
||||||
isPaymentReceived = false;
|
isPaymentReceivedMessageSent = false;
|
||||||
isPayoutPublished = false;
|
isPayoutPublished = false;
|
||||||
isWithdrawn = false;
|
isCompleted = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,12 +19,8 @@ package bisq.apitest.method.trade;
|
||||||
|
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
|
|
||||||
import bisq.proto.grpc.TradeInfo;
|
|
||||||
|
|
||||||
import io.grpc.StatusRuntimeException;
|
import io.grpc.StatusRuntimeException;
|
||||||
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
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.TestInfo;
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
import static bisq.apitest.config.ApiTestConfig.USD;
|
||||||
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.Phase.PAYOUT_PUBLISHED;
|
||||||
import static bisq.core.trade.Trade.State.*;
|
import static bisq.core.trade.Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG;
|
||||||
import static java.lang.String.format;
|
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.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 org.junit.jupiter.api.Assertions.fail;
|
||||||
import static protobuf.Offer.State.OFFER_FEE_RESERVED;
|
import static protobuf.OfferDirection.BUY;
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
|
||||||
import static protobuf.OpenOffer.State.AVAILABLE;
|
import static protobuf.OpenOffer.State.AVAILABLE;
|
||||||
|
|
||||||
@Disabled
|
@Disabled
|
||||||
|
@ -61,62 +54,35 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||||
try {
|
try {
|
||||||
PaymentAccount alicesUsdAccount = createDummyF2FAccount(aliceClient, "US");
|
PaymentAccount alicesUsdAccount = createDummyF2FAccount(aliceClient, "US");
|
||||||
var alicesOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
|
var alicesOffer = aliceClient.createMarketBasedPricedOffer(BUY.name(),
|
||||||
"usd",
|
USD,
|
||||||
12_500_000L,
|
12_500_000L,
|
||||||
12_500_000L, // min-amount = amount
|
12_500_000L, // min-amount = amount
|
||||||
0.00,
|
0.00,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
defaultBuyerSecurityDepositPct.get(),
|
||||||
alicesUsdAccount.getId());
|
alicesUsdAccount.getId(),
|
||||||
|
NO_TRIGGER_PRICE);
|
||||||
var offerId = alicesOffer.getId();
|
var offerId = alicesOffer.getId();
|
||||||
|
|
||||||
// Wait for Alice's AddToOfferBook task.
|
// Wait for Alice's AddToOfferBook task.
|
||||||
// Wait times vary; my logs show >= 2 second delay.
|
// Wait times vary; my logs show >= 2-second delay.
|
||||||
sleep(3000); // TODO loop instead of hard code wait time
|
sleep(3_000); // TODO loop instead of hard code a wait time
|
||||||
var alicesUsdOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), "usd");
|
var alicesUsdOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), USD);
|
||||||
assertEquals(1, alicesUsdOffers.size());
|
assertEquals(1, alicesUsdOffers.size());
|
||||||
|
|
||||||
PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobClient, "US");
|
PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobClient, "US");
|
||||||
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId());
|
var trade = takeAlicesOffer(offerId,
|
||||||
assertNotNull(trade);
|
bobsUsdAccount.getId(),
|
||||||
assertEquals(offerId, trade.getTradeId());
|
false);
|
||||||
// Cache the trade id for the other tests.
|
sleep(2_500); // Allow available offer to be removed from offer book.
|
||||||
tradeId = trade.getTradeId();
|
alicesUsdOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), USD);
|
||||||
|
|
||||||
genBtcBlocksThenWait(1, 4000);
|
|
||||||
alicesUsdOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), "usd");
|
|
||||||
assertEquals(0, alicesUsdOffers.size());
|
assertEquals(0, alicesUsdOffers.size());
|
||||||
|
genBtcBlocksThenWait(1, 2_500);
|
||||||
|
waitForDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
|
||||||
|
|
||||||
genBtcBlocksThenWait(1, 2500);
|
trade = bobClient.getTrade(tradeId);
|
||||||
|
verifyTakerDepositConfirmed(trade);
|
||||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
logTrade(log, testInfo, "Alice's Maker/Buyer View", aliceClient.getTrade(tradeId));
|
||||||
trade = bobClient.getTrade(trade.getTradeId());
|
logTrade(log, testInfo, "Bob's Taker/Seller View", bobClient.getTrade(tradeId));
|
||||||
|
|
||||||
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()));
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (StatusRuntimeException e) {
|
} catch (StatusRuntimeException e) {
|
||||||
fail(e);
|
fail(e);
|
||||||
}
|
}
|
||||||
|
@ -127,56 +93,10 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||||
public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) {
|
public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) {
|
||||||
try {
|
try {
|
||||||
var trade = aliceClient.getTrade(tradeId);
|
var trade = aliceClient.getTrade(tradeId);
|
||||||
|
waitForDepositConfirmation(log, testInfo, aliceClient, trade.getTradeId());
|
||||||
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()));
|
|
||||||
}
|
|
||||||
|
|
||||||
aliceClient.confirmPaymentStarted(trade.getTradeId());
|
aliceClient.confirmPaymentStarted(trade.getTradeId());
|
||||||
sleep(6000);
|
sleep(6_000);
|
||||||
|
waitForBuyerSeesPaymentInitiatedMessage(log, testInfo, aliceClient, tradeId);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (StatusRuntimeException e) {
|
} catch (StatusRuntimeException e) {
|
||||||
fail(e);
|
fail(e);
|
||||||
}
|
}
|
||||||
|
@ -186,82 +106,19 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||||
@Order(3)
|
@Order(3)
|
||||||
public void testBobsConfirmPaymentReceived(final TestInfo testInfo) {
|
public void testBobsConfirmPaymentReceived(final TestInfo testInfo) {
|
||||||
try {
|
try {
|
||||||
|
waitForSellerSeesPaymentInitiatedMessage(log, testInfo, bobClient, tradeId);
|
||||||
var trade = bobClient.getTrade(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());
|
bobClient.confirmPaymentReceived(trade.getTradeId());
|
||||||
sleep(3000);
|
sleep(3_000);
|
||||||
|
|
||||||
trade = bobClient.getTrade(tradeId);
|
trade = bobClient.getTrade(tradeId);
|
||||||
// Note: offer.state == available
|
// Note: offer.state == available
|
||||||
assertEquals(AVAILABLE.name(), trade.getOffer().getState());
|
assertEquals(AVAILABLE.name(), trade.getOffer().getState());
|
||||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
||||||
.setPhase(PAYOUT_PUBLISHED)
|
.setPhase(PAYOUT_PUBLISHED)
|
||||||
.setPayoutPublished(true)
|
.setPayoutPublished(true)
|
||||||
.setFiatReceived(true);
|
.setPaymentReceivedMessageSent(true);
|
||||||
verifyExpectedProtocolStatus(trade);
|
verifyExpectedProtocolStatus(trade);
|
||||||
logTrade(log, testInfo, "Bob's view after confirming fiat payment received", 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) {
|
} catch (StatusRuntimeException e) {
|
||||||
fail(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.core.payment.PaymentAccount;
|
||||||
|
|
||||||
import bisq.proto.grpc.TradeInfo;
|
|
||||||
|
|
||||||
import io.grpc.StatusRuntimeException;
|
import io.grpc.StatusRuntimeException;
|
||||||
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
@ -35,20 +31,16 @@ import org.junit.jupiter.api.TestInfo;
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||||
import static bisq.cli.TableFormat.formatBalancesTbls;
|
import static bisq.apitest.config.ApiTestConfig.USD;
|
||||||
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.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
||||||
import static bisq.core.trade.Trade.Phase.WITHDRAWN;
|
import static bisq.core.trade.Trade.Phase.WITHDRAWN;
|
||||||
import static bisq.core.trade.Trade.State.*;
|
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
|
||||||
import static java.lang.String.format;
|
import static bisq.core.trade.Trade.State.WITHDRAW_COMPLETED;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
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 org.junit.jupiter.api.Assertions.fail;
|
||||||
import static protobuf.Offer.State.OFFER_FEE_RESERVED;
|
import static protobuf.Offer.State.OFFER_FEE_RESERVED;
|
||||||
import static protobuf.OfferPayload.Direction.SELL;
|
import static protobuf.OfferDirection.SELL;
|
||||||
import static protobuf.OpenOffer.State.AVAILABLE;
|
|
||||||
|
|
||||||
@Disabled
|
@Disabled
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -57,6 +49,9 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
||||||
|
|
||||||
// Alice is maker/seller, Bob is taker/buyer.
|
// 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";
|
private static final String WITHDRAWAL_TX_MEMO = "Bob's trade withdrawal";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -65,63 +60,35 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
||||||
try {
|
try {
|
||||||
PaymentAccount alicesUsdAccount = createDummyF2FAccount(aliceClient, "US");
|
PaymentAccount alicesUsdAccount = createDummyF2FAccount(aliceClient, "US");
|
||||||
var alicesOffer = aliceClient.createMarketBasedPricedOffer(SELL.name(),
|
var alicesOffer = aliceClient.createMarketBasedPricedOffer(SELL.name(),
|
||||||
"usd",
|
USD,
|
||||||
12_500_000L,
|
12_500_000L,
|
||||||
12_500_000L, // min-amount = amount
|
12_500_000L, // min-amount = amount
|
||||||
0.00,
|
0.00,
|
||||||
getDefaultBuyerSecurityDepositAsPercent(),
|
defaultBuyerSecurityDepositPct.get(),
|
||||||
alicesUsdAccount.getId());
|
alicesUsdAccount.getId(),
|
||||||
|
NO_TRIGGER_PRICE);
|
||||||
var offerId = alicesOffer.getId();
|
var offerId = alicesOffer.getId();
|
||||||
|
|
||||||
// Wait for Alice's AddToOfferBook task.
|
// 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.
|
// seems to require more time to prepare.
|
||||||
sleep(3000); // TODO loop instead of hard code wait time
|
sleep(3_000); // TODO loop instead of hard code a wait time
|
||||||
var alicesUsdOffers = aliceClient.getMyOffersSortedByDate(SELL.name(), "usd");
|
var alicesUsdOffers = aliceClient.getMyOffersSortedByDate(SELL.name(), USD);
|
||||||
assertEquals(1, alicesUsdOffers.size());
|
assertEquals(1, alicesUsdOffers.size());
|
||||||
|
|
||||||
PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobClient, "US");
|
PaymentAccount bobsUsdAccount = createDummyF2FAccount(bobClient, "US");
|
||||||
var trade = takeAlicesOffer(offerId, bobsUsdAccount.getId());
|
var trade = takeAlicesOffer(offerId,
|
||||||
assertNotNull(trade);
|
bobsUsdAccount.getId(),
|
||||||
assertEquals(offerId, trade.getTradeId());
|
false);
|
||||||
// Cache the trade id for the other tests.
|
sleep(2_500); // Allow available offer to be removed from offer book.
|
||||||
tradeId = trade.getTradeId();
|
var takeableUsdOffers = bobClient.getOffersSortedByDate(SELL.name(), USD);
|
||||||
|
|
||||||
genBtcBlocksThenWait(1, 4000);
|
|
||||||
var takeableUsdOffers = bobClient.getOffersSortedByDate(SELL.name(), "usd");
|
|
||||||
assertEquals(0, takeableUsdOffers.size());
|
assertEquals(0, takeableUsdOffers.size());
|
||||||
|
genBtcBlocksThenWait(1, 2_500);
|
||||||
genBtcBlocksThenWait(1, 2500);
|
waitForDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
|
||||||
|
trade = bobClient.getTrade(tradeId);
|
||||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
verifyTakerDepositConfirmed(trade);
|
||||||
trade = bobClient.getTrade(trade.getTradeId());
|
logTrade(log, testInfo, "Alice's Maker/Buyer View", aliceClient.getTrade(tradeId));
|
||||||
|
logTrade(log, testInfo, "Bob's Taker/Seller View", bobClient.getTrade(tradeId));
|
||||||
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()));
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (StatusRuntimeException e) {
|
} catch (StatusRuntimeException e) {
|
||||||
fail(e);
|
fail(e);
|
||||||
}
|
}
|
||||||
|
@ -132,54 +99,10 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
||||||
public void testBobsConfirmPaymentStarted(final TestInfo testInfo) {
|
public void testBobsConfirmPaymentStarted(final TestInfo testInfo) {
|
||||||
try {
|
try {
|
||||||
var trade = bobClient.getTrade(tradeId);
|
var trade = bobClient.getTrade(tradeId);
|
||||||
|
verifyTakerDepositConfirmed(trade);
|
||||||
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()));
|
|
||||||
}
|
|
||||||
|
|
||||||
bobClient.confirmPaymentStarted(tradeId);
|
bobClient.confirmPaymentStarted(tradeId);
|
||||||
sleep(6000);
|
sleep(6_000);
|
||||||
|
waitForBuyerSeesPaymentInitiatedMessage(log, testInfo, bobClient, tradeId);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (StatusRuntimeException e) {
|
} catch (StatusRuntimeException e) {
|
||||||
fail(e);
|
fail(e);
|
||||||
}
|
}
|
||||||
|
@ -189,83 +112,21 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
||||||
@Order(3)
|
@Order(3)
|
||||||
public void testAlicesConfirmPaymentReceived(final TestInfo testInfo) {
|
public void testAlicesConfirmPaymentReceived(final TestInfo testInfo) {
|
||||||
try {
|
try {
|
||||||
|
waitForSellerSeesPaymentInitiatedMessage(log, testInfo, aliceClient, tradeId);
|
||||||
|
|
||||||
var trade = aliceClient.getTrade(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());
|
aliceClient.confirmPaymentReceived(trade.getTradeId());
|
||||||
sleep(3000);
|
sleep(3_000);
|
||||||
|
|
||||||
trade = aliceClient.getTrade(tradeId);
|
trade = aliceClient.getTrade(tradeId);
|
||||||
assertEquals(OFFER_FEE_RESERVED.name(), trade.getOffer().getState());
|
assertEquals(OFFER_FEE_RESERVED.name(), trade.getOffer().getState());
|
||||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
||||||
.setPhase(PAYOUT_PUBLISHED)
|
.setPhase(PAYOUT_PUBLISHED)
|
||||||
.setPayoutPublished(true)
|
.setPayoutPublished(true)
|
||||||
.setFiatReceived(true);
|
.setPaymentReceivedMessageSent(true);
|
||||||
verifyExpectedProtocolStatus(trade);
|
verifyExpectedProtocolStatus(trade);
|
||||||
logTrade(log, testInfo, "Alice's view after confirming fiat payment received", trade);
|
logTrade(log, testInfo, "Alice's view after confirming fiat payment received", trade);
|
||||||
} catch (StatusRuntimeException e) {
|
} catch (StatusRuntimeException e) {
|
||||||
fail(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)
|
@Order(2)
|
||||||
public void testSetInvalidTxFeeRateShouldThrowException(final TestInfo testInfo) {
|
public void testSetInvalidTxFeeRateShouldThrowException(final TestInfo testInfo) {
|
||||||
var currentTxFeeRateInfo = TxFeeRateInfo.fromProto(aliceClient.getTxFeeRate());
|
var currentTxFeeRateInfo = TxFeeRateInfo.fromProto(aliceClient.getTxFeeRate());
|
||||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.setTxFeeRate(1));
|
||||||
aliceClient.setTxFeeRate(10));
|
|
||||||
String expectedExceptionMessage =
|
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());
|
currentTxFeeRateInfo.getMinFeeServiceRate());
|
||||||
assertEquals(expectedExceptionMessage, exception.getMessage());
|
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.config.HavenoAppConfig.seednode;
|
||||||
import static bisq.apitest.method.wallet.WalletTestUtil.INITIAL_BTC_BALANCES;
|
import static bisq.apitest.method.wallet.WalletTestUtil.INITIAL_BTC_BALANCES;
|
||||||
import static bisq.apitest.method.wallet.WalletTestUtil.verifyBtcBalances;
|
import static bisq.apitest.method.wallet.WalletTestUtil.verifyBtcBalances;
|
||||||
import static bisq.cli.TableFormat.formatAddressBalanceTbl;
|
import static bisq.cli.table.builder.TableType.ADDRESS_BALANCE_TBL;
|
||||||
import static bisq.cli.TableFormat.formatBtcBalanceInfoTbl;
|
import static bisq.cli.table.builder.TableType.BTC_BALANCE_TBL;
|
||||||
import static java.util.Collections.singletonList;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
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.apitest.method.MethodTest;
|
||||||
|
import bisq.cli.table.builder.TableBuilder;
|
||||||
|
|
||||||
@Disabled
|
@Disabled
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -54,10 +54,14 @@ public class BtcWalletTest extends MethodTest {
|
||||||
// Bob & Alice's regtest Bisq wallets were initialized with 10 BTC.
|
// Bob & Alice's regtest Bisq wallets were initialized with 10 BTC.
|
||||||
|
|
||||||
BtcBalanceInfo alicesBalances = aliceClient.getBtcBalances();
|
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();
|
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(), alicesBalances.getAvailableBalance());
|
||||||
assertEquals(INITIAL_BTC_BALANCES.getAvailableBalance(), bobsBalances.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{}",
|
log.debug("{} -> Alice's Funded Address Balance -> \n{}",
|
||||||
testName(testInfo),
|
testName(testInfo),
|
||||||
formatAddressBalanceTbl(singletonList(aliceClient.getAddressBalance(newAddress))));
|
new TableBuilder(ADDRESS_BALANCE_TBL,
|
||||||
|
aliceClient.getAddressBalance(newAddress)));
|
||||||
|
|
||||||
// New balance is 12.5 BTC
|
// New balance is 12.5 BTC
|
||||||
btcBalanceInfo = aliceClient.getBtcBalances();
|
btcBalanceInfo = aliceClient.getBtcBalances();
|
||||||
|
@ -88,7 +93,7 @@ public class BtcWalletTest extends MethodTest {
|
||||||
verifyBtcBalances(alicesExpectedBalances, btcBalanceInfo);
|
verifyBtcBalances(alicesExpectedBalances, btcBalanceInfo);
|
||||||
log.debug("{} -> Alice's BTC Balances After Sending 2.5 BTC -> \n{}",
|
log.debug("{} -> Alice's BTC Balances After Sending 2.5 BTC -> \n{}",
|
||||||
testName(testInfo),
|
testName(testInfo),
|
||||||
formatBtcBalanceInfoTbl(btcBalanceInfo));
|
new TableBuilder(BTC_BALANCE_TBL, btcBalanceInfo).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -115,7 +120,7 @@ public class BtcWalletTest extends MethodTest {
|
||||||
BtcBalanceInfo alicesBalances = aliceClient.getBtcBalances();
|
BtcBalanceInfo alicesBalances = aliceClient.getBtcBalances();
|
||||||
log.debug("{} Alice's BTC Balances:\n{}",
|
log.debug("{} Alice's BTC Balances:\n{}",
|
||||||
testName(testInfo),
|
testName(testInfo),
|
||||||
formatBtcBalanceInfoTbl(alicesBalances));
|
new TableBuilder(BTC_BALANCE_TBL, alicesBalances).build());
|
||||||
bisq.core.api.model.BtcBalanceInfo alicesExpectedBalances =
|
bisq.core.api.model.BtcBalanceInfo alicesExpectedBalances =
|
||||||
bisq.core.api.model.BtcBalanceInfo.valueOf(700000000,
|
bisq.core.api.model.BtcBalanceInfo.valueOf(700000000,
|
||||||
0,
|
0,
|
||||||
|
@ -126,7 +131,7 @@ public class BtcWalletTest extends MethodTest {
|
||||||
BtcBalanceInfo bobsBalances = bobClient.getBtcBalances();
|
BtcBalanceInfo bobsBalances = bobClient.getBtcBalances();
|
||||||
log.debug("{} Bob's BTC Balances:\n{}",
|
log.debug("{} Bob's BTC Balances:\n{}",
|
||||||
testName(testInfo),
|
testName(testInfo),
|
||||||
formatBtcBalanceInfoTbl(bobsBalances));
|
new TableBuilder(BTC_BALANCE_TBL, bobsBalances).build());
|
||||||
// The sendbtc tx weight and size randomly varies between two distinct values
|
// 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
|
// (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].
|
// the assertion of an available balance range [1549978000, 1549978100].
|
||||||
|
|
|
@ -48,7 +48,7 @@ public class WalletProtectionTest extends MethodTest {
|
||||||
@Order(2)
|
@Order(2)
|
||||||
public void testGetBalanceOnEncryptedWalletShouldThrowException() {
|
public void testGetBalanceOnEncryptedWalletShouldThrowException() {
|
||||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
|
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
|
||||||
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
|
assertEquals("FAILED_PRECONDITION: wallet is locked", exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -58,7 +58,7 @@ public class WalletProtectionTest extends MethodTest {
|
||||||
aliceClient.getBtcBalances(); // should not throw 'wallet locked' exception
|
aliceClient.getBtcBalances(); // should not throw 'wallet locked' exception
|
||||||
sleep(4500); // let unlock timeout expire
|
sleep(4500); // let unlock timeout expire
|
||||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
|
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
|
||||||
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
|
assertEquals("FAILED_PRECONDITION: wallet is locked", exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -67,7 +67,7 @@ public class WalletProtectionTest extends MethodTest {
|
||||||
aliceClient.unlockWallet("first-password", 3);
|
aliceClient.unlockWallet("first-password", 3);
|
||||||
sleep(4000); // let unlock timeout expire
|
sleep(4000); // let unlock timeout expire
|
||||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
|
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
|
||||||
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
|
assertEquals("FAILED_PRECONDITION: wallet is locked", exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -76,14 +76,14 @@ public class WalletProtectionTest extends MethodTest {
|
||||||
aliceClient.unlockWallet("first-password", 60);
|
aliceClient.unlockWallet("first-password", 60);
|
||||||
aliceClient.lockWallet();
|
aliceClient.lockWallet();
|
||||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
|
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getBtcBalances());
|
||||||
assertEquals("UNKNOWN: wallet is locked", exception.getMessage());
|
assertEquals("FAILED_PRECONDITION: wallet is locked", exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(6)
|
@Order(6)
|
||||||
public void testLockWalletWhenWalletAlreadyLockedShouldThrowException() {
|
public void testLockWalletWhenWalletAlreadyLockedShouldThrowException() {
|
||||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.lockWallet());
|
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
|
@Test
|
||||||
|
@ -110,7 +110,7 @@ public class WalletProtectionTest extends MethodTest {
|
||||||
public void testSetNewWalletPasswordWithIncorrectNewPasswordShouldThrowException() {
|
public void testSetNewWalletPasswordWithIncorrectNewPasswordShouldThrowException() {
|
||||||
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
Throwable exception = assertThrows(StatusRuntimeException.class, () ->
|
||||||
aliceClient.setWalletPassword("bad old password", "irrelevant"));
|
aliceClient.setWalletPassword("bad old password", "irrelevant"));
|
||||||
assertEquals("UNKNOWN: incorrect old password", exception.getMessage());
|
assertEquals("INVALID_ARGUMENT: incorrect old password", exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -9,7 +9,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class WalletTestUtil {
|
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.
|
// are initialized with 10 BTC during the scaffolding setup.
|
||||||
public static final bisq.core.api.model.BtcBalanceInfo INITIAL_BTC_BALANCES =
|
public static final bisq.core.api.model.BtcBalanceInfo INITIAL_BTC_BALANCES =
|
||||||
bisq.core.api.model.BtcBalanceInfo.valueOf(1000000000,
|
bisq.core.api.model.BtcBalanceInfo.valueOf(1000000000,
|
||||||
|
@ -17,7 +17,6 @@ public class WalletTestUtil {
|
||||||
1000000000,
|
1000000000,
|
||||||
0);
|
0);
|
||||||
|
|
||||||
|
|
||||||
public static void verifyBtcBalances(bisq.core.api.model.BtcBalanceInfo expected,
|
public static void verifyBtcBalances(bisq.core.api.model.BtcBalanceInfo expected,
|
||||||
BtcBalanceInfo actual) {
|
BtcBalanceInfo actual) {
|
||||||
assertEquals(expected.getAvailableBalance(), actual.getAvailableBalance());
|
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.testTakeAlicesBuyOffer(testInfo);
|
||||||
test.testAlicesConfirmPaymentStarted(testInfo);
|
test.testAlicesConfirmPaymentStarted(testInfo);
|
||||||
test.testBobsConfirmPaymentReceived(testInfo);
|
test.testBobsConfirmPaymentReceived(testInfo);
|
||||||
test.testAlicesKeepFunds(testInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testTakeSellBTCOffer(final TestInfo testInfo) {
|
public void testTakeSellBTCOffer(final TestInfo testInfo) {
|
||||||
|
@ -80,7 +79,6 @@ public class LongRunningTradesTest extends AbstractTradeTest {
|
||||||
test.testTakeAlicesSellOffer(testInfo);
|
test.testTakeAlicesSellOffer(testInfo);
|
||||||
test.testBobsConfirmPaymentStarted(testInfo);
|
test.testBobsConfirmPaymentStarted(testInfo);
|
||||||
test.testAlicesConfirmPaymentReceived(testInfo);
|
test.testAlicesConfirmPaymentReceived(testInfo);
|
||||||
test.testBobsBtcWithdrawalToExternalAddress(testInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static boolean envLongRunningTestEnabled() {
|
protected static boolean envLongRunningTestEnabled() {
|
||||||
|
|
|
@ -20,6 +20,7 @@ package bisq.apitest.scenario;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.MethodOrderer;
|
import org.junit.jupiter.api.MethodOrderer;
|
||||||
import org.junit.jupiter.api.Order;
|
import org.junit.jupiter.api.Order;
|
||||||
import org.junit.jupiter.api.Test;
|
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.CancelOfferTest;
|
||||||
import bisq.apitest.method.offer.CreateOfferUsingFixedPriceTest;
|
import bisq.apitest.method.offer.CreateOfferUsingFixedPriceTest;
|
||||||
import bisq.apitest.method.offer.CreateOfferUsingMarketPriceMarginTest;
|
import bisq.apitest.method.offer.CreateOfferUsingMarketPriceMarginTest;
|
||||||
|
import bisq.apitest.method.offer.CreateXMROffersTest;
|
||||||
import bisq.apitest.method.offer.ValidateCreateOfferTest;
|
import bisq.apitest.method.offer.ValidateCreateOfferTest;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
public class OfferTest extends AbstractOfferTest {
|
public class OfferTest extends AbstractOfferTest {
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void setUp() {
|
||||||
|
setUp(false); // Use setUp(true) for running API daemons in remote debug mode.
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(1)
|
@Order(1)
|
||||||
public void testAmtTooLargeShouldThrowException() {
|
public void testCreateOfferValidation() {
|
||||||
ValidateCreateOfferTest test = new ValidateCreateOfferTest();
|
ValidateCreateOfferTest test = new ValidateCreateOfferTest();
|
||||||
test.testAmtTooLargeShouldThrowException();
|
test.testAmtTooLargeShouldThrowException();
|
||||||
test.testNoMatchingEURPaymentAccountShouldThrowException();
|
test.testNoMatchingEURPaymentAccountShouldThrowException();
|
||||||
|
@ -57,18 +64,32 @@ public class OfferTest extends AbstractOfferTest {
|
||||||
@Order(3)
|
@Order(3)
|
||||||
public void testCreateOfferUsingFixedPrice() {
|
public void testCreateOfferUsingFixedPrice() {
|
||||||
CreateOfferUsingFixedPriceTest test = new CreateOfferUsingFixedPriceTest();
|
CreateOfferUsingFixedPriceTest test = new CreateOfferUsingFixedPriceTest();
|
||||||
test.testCreateAUDXMRBuyOfferUsingFixedPrice16000();
|
test.testCreateAUDBTCBuyOfferUsingFixedPrice16000();
|
||||||
test.testCreateUSDXMRBuyOfferUsingFixedPrice100001234();
|
test.testCreateUSDBTCBuyOfferUsingFixedPrice100001234();
|
||||||
test.testCreateEURXMRSellOfferUsingFixedPrice95001234();
|
test.testCreateEURBTCSellOfferUsingFixedPrice95001234();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(4)
|
@Order(4)
|
||||||
public void testCreateOfferUsingMarketPriceMargin() {
|
public void testCreateOfferUsingMarketPriceMarginPct() {
|
||||||
CreateOfferUsingMarketPriceMarginTest test = new CreateOfferUsingMarketPriceMarginTest();
|
CreateOfferUsingMarketPriceMarginTest test = new CreateOfferUsingMarketPriceMarginTest();
|
||||||
test.testCreateUSDXMRBuyOffer5PctPriceMargin();
|
test.testCreateUSDBTCBuyOffer5PctPriceMargin();
|
||||||
test.testCreateNZDXMRBuyOfferMinus2PctPriceMargin();
|
test.testCreateNZDBTCBuyOfferMinus2PctPriceMargin();
|
||||||
test.testCreateGBPXMRSellOfferMinus1Point5PctPriceMargin();
|
test.testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin();
|
||||||
test.testCreateBRLXMRSellOffer6Point55PctPriceMargin();
|
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.testCreateAdvancedCashAccount(testInfo);
|
||||||
test.testCreateAliPayAccount(testInfo);
|
test.testCreateAliPayAccount(testInfo);
|
||||||
test.testCreateAustraliaPayidAccount(testInfo);
|
test.testCreateAustraliaPayidAccount(testInfo);
|
||||||
|
test.testCreateCapitualAccount(testInfo);
|
||||||
test.testCreateCashDepositAccount(testInfo);
|
test.testCreateCashDepositAccount(testInfo);
|
||||||
test.testCreateBrazilNationalBankAccount(testInfo);
|
test.testCreateBrazilNationalBankAccount(testInfo);
|
||||||
test.testCreateChaseQuickPayAccount(testInfo);
|
|
||||||
test.testCreateClearXChangeAccount(testInfo);
|
test.testCreateClearXChangeAccount(testInfo);
|
||||||
test.testCreateF2FAccount(testInfo);
|
test.testCreateF2FAccount(testInfo);
|
||||||
test.testCreateFasterPaymentsAccount(testInfo);
|
test.testCreateFasterPaymentsAccount(testInfo);
|
||||||
|
@ -61,6 +61,8 @@ public class PaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
test.testCreateMoneyBeamAccount(testInfo);
|
test.testCreateMoneyBeamAccount(testInfo);
|
||||||
test.testCreateMoneyGramAccount(testInfo);
|
test.testCreateMoneyGramAccount(testInfo);
|
||||||
test.testCreatePerfectMoneyAccount(testInfo);
|
test.testCreatePerfectMoneyAccount(testInfo);
|
||||||
|
test.testCreatePaxumAccount(testInfo);
|
||||||
|
test.testCreatePayseraAccount(testInfo);
|
||||||
test.testCreatePopmoneyAccount(testInfo);
|
test.testCreatePopmoneyAccount(testInfo);
|
||||||
test.testCreatePromptPayAccount(testInfo);
|
test.testCreatePromptPayAccount(testInfo);
|
||||||
test.testCreateRevolutAccount(testInfo);
|
test.testCreateRevolutAccount(testInfo);
|
||||||
|
@ -68,12 +70,12 @@ public class PaymentAccountTest extends AbstractPaymentAccountTest {
|
||||||
test.testCreateSepaInstantAccount(testInfo);
|
test.testCreateSepaInstantAccount(testInfo);
|
||||||
test.testCreateSepaAccount(testInfo);
|
test.testCreateSepaAccount(testInfo);
|
||||||
test.testCreateSpecificBanksAccount(testInfo);
|
test.testCreateSpecificBanksAccount(testInfo);
|
||||||
|
test.testCreateSwiftAccount(testInfo);
|
||||||
test.testCreateSwishAccount(testInfo);
|
test.testCreateSwishAccount(testInfo);
|
||||||
|
|
||||||
// TransferwiseAccount is only PaymentAccount with a
|
|
||||||
// tradeCurrencies field in the json form.
|
|
||||||
test.testCreateTransferwiseAccountWith1TradeCurrency(testInfo);
|
test.testCreateTransferwiseAccountWith1TradeCurrency(testInfo);
|
||||||
test.testCreateTransferwiseAccountWith10TradeCurrencies(testInfo);
|
test.testCreateTransferwiseAccountWith10TradeCurrencies(testInfo);
|
||||||
|
test.testCreateTransferwiseAccountWithSupportedTradeCurrencies(testInfo);
|
||||||
test.testCreateTransferwiseAccountWithInvalidBrlTradeCurrencyShouldThrowException(testInfo);
|
test.testCreateTransferwiseAccountWithInvalidBrlTradeCurrencyShouldThrowException(testInfo);
|
||||||
test.testCreateTransferwiseAccountWithoutTradeCurrenciesShouldThrowException(testInfo);
|
test.testCreateTransferwiseAccountWithoutTradeCurrenciesShouldThrowException(testInfo);
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind;
|
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.alicedaemon;
|
||||||
import static bisq.apitest.config.HavenoAppConfig.arbdaemon;
|
import static bisq.apitest.config.HavenoAppConfig.arbdaemon;
|
||||||
import static bisq.apitest.config.HavenoAppConfig.seednode;
|
import static bisq.apitest.config.HavenoAppConfig.seednode;
|
||||||
|
@ -54,7 +55,7 @@ public class StartupTest extends MethodTest {
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
public static void setUp() {
|
public static void setUp() {
|
||||||
try {
|
try {
|
||||||
callRateMeteringConfigFile = defaultRateMeterInterceptorConfig();
|
callRateMeteringConfigFile = getTestRateMeterInterceptorConfig();
|
||||||
startSupportingApps(callRateMeteringConfigFile,
|
startSupportingApps(callRateMeteringConfigFile,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
|
|
@ -30,7 +30,10 @@ import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
import bisq.apitest.method.trade.AbstractTradeTest;
|
import bisq.apitest.method.trade.AbstractTradeTest;
|
||||||
import bisq.apitest.method.trade.TakeBuyBTCOfferTest;
|
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.TakeSellBTCOfferTest;
|
||||||
|
import bisq.apitest.method.trade.TakeSellXMROfferTest;
|
||||||
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -49,7 +52,6 @@ public class TradeTest extends AbstractTradeTest {
|
||||||
test.testTakeAlicesBuyOffer(testInfo);
|
test.testTakeAlicesBuyOffer(testInfo);
|
||||||
test.testAlicesConfirmPaymentStarted(testInfo);
|
test.testAlicesConfirmPaymentStarted(testInfo);
|
||||||
test.testBobsConfirmPaymentReceived(testInfo);
|
test.testBobsConfirmPaymentReceived(testInfo);
|
||||||
test.testAlicesKeepFunds(testInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -59,6 +61,35 @@ public class TradeTest extends AbstractTradeTest {
|
||||||
test.testTakeAlicesSellOffer(testInfo);
|
test.testTakeAlicesSellOffer(testInfo);
|
||||||
test.testBobsConfirmPaymentStarted(testInfo);
|
test.testBobsConfirmPaymentStarted(testInfo);
|
||||||
test.testAlicesConfirmPaymentReceived(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.locale.CountryUtil.findCountryByCode;
|
||||||
import static bisq.core.payment.payload.PaymentMethod.CLEAR_X_CHANGE_ID;
|
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.String.format;
|
||||||
import static java.lang.System.getProperty;
|
import static java.lang.System.getProperty;
|
||||||
import static java.nio.file.Files.readAllBytes;
|
import static java.nio.file.Files.readAllBytes;
|
||||||
|
@ -74,7 +74,7 @@ public abstract class AbstractBotTest extends MethodTest {
|
||||||
} else {
|
} else {
|
||||||
throw new UnsupportedOperationException(
|
throw new UnsupportedOperationException(
|
||||||
format("This test harness bot does not work with %s payment accounts yet.",
|
format("This test harness bot does not work with %s payment accounts yet.",
|
||||||
getPaymentMethodById(paymentMethodId).getDisplayString()));
|
getPaymentMethod(paymentMethodId).getDisplayString()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
String countryCode = botScript.getCountryCode();
|
String countryCode = botScript.getCountryCode();
|
||||||
|
|
|
@ -8,7 +8,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import static bisq.core.locale.CountryUtil.findCountryByCode;
|
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.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.String.format;
|
||||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ class Bot {
|
||||||
} else {
|
} else {
|
||||||
throw new UnsupportedOperationException(
|
throw new UnsupportedOperationException(
|
||||||
format("This bot test does not work with %s payment accounts yet.",
|
format("This bot test does not work with %s payment accounts yet.",
|
||||||
getPaymentMethodById(paymentMethodId).getDisplayString()));
|
getPaymentMethod(paymentMethodId).getDisplayString()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Country country = findCountry(botScript.getCountryCode());
|
Country country = findCountry(botScript.getCountryCode());
|
||||||
|
|
|
@ -39,11 +39,6 @@ import bisq.cli.GrpcClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience GrpcClient wrapper for bots using gRPC services.
|
* 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"})
|
@SuppressWarnings({"JavaDoc", "unused"})
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -124,6 +119,8 @@ public class BotClient {
|
||||||
* @param minAmountInSatoshis
|
* @param minAmountInSatoshis
|
||||||
* @param priceMarginAsPercent
|
* @param priceMarginAsPercent
|
||||||
* @param securityDepositAsPercent
|
* @param securityDepositAsPercent
|
||||||
|
* @param feeCurrency
|
||||||
|
* @param triggerPrice
|
||||||
* @return OfferInfo
|
* @return OfferInfo
|
||||||
*/
|
*/
|
||||||
public OfferInfo createOfferAtMarketBasedPrice(PaymentAccount paymentAccount,
|
public OfferInfo createOfferAtMarketBasedPrice(PaymentAccount paymentAccount,
|
||||||
|
@ -132,14 +129,16 @@ public class BotClient {
|
||||||
long amountInSatoshis,
|
long amountInSatoshis,
|
||||||
long minAmountInSatoshis,
|
long minAmountInSatoshis,
|
||||||
double priceMarginAsPercent,
|
double priceMarginAsPercent,
|
||||||
double securityDepositAsPercent) {
|
double securityDepositAsPercent,
|
||||||
|
String triggerPrice) {
|
||||||
return grpcClient.createMarketBasedPricedOffer(direction,
|
return grpcClient.createMarketBasedPricedOffer(direction,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
amountInSatoshis,
|
amountInSatoshis,
|
||||||
minAmountInSatoshis,
|
minAmountInSatoshis,
|
||||||
priceMarginAsPercent,
|
priceMarginAsPercent,
|
||||||
securityDepositAsPercent,
|
securityDepositAsPercent,
|
||||||
paymentAccount.getId());
|
paymentAccount.getId(),
|
||||||
|
triggerPrice);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -151,6 +150,7 @@ public class BotClient {
|
||||||
* @param minAmountInSatoshis
|
* @param minAmountInSatoshis
|
||||||
* @param fixedOfferPriceAsString
|
* @param fixedOfferPriceAsString
|
||||||
* @param securityDepositAsPercent
|
* @param securityDepositAsPercent
|
||||||
|
* @param feeCurrency
|
||||||
* @return OfferInfo
|
* @return OfferInfo
|
||||||
*/
|
*/
|
||||||
public OfferInfo createOfferAtFixedPrice(PaymentAccount paymentAccount,
|
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
|
* @param tradeId a valid trade id
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public boolean isTakerDepositFeeTxUnlocked(String tradeId) {
|
public boolean isTakerDepositFeeTxConfirmed(String tradeId) {
|
||||||
return grpcClient.getTrade(tradeId).getIsDepositUnlocked();
|
return grpcClient.getTrade(tradeId).getIsDepositUnlocked();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,15 +278,6 @@ public class BotClient {
|
||||||
grpcClient.confirmPaymentReceived(tradeId);
|
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.
|
* Create and save a new PaymentAccount with details in the given json.
|
||||||
* @param json
|
* @param json
|
||||||
|
|
|
@ -33,10 +33,10 @@ import java.util.function.Supplier;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.cli.CurrencyFormat.formatSatoshis;
|
||||||
import static bisq.common.util.MathUtils.scaleDownByPowerOf10;
|
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 bisq.core.payment.payload.PaymentMethod.F2F_ID;
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
import static java.math.RoundingMode.HALF_UP;
|
import static java.math.RoundingMode.HALF_UP;
|
||||||
|
@ -124,7 +124,8 @@ public class RandomOffer {
|
||||||
amount,
|
amount,
|
||||||
minAmount,
|
minAmount,
|
||||||
priceMargin,
|
priceMargin,
|
||||||
getDefaultBuyerSecurityDepositAsPercent());
|
defaultBuyerSecurityDepositPct.get(),
|
||||||
|
"0" /*no trigger price*/);
|
||||||
} else {
|
} else {
|
||||||
this.offer = botClient.createOfferAtFixedPrice(paymentAccount,
|
this.offer = botClient.createOfferAtFixedPrice(paymentAccount,
|
||||||
direction,
|
direction,
|
||||||
|
@ -132,7 +133,7 @@ public class RandomOffer {
|
||||||
amount,
|
amount,
|
||||||
minAmount,
|
minAmount,
|
||||||
fixedOfferPrice,
|
fixedOfferPrice,
|
||||||
getDefaultBuyerSecurityDepositAsPercent());
|
defaultBuyerSecurityDepositPct.get());
|
||||||
}
|
}
|
||||||
this.id = offer.getId();
|
this.id = offer.getId();
|
||||||
return this;
|
return this;
|
||||||
|
@ -162,11 +163,11 @@ public class RandomOffer {
|
||||||
log.info(description);
|
log.info(description);
|
||||||
if (useMarketBasedPrice) {
|
if (useMarketBasedPrice) {
|
||||||
log.info("Offer Price Margin = {}%", priceMargin);
|
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 {
|
} else {
|
||||||
|
|
||||||
log.info("Fixed Offer Price = {} {}", fixedOfferPrice, currencyCode);
|
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.protocol.ProtocolStep.DONE;
|
||||||
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.isShutdownCalled;
|
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;
|
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.BashScriptGenerator;
|
||||||
import bisq.apitest.scenario.bot.script.BotScript;
|
import bisq.apitest.scenario.bot.script.BotScript;
|
||||||
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
||||||
|
import bisq.cli.table.builder.TableBuilder;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public
|
public
|
||||||
|
@ -74,10 +75,14 @@ class RobotBob extends Bot {
|
||||||
throw new IllegalStateException(botProtocol.getClass().getSimpleName() + " failed to complete.");
|
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{}",
|
log.info("Completed {} successful trade{}. Current Balance:\n{}",
|
||||||
++numTrades,
|
++numTrades,
|
||||||
numTrades == 1 ? "" : "s",
|
numTrades == 1 ? "" : "s",
|
||||||
formatBalancesTbls(botClient.getBalance()));
|
balancesBuilder);
|
||||||
|
|
||||||
if (numTrades < actions.length) {
|
if (numTrades < actions.length) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -39,6 +39,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.*;
|
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.*;
|
||||||
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.checkIfShutdownCalled;
|
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.String.format;
|
||||||
import static java.lang.System.currentTimeMillis;
|
import static java.lang.System.currentTimeMillis;
|
||||||
import static java.util.Arrays.stream;
|
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.BotClient;
|
||||||
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
||||||
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
||||||
import bisq.cli.TradeFormat;
|
import bisq.cli.table.builder.TableBuilder;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public abstract class BotProtocol {
|
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.",
|
log.info("Starting protocol step {}. Bot will shutdown if step not completed within {} minutes.",
|
||||||
currentProtocolStep.name(), MILLISECONDS.toMinutes(protocolStepTimeLimitInMs));
|
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.");
|
log.info("Generate a btc block to trigger taker's deposit fee tx confirmation.");
|
||||||
createGenerateBtcBlockScript();
|
createGenerateBtcBlockScript();
|
||||||
}
|
}
|
||||||
|
@ -133,7 +134,8 @@ public abstract class BotProtocol {
|
||||||
try {
|
try {
|
||||||
var t = this.getBotClient().getTrade(trade.getTradeId());
|
var t = this.getBotClient().getTrade(trade.getTradeId());
|
||||||
if (t.getIsPaymentSent()) {
|
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;
|
return t;
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
@ -167,7 +169,8 @@ public abstract class BotProtocol {
|
||||||
try {
|
try {
|
||||||
var t = this.getBotClient().getTrade(trade.getTradeId());
|
var t = this.getBotClient().getTrade(trade.getTradeId());
|
||||||
if (t.getIsPaymentReceived()) {
|
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;
|
return t;
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
@ -202,7 +205,7 @@ public abstract class BotProtocol {
|
||||||
if (t.getIsPayoutPublished()) {
|
if (t.getIsPayoutPublished()) {
|
||||||
log.info("Payout tx {} has been published for trade:\n{}",
|
log.info("Payout tx {} has been published for trade:\n{}",
|
||||||
t.getPayoutTxId(),
|
t.getPayoutTxId(),
|
||||||
TradeFormat.format(t));
|
new TableBuilder(TRADE_DETAIL_TBL, t).build().toString());
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} 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) {
|
protected void createPaymentStartedScript(TradeInfo trade) {
|
||||||
File script = bashScriptGenerator.createPaymentStartedScript(trade);
|
File script = bashScriptGenerator.createPaymentStartedScript(trade);
|
||||||
printCliHintAndOrScript(script, "The manual CLI side can send a 'payment started' message");
|
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) {
|
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) {
|
private void waitForTakerDepositFee(String tradeId, ProtocolStep depositTxProtocolStep) {
|
||||||
initProtocolStep.accept(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 {
|
try {
|
||||||
log.info(waitingForDepositFeeTxMsg(tradeId));
|
log.info(waitingForDepositFeeTxMsg(tradeId));
|
||||||
while (isWithinProtocolStepTimeLimit()) {
|
while (isWithinProtocolStepTimeLimit()) {
|
||||||
|
@ -316,8 +304,8 @@ public abstract class BotProtocol {
|
||||||
if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED) && trade.getIsDepositPublished()) {
|
if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED) && trade.getIsDepositPublished()) {
|
||||||
log.info("Taker deposit fee tx {} has been published.", trade.getTakerDepositTxId());
|
log.info("Taker deposit fee tx {} has been published.", trade.getTakerDepositTxId());
|
||||||
return true;
|
return true;
|
||||||
} else if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_UNLOCKED) && trade.getIsDepositUnlocked()) {
|
} else if (currentProtocolStep.equals(WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED) && trade.getIsDepositUnlocked()) {
|
||||||
log.info("Taker deposit fee tx {} is unlocked.", trade.getTakerDepositTxId());
|
log.info("Taker deposit fee tx {} has been confirmed.", trade.getTakerDepositTxId());
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
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.DONE;
|
||||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.WAIT_FOR_OFFER_TAKER;
|
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.WAIT_FOR_OFFER_TAKER;
|
||||||
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.checkIfShutdownCalled;
|
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 java.util.Collections.singletonList;
|
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.RandomOffer;
|
||||||
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
||||||
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
||||||
import bisq.cli.TradeFormat;
|
import bisq.cli.table.builder.TableBuilder;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class MakerBotProtocol extends BotProtocol {
|
public class MakerBotProtocol extends BotProtocol {
|
||||||
|
@ -56,16 +56,13 @@ public class MakerBotProtocol extends BotProtocol {
|
||||||
: waitForPaymentStartedMessage.andThen(sendPaymentReceivedMessage);
|
: waitForPaymentStartedMessage.andThen(sendPaymentReceivedMessage);
|
||||||
completeFiatTransaction.apply(trade);
|
completeFiatTransaction.apply(trade);
|
||||||
|
|
||||||
Function<TradeInfo, TradeInfo> closeTrade = waitForPayoutTx.andThen(keepFundsFromTrade);
|
|
||||||
closeTrade.apply(trade);
|
|
||||||
|
|
||||||
currentProtocolStep = DONE;
|
currentProtocolStep = DONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Supplier<OfferInfo> randomOffer = () -> {
|
private final Supplier<OfferInfo> randomOffer = () -> {
|
||||||
checkIfShutdownCalled("Interrupted before creating random offer.");
|
checkIfShutdownCalled("Interrupted before creating random offer.");
|
||||||
OfferInfo offer = new RandomOffer(botClient, paymentAccount).create().getOffer();
|
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;
|
return offer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -98,7 +95,9 @@ public class MakerBotProtocol extends BotProtocol {
|
||||||
private Optional<TradeInfo> getNewTrade(String offerId) {
|
private Optional<TradeInfo> getNewTrade(String offerId) {
|
||||||
try {
|
try {
|
||||||
var trade = botClient.getTrade(offerId);
|
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);
|
return Optional.of(trade);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
// Get trade will throw a non-fatal gRPC exception if not found.
|
// Get trade will throw a non-fatal gRPC exception if not found.
|
||||||
|
|
|
@ -6,12 +6,12 @@ public enum ProtocolStep {
|
||||||
TAKE_OFFER,
|
TAKE_OFFER,
|
||||||
WAIT_FOR_OFFER_TAKER,
|
WAIT_FOR_OFFER_TAKER,
|
||||||
WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED,
|
WAIT_FOR_TAKER_DEPOSIT_TX_PUBLISHED,
|
||||||
WAIT_FOR_TAKER_DEPOSIT_TX_UNLOCKED,
|
WAIT_FOR_TAKER_DEPOSIT_TX_CONFIRMED,
|
||||||
SEND_PAYMENT_STARTED_MESSAGE,
|
SEND_PAYMENT_STARTED_MESSAGE,
|
||||||
WAIT_FOR_PAYMENT_STARTED_MESSAGE,
|
WAIT_FOR_PAYMENT_STARTED_MESSAGE,
|
||||||
SEND_PAYMENT_RECEIVED_CONFIRMATION_MESSAGE,
|
SEND_PAYMENT_RECEIVED_CONFIRMATION_MESSAGE,
|
||||||
WAIT_FOR_PAYMENT_RECEIVED_CONFIRMATION_MESSAGE,
|
WAIT_FOR_PAYMENT_RECEIVED_CONFIRMATION_MESSAGE,
|
||||||
WAIT_FOR_PAYOUT_TX,
|
WAIT_FOR_PAYOUT_TX,
|
||||||
KEEP_FUNDS,
|
CLOSE_TRADE,
|
||||||
DONE
|
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.FIND_OFFER;
|
||||||
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.TAKE_OFFER;
|
import static bisq.apitest.scenario.bot.protocol.ProtocolStep.TAKE_OFFER;
|
||||||
import static bisq.apitest.scenario.bot.shutdown.ManualShutdown.checkIfShutdownCalled;
|
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;
|
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.BotClient;
|
||||||
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
import bisq.apitest.scenario.bot.script.BashScriptGenerator;
|
||||||
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
import bisq.apitest.scenario.bot.shutdown.ManualBotShutdownException;
|
||||||
|
import bisq.cli.table.builder.TableBuilder;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class TakerBotProtocol extends BotProtocol {
|
public class TakerBotProtocol extends BotProtocol {
|
||||||
|
@ -55,16 +56,13 @@ public class TakerBotProtocol extends BotProtocol {
|
||||||
: sendPaymentStartedMessage.andThen(waitForPaymentReceivedConfirmation);
|
: sendPaymentStartedMessage.andThen(waitForPaymentReceivedConfirmation);
|
||||||
completeFiatTransaction.apply(trade);
|
completeFiatTransaction.apply(trade);
|
||||||
|
|
||||||
Function<TradeInfo, TradeInfo> closeTrade = waitForPayoutTx.andThen(keepFundsFromTrade);
|
|
||||||
closeTrade.apply(trade);
|
|
||||||
|
|
||||||
currentProtocolStep = DONE;
|
currentProtocolStep = DONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Supplier<Optional<OfferInfo>> firstOffer = () -> {
|
private final Supplier<Optional<OfferInfo>> firstOffer = () -> {
|
||||||
var offers = botClient.getOffers(currencyCode);
|
var offers = botClient.getOffers(currencyCode);
|
||||||
if (offers.size() > 0) {
|
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);
|
OfferInfo offer = offers.get(0);
|
||||||
log.info("Will take first offer {}", offer.getId());
|
log.info("Will take first offer {}", offer.getId());
|
||||||
return Optional.of(offer);
|
return Optional.of(offer);
|
||||||
|
@ -107,7 +105,6 @@ public class TakerBotProtocol extends BotProtocol {
|
||||||
|
|
||||||
private void createMakeOfferScript() {
|
private void createMakeOfferScript() {
|
||||||
String direction = RANDOM.nextBoolean() ? "BUY" : "SELL";
|
String direction = RANDOM.nextBoolean() ? "BUY" : "SELL";
|
||||||
String feeCurrency = "BTC";
|
|
||||||
boolean createMarginPricedOffer = RANDOM.nextBoolean();
|
boolean createMarginPricedOffer = RANDOM.nextBoolean();
|
||||||
// If not using an F2F account, don't go over possible 0.01 BTC
|
// If not using an F2F account, don't go over possible 0.01 BTC
|
||||||
// limit if account is not signed.
|
// limit if account is not signed.
|
||||||
|
@ -120,15 +117,13 @@ public class TakerBotProtocol extends BotProtocol {
|
||||||
currencyCode,
|
currencyCode,
|
||||||
amount,
|
amount,
|
||||||
"0.0",
|
"0.0",
|
||||||
"15.0",
|
"15.0");
|
||||||
feeCurrency);
|
|
||||||
} else {
|
} else {
|
||||||
script = bashScriptGenerator.createMakeFixedPricedOfferScript(direction,
|
script = bashScriptGenerator.createMakeFixedPricedOfferScript(direction,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
amount,
|
amount,
|
||||||
botClient.getCurrentBTCMarketPriceAsIntegerString(currencyCode),
|
botClient.getCurrentBTCMarketPriceAsIntegerString(currencyCode),
|
||||||
"15.0",
|
"15.0");
|
||||||
feeCurrency);
|
|
||||||
}
|
}
|
||||||
printCliHintAndOrScript(script, "The manual CLI side can create an offer");
|
printCliHintAndOrScript(script, "The manual CLI side can create an offer");
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,8 +67,7 @@ public class BashScriptGenerator {
|
||||||
String currencyCode,
|
String currencyCode,
|
||||||
String amount,
|
String amount,
|
||||||
String marketPriceMargin,
|
String marketPriceMargin,
|
||||||
String securityDeposit,
|
String securityDeposit) {
|
||||||
String feeCurrency) {
|
|
||||||
String makeOfferCmd = format("%s createoffer --payment-account=%s "
|
String makeOfferCmd = format("%s createoffer --payment-account=%s "
|
||||||
+ " --direction=%s"
|
+ " --direction=%s"
|
||||||
+ " --currency-code=%s"
|
+ " --currency-code=%s"
|
||||||
|
@ -82,8 +81,7 @@ public class BashScriptGenerator {
|
||||||
currencyCode,
|
currencyCode,
|
||||||
amount,
|
amount,
|
||||||
marketPriceMargin,
|
marketPriceMargin,
|
||||||
securityDeposit,
|
securityDeposit);
|
||||||
feeCurrency);
|
|
||||||
String getOffersCmd = format("%s getmyoffers --direction=%s --currency-code=%s",
|
String getOffersCmd = format("%s getmyoffers --direction=%s --currency-code=%s",
|
||||||
cliBase,
|
cliBase,
|
||||||
direction,
|
direction,
|
||||||
|
@ -98,8 +96,7 @@ public class BashScriptGenerator {
|
||||||
String currencyCode,
|
String currencyCode,
|
||||||
String amount,
|
String amount,
|
||||||
String fixedPrice,
|
String fixedPrice,
|
||||||
String securityDeposit,
|
String securityDeposit) {
|
||||||
String feeCurrency) {
|
|
||||||
String makeOfferCmd = format("%s createoffer --payment-account=%s "
|
String makeOfferCmd = format("%s createoffer --payment-account=%s "
|
||||||
+ " --direction=%s"
|
+ " --direction=%s"
|
||||||
+ " --currency-code=%s"
|
+ " --currency-code=%s"
|
||||||
|
@ -113,8 +110,7 @@ public class BashScriptGenerator {
|
||||||
currencyCode,
|
currencyCode,
|
||||||
amount,
|
amount,
|
||||||
fixedPrice,
|
fixedPrice,
|
||||||
securityDeposit,
|
securityDeposit);
|
||||||
feeCurrency);
|
|
||||||
String getOffersCmd = format("%s getmyoffers --direction=%s --currency-code=%s",
|
String getOffersCmd = format("%s getmyoffers --direction=%s --currency-code=%s",
|
||||||
cliBase,
|
cliBase,
|
||||||
direction,
|
direction,
|
||||||
|
@ -167,10 +163,10 @@ public class BashScriptGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
public File createKeepFundsScript(TradeInfo trade) {
|
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 getTradeCmd = format("%s gettrade --trade-id=%s", cliBase, trade.getTradeId());
|
||||||
String getBalanceCmd = format("%s getbalance", cliBase);
|
String getBalanceCmd = format("%s getbalance", cliBase);
|
||||||
return createCliScript("keepfunds.sh",
|
return createCliScript("closetrade.sh",
|
||||||
paymentStartedCmd,
|
paymentStartedCmd,
|
||||||
"sleep 2",
|
"sleep 2",
|
||||||
getTradeCmd,
|
getTradeCmd,
|
||||||
|
|
|
@ -17,8 +17,9 @@
|
||||||
|
|
||||||
package bisq.apitest.scenario.bot.script;
|
package bisq.apitest.scenario.bot.script;
|
||||||
|
|
||||||
|
import bisq.core.util.JsonUtil;
|
||||||
|
|
||||||
import bisq.common.file.JsonFileManager;
|
import bisq.common.file.JsonFileManager;
|
||||||
import bisq.common.util.Utilities;
|
|
||||||
|
|
||||||
import joptsimple.BuiltinHelpFormatter;
|
import joptsimple.BuiltinHelpFormatter;
|
||||||
import joptsimple.OptionParser;
|
import joptsimple.OptionParser;
|
||||||
|
@ -214,7 +215,7 @@ public class BotScriptGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String generateBotScriptTemplate() {
|
private String generateBotScriptTemplate() {
|
||||||
return Utilities.objectToJson(new BotScript(
|
return JsonUtil.objectToJson(new BotScript(
|
||||||
useTestHarness,
|
useTestHarness,
|
||||||
botPaymentMethodId,
|
botPaymentMethodId,
|
||||||
countryCode,
|
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'
|
bcVersion = '1.63'
|
||||||
bitcoinjVersion = '2a80db4'
|
bitcoinjVersion = '2a80db4'
|
||||||
codecVersion = '1.13'
|
codecVersion = '1.13'
|
||||||
|
cowwocVersion = '1.2'
|
||||||
easybindVersion = '1.0.3'
|
easybindVersion = '1.0.3'
|
||||||
easyVersion = '4.0.1'
|
easyVersion = '4.0.1'
|
||||||
findbugsVersion = '3.0.2'
|
findbugsVersion = '3.0.2'
|
||||||
|
@ -404,6 +405,7 @@ configure(project(':cli')) {
|
||||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$jupiterVersion")
|
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$jupiterVersion")
|
||||||
testAnnotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
testAnnotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
||||||
testCompileOnly "org.projectlombok:lombok:$lombokVersion"
|
testCompileOnly "org.projectlombok:lombok:$lombokVersion"
|
||||||
|
testImplementation "org.bitbucket.cowwoc:diff-match-patch:$cowwocVersion"
|
||||||
testRuntime "javax.annotation:javax.annotation-api:$javaxAnnotationVersion"
|
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;
|
package bisq.cli;
|
||||||
|
|
||||||
import bisq.proto.grpc.OfferInfo;
|
import bisq.proto.grpc.OfferInfo;
|
||||||
|
import bisq.proto.grpc.TradeInfo;
|
||||||
|
|
||||||
import io.grpc.StatusRuntimeException;
|
import io.grpc.StatusRuntimeException;
|
||||||
|
|
||||||
|
@ -39,18 +40,18 @@ import java.util.List;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.formatTxFeeRateInfo;
|
||||||
import static bisq.cli.CurrencyFormat.toSatoshis;
|
import static bisq.cli.CurrencyFormat.toSatoshis;
|
||||||
import static bisq.cli.CurrencyFormat.toSecurityDepositAsPct;
|
|
||||||
import static bisq.cli.Method.*;
|
import static bisq.cli.Method.*;
|
||||||
import static bisq.cli.TableFormat.*;
|
|
||||||
import static bisq.cli.opts.OptLabel.*;
|
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.String.format;
|
||||||
import static java.lang.System.err;
|
import static java.lang.System.err;
|
||||||
import static java.lang.System.exit;
|
import static java.lang.System.exit;
|
||||||
import static java.lang.System.out;
|
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.GetAddressBalanceOptionParser;
|
||||||
import bisq.cli.opts.GetBTCMarketPriceOptionParser;
|
import bisq.cli.opts.GetBTCMarketPriceOptionParser;
|
||||||
import bisq.cli.opts.GetBalanceOptionParser;
|
import bisq.cli.opts.GetBalanceOptionParser;
|
||||||
import bisq.cli.opts.GetOfferOptionParser;
|
|
||||||
import bisq.cli.opts.GetOffersOptionParser;
|
import bisq.cli.opts.GetOffersOptionParser;
|
||||||
import bisq.cli.opts.GetPaymentAcctFormOptionParser;
|
import bisq.cli.opts.GetPaymentAcctFormOptionParser;
|
||||||
import bisq.cli.opts.GetTradeOptionParser;
|
import bisq.cli.opts.GetTradeOptionParser;
|
||||||
|
import bisq.cli.opts.GetTradesOptionParser;
|
||||||
import bisq.cli.opts.GetTransactionOptionParser;
|
import bisq.cli.opts.GetTransactionOptionParser;
|
||||||
|
import bisq.cli.opts.OfferIdOptionParser;
|
||||||
import bisq.cli.opts.RegisterDisputeAgentOptionParser;
|
import bisq.cli.opts.RegisterDisputeAgentOptionParser;
|
||||||
import bisq.cli.opts.RemoveWalletPasswordOptionParser;
|
import bisq.cli.opts.RemoveWalletPasswordOptionParser;
|
||||||
import bisq.cli.opts.SendBtcOptionParser;
|
import bisq.cli.opts.SendBtcOptionParser;
|
||||||
|
@ -76,6 +78,7 @@ import bisq.cli.opts.SimpleMethodOptionParser;
|
||||||
import bisq.cli.opts.TakeOfferOptionParser;
|
import bisq.cli.opts.TakeOfferOptionParser;
|
||||||
import bisq.cli.opts.UnlockWalletOptionParser;
|
import bisq.cli.opts.UnlockWalletOptionParser;
|
||||||
import bisq.cli.opts.WithdrawFundsOptionParser;
|
import bisq.cli.opts.WithdrawFundsOptionParser;
|
||||||
|
import bisq.cli.table.builder.TableBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A command-line client for the Bisq gRPC API.
|
* A command-line client for the Bisq gRPC API.
|
||||||
|
@ -167,15 +170,14 @@ public class CliMain {
|
||||||
var balances = client.getBalances(currencyCode);
|
var balances = client.getBalances(currencyCode);
|
||||||
switch (currencyCode.toUpperCase()) {
|
switch (currencyCode.toUpperCase()) {
|
||||||
case "BTC":
|
case "BTC":
|
||||||
out.println(formatBtcBalanceInfoTbl(balances.getBtc()));
|
new TableBuilder(BTC_BALANCE_TBL, balances.getBtc()).build().print(out);
|
||||||
break;
|
break;
|
||||||
case "XMR":
|
|
||||||
out.println(formatXmrBalanceInfoTbl(balances.getXmr()));
|
|
||||||
break;
|
|
||||||
case "":
|
case "":
|
||||||
default:
|
default: {
|
||||||
out.println(formatBalancesTbls(balances));
|
out.println("BTC");
|
||||||
|
new TableBuilder(BTC_BALANCE_TBL, balances.getBtc()).build().print(out);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -187,7 +189,7 @@ public class CliMain {
|
||||||
}
|
}
|
||||||
var address = opts.getAddress();
|
var address = opts.getAddress();
|
||||||
var addressBalance = client.getAddressBalance(address);
|
var addressBalance = client.getAddressBalance(address);
|
||||||
out.println(formatAddressBalanceTbl(singletonList(addressBalance)));
|
new TableBuilder(ADDRESS_BALANCE_TBL, addressBalance).build().print(out);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case getbtcprice: {
|
case getbtcprice: {
|
||||||
|
@ -198,7 +200,7 @@ public class CliMain {
|
||||||
}
|
}
|
||||||
var currencyCode = opts.getCurrencyCode();
|
var currencyCode = opts.getCurrencyCode();
|
||||||
var price = client.getBtcPrice(currencyCode);
|
var price = client.getBtcPrice(currencyCode);
|
||||||
out.println(formatMarketPrice(price));
|
out.println(formatInternalFiatPrice(price));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case getfundingaddresses: {
|
case getfundingaddresses: {
|
||||||
|
@ -207,7 +209,7 @@ public class CliMain {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var fundingAddresses = client.getFundingAddresses();
|
var fundingAddresses = client.getFundingAddresses();
|
||||||
out.println(formatAddressBalanceTbl(fundingAddresses));
|
new TableBuilder(ADDRESS_BALANCE_TBL, fundingAddresses).build().print(out);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case sendbtc: {
|
case sendbtc: {
|
||||||
|
@ -269,7 +271,7 @@ public class CliMain {
|
||||||
}
|
}
|
||||||
var txId = opts.getTxId();
|
var txId = opts.getTxId();
|
||||||
var tx = client.getTransaction(txId);
|
var tx = client.getTransaction(txId);
|
||||||
out.println(TransactionFormat.format(tx));
|
new TableBuilder(TRANSACTION_TBL, tx).build().print(out);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case createoffer: {
|
case createoffer: {
|
||||||
|
@ -285,18 +287,21 @@ public class CliMain {
|
||||||
var minAmount = toSatoshis(opts.getMinAmount());
|
var minAmount = toSatoshis(opts.getMinAmount());
|
||||||
var useMarketBasedPrice = opts.isUsingMktPriceMargin();
|
var useMarketBasedPrice = opts.isUsingMktPriceMargin();
|
||||||
var fixedPrice = opts.getFixedPrice();
|
var fixedPrice = opts.getFixedPrice();
|
||||||
var marketPriceMargin = opts.getMktPriceMarginAsBigDecimal();
|
var marketPriceMarginPct = opts.getMktPriceMarginPct();
|
||||||
var securityDeposit = toSecurityDepositAsPct(opts.getSecurityDeposit());
|
var securityDepositPct = opts.getSecurityDepositPct();
|
||||||
var offer = client.createOffer(direction,
|
var triggerPrice = "0"; // Cannot be defined until the new offer is added to book.
|
||||||
|
OfferInfo offer;
|
||||||
|
offer = client.createOffer(direction,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
amount,
|
amount,
|
||||||
minAmount,
|
minAmount,
|
||||||
useMarketBasedPrice,
|
useMarketBasedPrice,
|
||||||
fixedPrice,
|
fixedPrice,
|
||||||
marketPriceMargin.doubleValue(),
|
marketPriceMarginPct,
|
||||||
securityDeposit,
|
securityDepositPct,
|
||||||
paymentAcctId);
|
paymentAcctId,
|
||||||
out.println(formatOfferTable(singletonList(offer), currencyCode));
|
triggerPrice);
|
||||||
|
new TableBuilder(OFFER_TBL, offer).build().print(out);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case canceloffer: {
|
case canceloffer: {
|
||||||
|
@ -311,25 +316,25 @@ public class CliMain {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case getoffer: {
|
case getoffer: {
|
||||||
var opts = new GetOfferOptionParser(args).parse();
|
var opts = new OfferIdOptionParser(args).parse();
|
||||||
if (opts.isForHelp()) {
|
if (opts.isForHelp()) {
|
||||||
out.println(client.getMethodHelp(method));
|
out.println(client.getMethodHelp(method));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var offerId = opts.getOfferId();
|
var offerId = opts.getOfferId();
|
||||||
var offer = client.getOffer(offerId);
|
var offer = client.getOffer(offerId);
|
||||||
out.println(formatOfferTable(singletonList(offer), offer.getCounterCurrencyCode()));
|
new TableBuilder(OFFER_TBL, offer).build().print(out);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case getmyoffer: {
|
case getmyoffer: {
|
||||||
var opts = new GetOfferOptionParser(args).parse();
|
var opts = new OfferIdOptionParser(args).parse();
|
||||||
if (opts.isForHelp()) {
|
if (opts.isForHelp()) {
|
||||||
out.println(client.getMethodHelp(method));
|
out.println(client.getMethodHelp(method));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var offerId = opts.getOfferId();
|
var offerId = opts.getOfferId();
|
||||||
var offer = client.getMyOffer(offerId);
|
var offer = client.getMyOffer(offerId);
|
||||||
out.println(formatOfferTable(singletonList(offer), offer.getCounterCurrencyCode()));
|
new TableBuilder(OFFER_TBL, offer).build().print(out);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case getoffers: {
|
case getoffers: {
|
||||||
|
@ -344,7 +349,7 @@ public class CliMain {
|
||||||
if (offers.isEmpty())
|
if (offers.isEmpty())
|
||||||
out.printf("no %s %s offers found%n", direction, currencyCode);
|
out.printf("no %s %s offers found%n", direction, currencyCode);
|
||||||
else
|
else
|
||||||
out.println(formatOfferTable(offers, currencyCode));
|
new TableBuilder(OFFER_TBL, offers).build().print(out);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -360,11 +365,12 @@ public class CliMain {
|
||||||
if (offers.isEmpty())
|
if (offers.isEmpty())
|
||||||
out.printf("no %s %s offers found%n", direction, currencyCode);
|
out.printf("no %s %s offers found%n", direction, currencyCode);
|
||||||
else
|
else
|
||||||
out.println(formatOfferTable(offers, currencyCode));
|
new TableBuilder(OFFER_TBL, offers).build().print(out);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case takeoffer: {
|
case takeoffer: {
|
||||||
|
|
||||||
var opts = new TakeOfferOptionParser(args).parse();
|
var opts = new TakeOfferOptionParser(args).parse();
|
||||||
if (opts.isForHelp()) {
|
if (opts.isForHelp()) {
|
||||||
out.println(client.getMethodHelp(method));
|
out.println(client.getMethodHelp(method));
|
||||||
|
@ -389,10 +395,30 @@ public class CliMain {
|
||||||
if (showContract)
|
if (showContract)
|
||||||
out.println(trade.getContractAsJson());
|
out.println(trade.getContractAsJson());
|
||||||
else
|
else
|
||||||
out.println(TradeFormat.format(trade));
|
new TableBuilder(TRADE_DETAIL_TBL, trade).build().print(out);
|
||||||
|
|
||||||
return;
|
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: {
|
case confirmpaymentstarted: {
|
||||||
var opts = new GetTradeOptionParser(args).parse();
|
var opts = new GetTradeOptionParser(args).parse();
|
||||||
if (opts.isForHelp()) {
|
if (opts.isForHelp()) {
|
||||||
|
@ -415,17 +441,6 @@ public class CliMain {
|
||||||
out.printf("trade %s payment received message sent%n", tradeId);
|
out.printf("trade %s payment received message sent%n", tradeId);
|
||||||
return;
|
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: {
|
case withdrawfunds: {
|
||||||
var opts = new WithdrawFundsOptionParser(args).parse();
|
var opts = new WithdrawFundsOptionParser(args).parse();
|
||||||
if (opts.isForHelp()) {
|
if (opts.isForHelp()) {
|
||||||
|
@ -434,7 +449,7 @@ public class CliMain {
|
||||||
}
|
}
|
||||||
var tradeId = opts.getTradeId();
|
var tradeId = opts.getTradeId();
|
||||||
var address = opts.getAddress();
|
var address = opts.getAddress();
|
||||||
// Multi-word memos must be double quoted.
|
// Multi-word memos must be double-quoted.
|
||||||
var memo = opts.getMemo();
|
var memo = opts.getMemo();
|
||||||
client.withdrawFunds(tradeId, address, memo);
|
client.withdrawFunds(tradeId, address, memo);
|
||||||
out.printf("trade %s funds sent to btc address %s%n", tradeId, address);
|
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);
|
var paymentAccount = client.createPaymentAccount(jsonString);
|
||||||
out.println("payment account saved");
|
out.println("payment account saved");
|
||||||
out.println(formatPaymentAcctTbl(singletonList(paymentAccount)));
|
new TableBuilder(PAYMENT_ACCOUNT_TBL, paymentAccount).build().print(out);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case createcryptopaymentacct: {
|
case createcryptopaymentacct: {
|
||||||
var opts = new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse();
|
var opts =
|
||||||
|
new CreateCryptoCurrencyPaymentAcctOptionParser(args).parse();
|
||||||
if (opts.isForHelp()) {
|
if (opts.isForHelp()) {
|
||||||
out.println(client.getMethodHelp(method));
|
out.println(client.getMethodHelp(method));
|
||||||
return;
|
return;
|
||||||
|
@ -499,7 +515,7 @@ public class CliMain {
|
||||||
address,
|
address,
|
||||||
isTradeInstant);
|
isTradeInstant);
|
||||||
out.println("payment account saved");
|
out.println("payment account saved");
|
||||||
out.println(formatPaymentAcctTbl(singletonList(paymentAccount)));
|
new TableBuilder(PAYMENT_ACCOUNT_TBL, paymentAccount).build().print(out);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case getpaymentaccts: {
|
case getpaymentaccts: {
|
||||||
|
@ -509,7 +525,7 @@ public class CliMain {
|
||||||
}
|
}
|
||||||
var paymentAccounts = client.getPaymentAccounts();
|
var paymentAccounts = client.getPaymentAccounts();
|
||||||
if (paymentAccounts.size() > 0)
|
if (paymentAccounts.size() > 0)
|
||||||
out.println(formatPaymentAcctTbl(paymentAccounts));
|
new TableBuilder(PAYMENT_ACCOUNT_TBL, paymentAccounts).build().print(out);
|
||||||
else
|
else
|
||||||
out.println("no payment accounts are saved");
|
out.println("no payment accounts are saved");
|
||||||
|
|
||||||
|
@ -585,7 +601,8 @@ public class CliMain {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (StatusRuntimeException ex) {
|
} 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_]+: ", "");
|
String message = ex.getMessage().replaceFirst("^[A-Z_]+: ", "");
|
||||||
if (message.equals("io exception"))
|
if (message.equals("io exception"))
|
||||||
throw new RuntimeException(message + ", server may not be running", ex);
|
throw new RuntimeException(message + ", server may not be running", ex);
|
||||||
|
@ -666,7 +683,7 @@ public class CliMain {
|
||||||
stream.format(rowFormat, "------", "------", "------------");
|
stream.format(rowFormat, "------", "------", "------------");
|
||||||
stream.format(rowFormat, getversion.name(), "", "Get server version");
|
stream.format(rowFormat, getversion.name(), "", "Get server version");
|
||||||
stream.println();
|
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.println();
|
||||||
stream.format(rowFormat, getaddressbalance.name(), "--address=<btc-address>", "Get server wallet address balance");
|
stream.format(rowFormat, getaddressbalance.name(), "--address=<btc-address>", "Get server wallet address balance");
|
||||||
stream.println();
|
stream.println();
|
||||||
|
@ -674,11 +691,14 @@ public class CliMain {
|
||||||
stream.println();
|
stream.println();
|
||||||
stream.format(rowFormat, getfundingaddresses.name(), "", "Get BTC funding addresses");
|
stream.format(rowFormat, getfundingaddresses.name(), "", "Get BTC funding addresses");
|
||||||
stream.println();
|
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, sendbtc.name(), "--address=<btc-address> --amount=<btc-amount> \\", "Send BTC");
|
||||||
stream.format(rowFormat, "", "[--tx-fee-rate=<sats/byte>]", "");
|
stream.format(rowFormat, "", "[--tx-fee-rate=<sats/byte>]", "");
|
||||||
stream.format(rowFormat, "", "[--memo=<\"memo\">]", "");
|
stream.format(rowFormat, "", "[--memo=<\"memo\">]", "");
|
||||||
stream.println();
|
stream.println();
|
||||||
stream.println();
|
|
||||||
stream.format(rowFormat, gettxfeerate.name(), "", "Get current tx fee rate in sats/byte");
|
stream.format(rowFormat, gettxfeerate.name(), "", "Get current tx fee rate in sats/byte");
|
||||||
stream.println();
|
stream.println();
|
||||||
stream.format(rowFormat, settxfeerate.name(), "--tx-fee-rate=<sats/byte>", "Set custom tx fee rate in sats/byte");
|
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, "", "--currency-code=<currency-code> \\", "");
|
||||||
stream.format(rowFormat, "", "--amount=<btc-amount> \\", "");
|
stream.format(rowFormat, "", "--amount=<btc-amount> \\", "");
|
||||||
stream.format(rowFormat, "", "[--min-amount=<min-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, "", "--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.println();
|
||||||
stream.format(rowFormat, canceloffer.name(), "--offer-id=<offer-id>", "Cancel offer with id");
|
stream.format(rowFormat, canceloffer.name(), "--offer-id=<offer-id>", "Cancel offer with id");
|
||||||
stream.println();
|
stream.println();
|
||||||
|
@ -709,22 +737,28 @@ public class CliMain {
|
||||||
stream.format(rowFormat, "", "--currency-code=<currency-code>", "");
|
stream.format(rowFormat, "", "--currency-code=<currency-code>", "");
|
||||||
stream.println();
|
stream.println();
|
||||||
stream.format(rowFormat, takeoffer.name(), "--offer-id=<offer-id> \\", "Take offer with id");
|
stream.format(rowFormat, takeoffer.name(), "--offer-id=<offer-id> \\", "Take offer with id");
|
||||||
stream.format(rowFormat, "", "--payment-account=<payment-account-id>", "");
|
stream.format(rowFormat, "", "[--payment-account=<payment-account-id>]", "");
|
||||||
stream.format(rowFormat, "", "[--fee-currency=<btc>]", "");
|
stream.format(rowFormat, "", "[--fee-currency=<btc|bsq>]", "");
|
||||||
stream.println();
|
stream.println();
|
||||||
stream.format(rowFormat, gettrade.name(), "--trade-id=<trade-id> \\", "Get trade summary or full contract");
|
stream.format(rowFormat, gettrade.name(), "--trade-id=<trade-id> \\", "Get trade summary or full contract");
|
||||||
stream.format(rowFormat, "", "[--show-contract=<true|false>]", "");
|
stream.format(rowFormat, "", "[--show-contract=<true|false>]", "");
|
||||||
stream.println();
|
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.format(rowFormat, confirmpaymentstarted.name(), "--trade-id=<trade-id>", "Confirm payment started");
|
||||||
stream.println();
|
stream.println();
|
||||||
stream.format(rowFormat, confirmpaymentreceived.name(), "--trade-id=<trade-id>", "Confirm payment received");
|
stream.format(rowFormat, confirmpaymentreceived.name(), "--trade-id=<trade-id>", "Confirm payment received");
|
||||||
stream.println();
|
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.println();
|
||||||
stream.format(rowFormat, withdrawfunds.name(), "--trade-id=<trade-id> --address=<btc-address> \\",
|
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.format(rowFormat, "", "[--memo=<\"memo\">]", "");
|
||||||
stream.println();
|
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.format(rowFormat, getpaymentmethods.name(), "", "Get list of supported payment account method ids");
|
||||||
stream.println();
|
stream.println();
|
||||||
stream.format(rowFormat, getpaymentacctform.name(), "--payment-method-id=<payment-method-id>", "Get a new payment account form");
|
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.format(rowFormat, createpaymentacct.name(), "--payment-account-form=<path>", "Create a new payment account");
|
||||||
stream.println();
|
stream.println();
|
||||||
stream.format(rowFormat, createcryptopaymentacct.name(), "--account-name=<name> \\", "Create a new cryptocurrency payment account");
|
stream.format(rowFormat, createcryptopaymentacct.name(), "--account-name=<name> \\", "Create a new cryptocurrency payment account");
|
||||||
stream.format(rowFormat, "", "--currency-code=<btc> \\", "");
|
stream.format(rowFormat, "", "--currency-code=<bsq> \\", "");
|
||||||
stream.format(rowFormat, "", "--address=<address>", "");
|
stream.format(rowFormat, "", "--address=<bsq-address>", "");
|
||||||
stream.format(rowFormat, "", "--trade-instant=<true|false>", "");
|
stream.format(rowFormat, "", "--trade-instant=<true|false>", "");
|
||||||
stream.println();
|
stream.println();
|
||||||
stream.format(rowFormat, getpaymentaccts.name(), "", "Get user payment accounts");
|
stream.format(rowFormat, getpaymentaccts.name(), "", "Get user payment accounts");
|
||||||
|
|
|
@ -20,14 +20,15 @@ package bisq.cli;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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());
|
return getSupportedCryptoCurrencies().contains(currencyCode.toUpperCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<String> getSupportedCryptoCurrencies() {
|
public static List<String> getSupportedCryptoCurrencies() {
|
||||||
final List<String> result = new ArrayList<>();
|
final List<String> result = new ArrayList<>();
|
||||||
|
result.add("BCH");
|
||||||
result.sort(String::compareTo);
|
result.sort(String::compareTo);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,10 +22,10 @@ import bisq.proto.grpc.TxFeeRateInfo;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
|
import java.text.DecimalFormatSymbols;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.BigInteger;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
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.HALF_UP;
|
||||||
import static java.math.RoundingMode.UNNECESSARY;
|
import static java.math.RoundingMode.UNNECESSARY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility for formatting amounts, volumes and fees; there is no i18n support in the CLI.
|
||||||
import monero.common.MoneroUtils;
|
*/
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public class CurrencyFormat {
|
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 BigDecimal SATOSHI_DIVISOR = new BigDecimal(100_000_000);
|
||||||
static final DecimalFormat BTC_FORMAT = new DecimalFormat("###,##0.00000000");
|
static final DecimalFormat SATOSHI_FORMAT = new DecimalFormat("###,##0.00000000", DECIMAL_FORMAT_SYMBOLS);
|
||||||
static final DecimalFormat BTC_TX_FEE_FORMAT = new DecimalFormat("###,###,##0");
|
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")
|
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
|
||||||
public static String formatSatoshis(long sats) {
|
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) {
|
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
|
||||||
return "" + MoneroUtils.atomicUnitsToXmr(amount);
|
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) {
|
public static String formatTxFeeRateInfo(TxFeeRateInfo txFeeRateInfo) {
|
||||||
if (txFeeRateInfo.getUseCustomTxFeeRate())
|
if (txFeeRateInfo.getUseCustomTxFeeRate())
|
||||||
|
@ -72,56 +88,30 @@ public class CurrencyFormat {
|
||||||
formatFeeSatoshis(txFeeRateInfo.getMinFeeServiceRate()));
|
formatFeeSatoshis(txFeeRateInfo.getMinFeeServiceRate()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatAmountRange(long minAmount, long amount) {
|
public static String formatInternalFiatPrice(BigDecimal price) {
|
||||||
return minAmount != amount
|
INTERNAL_FIAT_DECIMAL_FORMAT.setMinimumFractionDigits(4);
|
||||||
? formatSatoshis(minAmount) + " - " + formatSatoshis(amount)
|
INTERNAL_FIAT_DECIMAL_FORMAT.setMaximumFractionDigits(4);
|
||||||
: formatSatoshis(amount);
|
return INTERNAL_FIAT_DECIMAL_FORMAT.format(price);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatVolumeRange(long minVolume, long volume) {
|
public static String formatInternalFiatPrice(double price) {
|
||||||
return minVolume != volume
|
US_LOCALE_NUMBER_FORMAT.setMinimumFractionDigits(4);
|
||||||
? formatOfferVolume(minVolume) + " - " + formatOfferVolume(volume)
|
US_LOCALE_NUMBER_FORMAT.setMaximumFractionDigits(4);
|
||||||
: formatOfferVolume(volume);
|
return US_LOCALE_NUMBER_FORMAT.format(price);
|
||||||
}
|
|
||||||
|
|
||||||
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 formatPrice(long price) {
|
public static String formatPrice(long price) {
|
||||||
NUMBER_FORMAT.setMinimumFractionDigits(4);
|
US_LOCALE_NUMBER_FORMAT.setMinimumFractionDigits(4);
|
||||||
NUMBER_FORMAT.setMaximumFractionDigits(4);
|
US_LOCALE_NUMBER_FORMAT.setMaximumFractionDigits(4);
|
||||||
NUMBER_FORMAT.setRoundingMode(UNNECESSARY);
|
US_LOCALE_NUMBER_FORMAT.setRoundingMode(UNNECESSARY);
|
||||||
return NUMBER_FORMAT.format((double) price / 10_000);
|
return US_LOCALE_NUMBER_FORMAT.format((double) price / 10_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String formatCryptoCurrencyPrice(long price) {
|
public static String formatFiatVolume(long volume) {
|
||||||
NUMBER_FORMAT.setMinimumFractionDigits(8);
|
US_LOCALE_NUMBER_FORMAT.setMinimumFractionDigits(0);
|
||||||
NUMBER_FORMAT.setMaximumFractionDigits(8);
|
US_LOCALE_NUMBER_FORMAT.setMaximumFractionDigits(0);
|
||||||
NUMBER_FORMAT.setRoundingMode(UNNECESSARY);
|
US_LOCALE_NUMBER_FORMAT.setRoundingMode(HALF_UP);
|
||||||
return NUMBER_FORMAT.format((double) price / SATOSHI_DIVISOR.doubleValue());
|
return US_LOCALE_NUMBER_FORMAT.format((double) volume / 10_000);
|
||||||
}
|
|
||||||
|
|
||||||
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 long toSatoshis(String btc) {
|
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) {
|
public static String formatFeeSatoshis(long sats) {
|
||||||
return BTC_TX_FEE_FORMAT.format(BigDecimal.valueOf(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 bisq.cli.ColumnHeaderConstants.COL_HEADER_DIRECTION;
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
import static protobuf.OfferDirection.BUY;
|
||||||
import static protobuf.OfferPayload.Direction.SELL;
|
import static protobuf.OfferDirection.SELL;
|
||||||
|
|
||||||
class DirectionFormat {
|
class DirectionFormat {
|
||||||
|
|
||||||
|
|
|
@ -20,62 +20,28 @@ package bisq.cli;
|
||||||
import bisq.proto.grpc.AddressBalanceInfo;
|
import bisq.proto.grpc.AddressBalanceInfo;
|
||||||
import bisq.proto.grpc.BalancesInfo;
|
import bisq.proto.grpc.BalancesInfo;
|
||||||
import bisq.proto.grpc.BtcBalanceInfo;
|
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.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.GetTradesRequest;
|
||||||
import bisq.proto.grpc.GetTransactionRequest;
|
|
||||||
import bisq.proto.grpc.GetTxFeeRateRequest;
|
|
||||||
import bisq.proto.grpc.GetVersionRequest;
|
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.OfferInfo;
|
||||||
import bisq.proto.grpc.RegisterDisputeAgentRequest;
|
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.StopRequest;
|
||||||
import bisq.proto.grpc.TakeOfferReply;
|
|
||||||
import bisq.proto.grpc.TakeOfferRequest;
|
|
||||||
import bisq.proto.grpc.TradeInfo;
|
import bisq.proto.grpc.TradeInfo;
|
||||||
import bisq.proto.grpc.TxFeeRateInfo;
|
import bisq.proto.grpc.TxFeeRateInfo;
|
||||||
import bisq.proto.grpc.TxInfo;
|
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.PaymentAccount;
|
||||||
import protobuf.PaymentMethod;
|
import protobuf.PaymentMethod;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import static bisq.cli.CryptoCurrencyUtil.isSupportedCryptoCurrency;
|
|
||||||
import static java.util.Comparator.comparing;
|
import bisq.cli.request.OffersServiceRequest;
|
||||||
import static java.util.stream.Collectors.toList;
|
import bisq.cli.request.PaymentAccountsServiceRequest;
|
||||||
import static protobuf.OfferPayload.Direction.BUY;
|
import bisq.cli.request.TradesServiceRequest;
|
||||||
import static protobuf.OfferPayload.Direction.SELL;
|
import bisq.cli.request.WalletsServiceRequest;
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
|
@ -83,9 +49,19 @@ import static protobuf.OfferPayload.Direction.SELL;
|
||||||
public final class GrpcClient {
|
public final class GrpcClient {
|
||||||
|
|
||||||
private final GrpcStubs grpcStubs;
|
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.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() {
|
public String getVersion() {
|
||||||
|
@ -94,86 +70,51 @@ public final class GrpcClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
public BalancesInfo getBalances() {
|
public BalancesInfo getBalances() {
|
||||||
return getBalances("");
|
return walletsServiceRequest.getBalances();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BtcBalanceInfo getBtcBalances() {
|
public BtcBalanceInfo getBtcBalances() {
|
||||||
return getBalances("BTC").getBtc();
|
return walletsServiceRequest.getBtcBalances();
|
||||||
}
|
|
||||||
|
|
||||||
public XmrBalanceInfo getXmrBalances() {
|
|
||||||
return getBalances("XMR").getXmr();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BalancesInfo getBalances(String currencyCode) {
|
public BalancesInfo getBalances(String currencyCode) {
|
||||||
var request = GetBalancesRequest.newBuilder()
|
return walletsServiceRequest.getBalances(currencyCode);
|
||||||
.setCurrencyCode(currencyCode)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.walletsService.getBalances(request).getBalances();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AddressBalanceInfo getAddressBalance(String address) {
|
public AddressBalanceInfo getAddressBalance(String address) {
|
||||||
var request = GetAddressBalanceRequest.newBuilder()
|
return walletsServiceRequest.getAddressBalance(address);
|
||||||
.setAddress(address).build();
|
|
||||||
return grpcStubs.walletsService.getAddressBalance(request).getAddressBalanceInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getBtcPrice(String currencyCode) {
|
public double getBtcPrice(String currencyCode) {
|
||||||
var request = MarketPriceRequest.newBuilder()
|
return walletsServiceRequest.getBtcPrice(currencyCode);
|
||||||
.setCurrencyCode(currencyCode)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.priceService.getMarketPrice(request).getPrice();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<AddressBalanceInfo> getFundingAddresses() {
|
public List<AddressBalanceInfo> getFundingAddresses() {
|
||||||
var request = GetFundingAddressesRequest.newBuilder().build();
|
return walletsServiceRequest.getFundingAddresses();
|
||||||
return grpcStubs.walletsService.getFundingAddresses(request).getAddressBalanceInfoList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUnusedBtcAddress() {
|
public String getUnusedBtcAddress() {
|
||||||
var request = GetFundingAddressesRequest.newBuilder().build();
|
return walletsServiceRequest.getUnusedBtcAddress();
|
||||||
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) {
|
public TxInfo sendBtc(String address, String amount, String txFeeRate, String memo) {
|
||||||
var request = SendBtcRequest.newBuilder()
|
return walletsServiceRequest.sendBtc(address, amount, txFeeRate, memo);
|
||||||
.setAddress(address)
|
|
||||||
.setAmount(amount)
|
|
||||||
.setTxFeeRate(txFeeRate)
|
|
||||||
.setMemo(memo)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.walletsService.sendBtc(request).getTxInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TxFeeRateInfo getTxFeeRate() {
|
public TxFeeRateInfo getTxFeeRate() {
|
||||||
var request = GetTxFeeRateRequest.newBuilder().build();
|
return walletsServiceRequest.getTxFeeRate();
|
||||||
return grpcStubs.walletsService.getTxFeeRate(request).getTxFeeRateInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TxFeeRateInfo setTxFeeRate(long txFeeRate) {
|
public TxFeeRateInfo setTxFeeRate(long txFeeRate) {
|
||||||
var request = SetTxFeeRatePreferenceRequest.newBuilder()
|
return walletsServiceRequest.setTxFeeRate(txFeeRate);
|
||||||
.setTxFeeRatePreference(txFeeRate)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.walletsService.setTxFeeRatePreference(request).getTxFeeRateInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TxFeeRateInfo unsetTxFeeRate() {
|
public TxFeeRateInfo unsetTxFeeRate() {
|
||||||
var request = UnsetTxFeeRatePreferenceRequest.newBuilder().build();
|
return walletsServiceRequest.unsetTxFeeRate();
|
||||||
return grpcStubs.walletsService.unsetTxFeeRatePreference(request).getTxFeeRateInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TxInfo getTransaction(String txId) {
|
public TxInfo getTransaction(String txId) {
|
||||||
var request = GetTransactionRequest.newBuilder()
|
return walletsServiceRequest.getTransaction(txId);
|
||||||
.setTxId(txId)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.walletsService.getTransaction(request).getTxInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public OfferInfo createFixedPricedOffer(String direction,
|
public OfferInfo createFixedPricedOffer(String direction,
|
||||||
|
@ -181,35 +122,38 @@ public final class GrpcClient {
|
||||||
long amount,
|
long amount,
|
||||||
long minAmount,
|
long minAmount,
|
||||||
String fixedPrice,
|
String fixedPrice,
|
||||||
double securityDeposit,
|
double securityDepositPct,
|
||||||
String paymentAcctId) {
|
String paymentAcctId) {
|
||||||
return createOffer(direction,
|
return offersServiceRequest.createOffer(direction,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
amount,
|
amount,
|
||||||
minAmount,
|
minAmount,
|
||||||
false,
|
false,
|
||||||
fixedPrice,
|
fixedPrice,
|
||||||
0.00,
|
0.00,
|
||||||
securityDeposit,
|
securityDepositPct,
|
||||||
paymentAcctId);
|
paymentAcctId,
|
||||||
|
"0" /* no trigger price */);
|
||||||
}
|
}
|
||||||
|
|
||||||
public OfferInfo createMarketBasedPricedOffer(String direction,
|
public OfferInfo createMarketBasedPricedOffer(String direction,
|
||||||
String currencyCode,
|
String currencyCode,
|
||||||
long amount,
|
long amount,
|
||||||
long minAmount,
|
long minAmount,
|
||||||
double marketPriceMargin,
|
double marketPriceMarginPct,
|
||||||
double securityDeposit,
|
double securityDepositPct,
|
||||||
String paymentAcctId) {
|
String paymentAcctId,
|
||||||
return createOffer(direction,
|
String triggerPrice) {
|
||||||
|
return offersServiceRequest.createOffer(direction,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
amount,
|
amount,
|
||||||
minAmount,
|
minAmount,
|
||||||
true,
|
true,
|
||||||
"0",
|
"0",
|
||||||
marketPriceMargin,
|
marketPriceMarginPct,
|
||||||
securityDeposit,
|
securityDepositPct,
|
||||||
paymentAcctId);
|
paymentAcctId,
|
||||||
|
triggerPrice);
|
||||||
}
|
}
|
||||||
|
|
||||||
public OfferInfo createOffer(String direction,
|
public OfferInfo createOffer(String direction,
|
||||||
|
@ -218,244 +162,140 @@ public final class GrpcClient {
|
||||||
long minAmount,
|
long minAmount,
|
||||||
boolean useMarketBasedPrice,
|
boolean useMarketBasedPrice,
|
||||||
String fixedPrice,
|
String fixedPrice,
|
||||||
double marketPriceMargin,
|
double marketPriceMarginPct,
|
||||||
double securityDeposit,
|
double securityDepositPct,
|
||||||
String paymentAcctId) {
|
String paymentAcctId,
|
||||||
var request = CreateOfferRequest.newBuilder()
|
String triggerPrice) {
|
||||||
.setDirection(direction)
|
return offersServiceRequest.createOffer(direction,
|
||||||
.setCurrencyCode(currencyCode)
|
currencyCode,
|
||||||
.setAmount(amount)
|
amount,
|
||||||
.setMinAmount(minAmount)
|
minAmount,
|
||||||
.setUseMarketBasedPrice(useMarketBasedPrice)
|
useMarketBasedPrice,
|
||||||
.setPrice(fixedPrice)
|
fixedPrice,
|
||||||
.setMarketPriceMargin(marketPriceMargin)
|
marketPriceMarginPct,
|
||||||
.setBuyerSecurityDeposit(securityDeposit)
|
securityDepositPct,
|
||||||
.setPaymentAccountId(paymentAcctId)
|
paymentAcctId,
|
||||||
.build();
|
triggerPrice);
|
||||||
return grpcStubs.offersService.createOffer(request).getOffer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancelOffer(String offerId) {
|
public void cancelOffer(String offerId) {
|
||||||
var request = CancelOfferRequest.newBuilder()
|
offersServiceRequest.cancelOffer(offerId);
|
||||||
.setId(offerId)
|
|
||||||
.build();
|
|
||||||
grpcStubs.offersService.cancelOffer(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public OfferInfo getOffer(String offerId) {
|
public OfferInfo getOffer(String offerId) {
|
||||||
var request = GetOfferRequest.newBuilder()
|
return offersServiceRequest.getOffer(offerId);
|
||||||
.setId(offerId)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.offersService.getOffer(request).getOffer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated // Since 5-Dec-2021.
|
||||||
|
// Endpoint to be removed from future version. Use getOffer service method instead.
|
||||||
public OfferInfo getMyOffer(String offerId) {
|
public OfferInfo getMyOffer(String offerId) {
|
||||||
var request = GetMyOfferRequest.newBuilder()
|
return offersServiceRequest.getMyOffer(offerId);
|
||||||
.setId(offerId)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.offersService.getMyOffer(request).getOffer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getOffers(String direction, String currencyCode) {
|
public List<OfferInfo> getOffers(String direction, String currencyCode) {
|
||||||
if (isSupportedCryptoCurrency(currencyCode)) {
|
return offersServiceRequest.getOffers(direction, 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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getOffersSortedByDate(String currencyCode) {
|
public List<OfferInfo> getOffersSortedByDate(String currencyCode) {
|
||||||
ArrayList<OfferInfo> offers = new ArrayList<>();
|
return offersServiceRequest.getOffersSortedByDate(currencyCode);
|
||||||
offers.addAll(getOffers(BUY.name(), currencyCode));
|
|
||||||
offers.addAll(getOffers(SELL.name(), currencyCode));
|
|
||||||
return sortOffersByDate(offers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getOffersSortedByDate(String direction, String currencyCode) {
|
public List<OfferInfo> getOffersSortedByDate(String direction, String currencyCode) {
|
||||||
var offers = getOffers(direction, currencyCode);
|
return offersServiceRequest.getOffersSortedByDate(direction, currencyCode);
|
||||||
return offers.isEmpty() ? offers : sortOffersByDate(offers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
|
public List<OfferInfo> getMyOffers(String direction, String currencyCode) {
|
||||||
if (isSupportedCryptoCurrency(currencyCode)) {
|
return offersServiceRequest.getMyOffers(direction, 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OfferInfo> getMyOffersSortedByDate(String currencyCode) {
|
public List<OfferInfo> getMyOffersSortedByDate(String currencyCode) {
|
||||||
ArrayList<OfferInfo> offers = new ArrayList<>();
|
return offersServiceRequest.getMyOffersSortedByDate(currencyCode);
|
||||||
offers.addAll(getMyOffers(BUY.name(), currencyCode));
|
|
||||||
offers.addAll(getMyOffers(SELL.name(), currencyCode));
|
|
||||||
return sortOffersByDate(offers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public OfferInfo getMostRecentOffer(String direction, String currencyCode) {
|
public List<OfferInfo> getMyOffersSortedByDate(String direction, String currencyCode) {
|
||||||
List<OfferInfo> offers = getOffersSortedByDate(direction, currencyCode);
|
return offersServiceRequest.getMyOffersSortedByDate(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 TradeInfo takeOffer(String offerId, String paymentAccountId) {
|
public TradeInfo takeOffer(String offerId, String paymentAccountId) {
|
||||||
var reply = getTakeOfferReply(offerId, paymentAccountId);
|
return tradesServiceRequest.takeOffer(offerId, paymentAccountId);
|
||||||
if (reply.hasTrade())
|
|
||||||
return reply.getTrade();
|
|
||||||
else
|
|
||||||
throw new IllegalStateException(reply.getFailureReason().getDescription());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TradeInfo getTrade(String tradeId) {
|
public TradeInfo getTrade(String tradeId) {
|
||||||
var request = GetTradeRequest.newBuilder()
|
return tradesServiceRequest.getTrade(tradeId);
|
||||||
.setTradeId(tradeId)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.tradesService.getTrade(request).getTrade();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<TradeInfo> getTrades() {
|
public List<TradeInfo> getOpenTrades() {
|
||||||
var request = GetTradesRequest.newBuilder().build();
|
return tradesServiceRequest.getOpenTrades();
|
||||||
return grpcStubs.tradesService.getTrades(request).getTradesList();
|
}
|
||||||
|
|
||||||
|
public List<TradeInfo> getTradeHistory(GetTradesRequest.Category category) {
|
||||||
|
return tradesServiceRequest.getTradeHistory(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void confirmPaymentStarted(String tradeId) {
|
public void confirmPaymentStarted(String tradeId) {
|
||||||
var request = ConfirmPaymentStartedRequest.newBuilder()
|
tradesServiceRequest.confirmPaymentStarted(tradeId);
|
||||||
.setTradeId(tradeId)
|
|
||||||
.build();
|
|
||||||
grpcStubs.tradesService.confirmPaymentStarted(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void confirmPaymentReceived(String tradeId) {
|
public void confirmPaymentReceived(String tradeId) {
|
||||||
var request = ConfirmPaymentReceivedRequest.newBuilder()
|
tradesServiceRequest.confirmPaymentReceived(tradeId);
|
||||||
.setTradeId(tradeId)
|
|
||||||
.build();
|
|
||||||
grpcStubs.tradesService.confirmPaymentReceived(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void keepFunds(String tradeId) {
|
|
||||||
var request = KeepFundsRequest.newBuilder()
|
|
||||||
.setTradeId(tradeId)
|
|
||||||
.build();
|
|
||||||
grpcStubs.tradesService.keepFunds(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void withdrawFunds(String tradeId, String address, String memo) {
|
public void withdrawFunds(String tradeId, String address, String memo) {
|
||||||
var request = WithdrawFundsRequest.newBuilder()
|
tradesServiceRequest.withdrawFunds(tradeId, address, memo);
|
||||||
.setTradeId(tradeId)
|
|
||||||
.setAddress(address)
|
|
||||||
.setMemo(memo)
|
|
||||||
.build();
|
|
||||||
grpcStubs.tradesService.withdrawFunds(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PaymentMethod> getPaymentMethods() {
|
public List<PaymentMethod> getPaymentMethods() {
|
||||||
var request = GetPaymentMethodsRequest.newBuilder().build();
|
return paymentAccountsServiceRequest.getPaymentMethods();
|
||||||
return grpcStubs.paymentAccountsService.getPaymentMethods(request).getPaymentMethodsList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPaymentAcctFormAsJson(String paymentMethodId) {
|
public String getPaymentAcctFormAsJson(String paymentMethodId) {
|
||||||
var request = GetPaymentAccountFormRequest.newBuilder()
|
return paymentAccountsServiceRequest.getPaymentAcctFormAsJson(paymentMethodId);
|
||||||
.setPaymentMethodId(paymentMethodId)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.paymentAccountsService.getPaymentAccountForm(request).getPaymentAccountFormJson();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PaymentAccount createPaymentAccount(String json) {
|
public PaymentAccount createPaymentAccount(String json) {
|
||||||
var request = CreatePaymentAccountRequest.newBuilder()
|
return paymentAccountsServiceRequest.createPaymentAccount(json);
|
||||||
.setPaymentAccountForm(json)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.paymentAccountsService.createPaymentAccount(request).getPaymentAccount();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PaymentAccount> getPaymentAccounts() {
|
public List<PaymentAccount> getPaymentAccounts() {
|
||||||
var request = GetPaymentAccountsRequest.newBuilder().build();
|
return paymentAccountsServiceRequest.getPaymentAccounts();
|
||||||
return grpcStubs.paymentAccountsService.getPaymentAccounts(request).getPaymentAccountsList();
|
}
|
||||||
|
|
||||||
|
public PaymentAccount getPaymentAccount(String accountName) {
|
||||||
|
return paymentAccountsServiceRequest.getPaymentAccount(accountName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
|
public PaymentAccount createCryptoCurrencyPaymentAccount(String accountName,
|
||||||
String currencyCode,
|
String currencyCode,
|
||||||
String address,
|
String address,
|
||||||
boolean tradeInstant) {
|
boolean tradeInstant) {
|
||||||
var request = CreateCryptoCurrencyPaymentAccountRequest.newBuilder()
|
return paymentAccountsServiceRequest.createCryptoCurrencyPaymentAccount(accountName,
|
||||||
.setAccountName(accountName)
|
currencyCode,
|
||||||
.setCurrencyCode(currencyCode)
|
address,
|
||||||
.setAddress(address)
|
tradeInstant);
|
||||||
.setTradeInstant(tradeInstant)
|
|
||||||
.build();
|
|
||||||
return grpcStubs.paymentAccountsService.createCryptoCurrencyPaymentAccount(request).getPaymentAccount();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PaymentMethod> getCryptoPaymentMethods() {
|
public List<PaymentMethod> getCryptoPaymentMethods() {
|
||||||
var request = GetCryptoCurrencyPaymentMethodsRequest.newBuilder().build();
|
return paymentAccountsServiceRequest.getCryptoPaymentMethods();
|
||||||
return grpcStubs.paymentAccountsService.getCryptoCurrencyPaymentMethods(request).getPaymentMethodsList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void lockWallet() {
|
public void lockWallet() {
|
||||||
var request = LockWalletRequest.newBuilder().build();
|
walletsServiceRequest.lockWallet();
|
||||||
grpcStubs.walletsService.lockWallet(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unlockWallet(String walletPassword, long timeout) {
|
public void unlockWallet(String walletPassword, long timeout) {
|
||||||
var request = UnlockWalletRequest.newBuilder()
|
walletsServiceRequest.unlockWallet(walletPassword, timeout);
|
||||||
.setPassword(walletPassword)
|
|
||||||
.setTimeout(timeout).build();
|
|
||||||
grpcStubs.walletsService.unlockWallet(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeWalletPassword(String walletPassword) {
|
public void removeWalletPassword(String walletPassword) {
|
||||||
var request = RemoveWalletPasswordRequest.newBuilder()
|
walletsServiceRequest.removeWalletPassword(walletPassword);
|
||||||
.setPassword(walletPassword).build();
|
|
||||||
grpcStubs.walletsService.removeWalletPassword(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setWalletPassword(String walletPassword) {
|
public void setWalletPassword(String walletPassword) {
|
||||||
var request = SetWalletPasswordRequest.newBuilder()
|
walletsServiceRequest.setWalletPassword(walletPassword);
|
||||||
.setPassword(walletPassword).build();
|
|
||||||
grpcStubs.walletsService.setWalletPassword(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setWalletPassword(String oldWalletPassword, String newWalletPassword) {
|
public void setWalletPassword(String oldWalletPassword, String newWalletPassword) {
|
||||||
var request = SetWalletPasswordRequest.newBuilder()
|
walletsServiceRequest.setWalletPassword(oldWalletPassword, newWalletPassword);
|
||||||
.setPassword(oldWalletPassword)
|
|
||||||
.setNewPassword(newWalletPassword).build();
|
|
||||||
grpcStubs.walletsService.setWalletPassword(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerDisputeAgent(String disputeAgentType, String registrationKey) {
|
public void registerDisputeAgent(String disputeAgentType, String registrationKey) {
|
||||||
|
|
|
@ -22,16 +22,19 @@ package bisq.cli;
|
||||||
*/
|
*/
|
||||||
public enum Method {
|
public enum Method {
|
||||||
canceloffer,
|
canceloffer,
|
||||||
|
closetrade,
|
||||||
confirmpaymentreceived,
|
confirmpaymentreceived,
|
||||||
confirmpaymentstarted,
|
confirmpaymentstarted,
|
||||||
createoffer,
|
createoffer,
|
||||||
|
editoffer,
|
||||||
createpaymentacct,
|
createpaymentacct,
|
||||||
createcryptopaymentacct,
|
createcryptopaymentacct,
|
||||||
getaddressbalance,
|
getaddressbalance,
|
||||||
getbalance,
|
getbalance,
|
||||||
getbtcprice,
|
getbtcprice,
|
||||||
getfundingaddresses,
|
getfundingaddresses,
|
||||||
getmyoffer,
|
@Deprecated // Since 27-Dec-2021.
|
||||||
|
getmyoffer, // Endpoint to be removed from future version. Use getoffer instead.
|
||||||
getmyoffers,
|
getmyoffers,
|
||||||
getoffer,
|
getoffer,
|
||||||
getoffers,
|
getoffers,
|
||||||
|
@ -39,10 +42,13 @@ public enum Method {
|
||||||
getpaymentaccts,
|
getpaymentaccts,
|
||||||
getpaymentmethods,
|
getpaymentmethods,
|
||||||
gettrade,
|
gettrade,
|
||||||
|
gettrades,
|
||||||
|
failtrade,
|
||||||
|
unfailtrade,
|
||||||
gettransaction,
|
gettransaction,
|
||||||
gettxfeerate,
|
gettxfeerate,
|
||||||
|
getunusedbsqaddress,
|
||||||
getversion,
|
getversion,
|
||||||
keepfunds,
|
|
||||||
lockwallet,
|
lockwallet,
|
||||||
registerdisputeagent,
|
registerdisputeagent,
|
||||||
removewalletpassword,
|
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.List;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
import static bisq.cli.opts.OptLabel.OPT_HELP;
|
import static bisq.cli.opts.OptLabel.OPT_HELP;
|
||||||
|
import static java.lang.String.format;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
abstract class AbstractMethodOptionParser implements MethodOpts {
|
abstract class AbstractMethodOptionParser implements MethodOpts {
|
||||||
|
|
||||||
// The full command line args passed to CliMain.main(String[] args).
|
// 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();
|
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();
|
protected final OptionSpec<Void> helpOpt = parser.accepts(OPT_HELP, "Print method help").forHelp();
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
@ -52,7 +55,6 @@ abstract class AbstractMethodOptionParser implements MethodOpts {
|
||||||
public AbstractMethodOptionParser parse() {
|
public AbstractMethodOptionParser parse() {
|
||||||
try {
|
try {
|
||||||
options = parser.parse(new ArgumentList(args).getMethodArguments());
|
options = parser.parse(new ArgumentList(args).getMethodArguments());
|
||||||
//noinspection unchecked
|
|
||||||
nonOptionArguments = (List<String>) options.nonOptionArguments();
|
nonOptionArguments = (List<String>) options.nonOptionArguments();
|
||||||
return this;
|
return this;
|
||||||
} catch (OptionException ex) {
|
} catch (OptionException ex) {
|
||||||
|
@ -64,6 +66,17 @@ abstract class AbstractMethodOptionParser implements MethodOpts {
|
||||||
return options.has(helpOpt);
|
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) -> {
|
private final Function<OptionException, String> cliExceptionMessageStyle = (ex) -> {
|
||||||
if (ex.getMessage() == null)
|
if (ex.getMessage() == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -18,14 +18,7 @@
|
||||||
package bisq.cli.opts;
|
package bisq.cli.opts;
|
||||||
|
|
||||||
|
|
||||||
import joptsimple.OptionSpec;
|
public class CancelOfferOptionParser extends OfferIdOptionParser implements MethodOpts {
|
||||||
|
|
||||||
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 CancelOfferOptionParser(String[] args) {
|
public CancelOfferOptionParser(String[] args) {
|
||||||
super(args);
|
super(args);
|
||||||
|
@ -34,12 +27,7 @@ public class CancelOfferOptionParser extends AbstractMethodOptionParser implemen
|
||||||
public CancelOfferOptionParser parse() {
|
public CancelOfferOptionParser parse() {
|
||||||
super.parse();
|
super.parse();
|
||||||
|
|
||||||
// Short circuit opt validation if user just wants help.
|
// Super class will short-circuit parsing if help option is present.
|
||||||
if (options.has(helpOpt))
|
|
||||||
return this;
|
|
||||||
|
|
||||||
if (!options.has(offerIdOpt) || options.valueOf(offerIdOpt).isEmpty())
|
|
||||||
throw new IllegalArgumentException("no offer id specified");
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,20 +20,22 @@ package bisq.cli.opts;
|
||||||
|
|
||||||
import joptsimple.OptionSpec;
|
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_ACCOUNT_NAME;
|
||||||
import static bisq.cli.opts.OptLabel.OPT_ADDRESS;
|
import static bisq.cli.opts.OptLabel.OPT_ADDRESS;
|
||||||
import static bisq.cli.opts.OptLabel.OPT_CURRENCY_CODE;
|
import static bisq.cli.opts.OptLabel.OPT_CURRENCY_CODE;
|
||||||
import static bisq.cli.opts.OptLabel.OPT_TRADE_INSTANT;
|
import static bisq.cli.opts.OptLabel.OPT_TRADE_INSTANT;
|
||||||
|
import static java.lang.String.format;
|
||||||
|
|
||||||
public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
public class CreateCryptoCurrencyPaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
||||||
|
|
||||||
final OptionSpec<String> accountNameOpt = parser.accepts(OPT_ACCOUNT_NAME, "crypto currency account name")
|
final OptionSpec<String> accountNameOpt = parser.accepts(OPT_ACCOUNT_NAME, "crypto currency account name")
|
||||||
.withRequiredArg();
|
.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();
|
.withRequiredArg();
|
||||||
|
|
||||||
final OptionSpec<String> addressOpt = parser.accepts(OPT_ADDRESS, "address")
|
final OptionSpec<String> addressOpt = parser.accepts(OPT_ADDRESS, "altcoin address")
|
||||||
.withRequiredArg();
|
.withRequiredArg();
|
||||||
|
|
||||||
final OptionSpec<Boolean> tradeInstantOpt = parser.accepts(OPT_TRADE_INSTANT, "create trade instant account")
|
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())
|
if (!options.has(accountNameOpt) || options.valueOf(accountNameOpt).isEmpty())
|
||||||
throw new IllegalArgumentException("no payment account name specified");
|
throw new IllegalArgumentException("no payment account name specified");
|
||||||
|
|
||||||
if (!options.has(currencyCodeOpt) || options.valueOf(currencyCodeOpt).isEmpty())
|
if (!options.has(currencyCodeOpt) || options.valueOf(currencyCodeOpt).isEmpty())
|
||||||
throw new IllegalArgumentException("no currency code specified");
|
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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,22 +20,20 @@ package bisq.cli.opts;
|
||||||
|
|
||||||
import joptsimple.OptionSpec;
|
import joptsimple.OptionSpec;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
import static bisq.cli.opts.OptLabel.*;
|
import static bisq.cli.opts.OptLabel.*;
|
||||||
import static joptsimple.internal.Strings.EMPTY;
|
import static joptsimple.internal.Strings.EMPTY;
|
||||||
|
|
||||||
public class CreateOfferOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
public class CreateOfferOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
||||||
|
|
||||||
final OptionSpec<String> paymentAccountIdOpt = parser.accepts(OPT_PAYMENT_ACCOUNT,
|
final OptionSpec<String> paymentAccountIdOpt = parser.accepts(OPT_PAYMENT_ACCOUNT_ID,
|
||||||
"id of payment account used for offer")
|
"id of payment account used for offer")
|
||||||
.withRequiredArg()
|
.withRequiredArg()
|
||||||
.defaultsTo(EMPTY);
|
.defaultsTo(EMPTY);
|
||||||
|
|
||||||
final OptionSpec<String> directionOpt = parser.accepts(OPT_DIRECTION, "offer direction (buy|sell)")
|
final OptionSpec<String> directionOpt = parser.accepts(OPT_DIRECTION, "offer direction (buy|sell)")
|
||||||
.withRequiredArg();
|
.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();
|
.withRequiredArg();
|
||||||
|
|
||||||
final OptionSpec<String> amountOpt = parser.accepts(OPT_AMOUNT, "amount of btc to buy or sell")
|
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")
|
final OptionSpec<String> minAmountOpt = parser.accepts(OPT_MIN_AMOUNT, "minimum amount of btc to buy or sell")
|
||||||
.withOptionalArg();
|
.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()
|
.withOptionalArg()
|
||||||
.defaultsTo("0.00");
|
.defaultsTo("0.00");
|
||||||
|
|
||||||
|
@ -52,13 +50,14 @@ public class CreateOfferOptionParser extends AbstractMethodOptionParser implemen
|
||||||
.withOptionalArg()
|
.withOptionalArg()
|
||||||
.defaultsTo("0");
|
.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();
|
.withRequiredArg();
|
||||||
|
|
||||||
public CreateOfferOptionParser(String[] args) {
|
public CreateOfferOptionParser(String[] args) {
|
||||||
super(args);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public CreateOfferOptionParser parse() {
|
public CreateOfferOptionParser parse() {
|
||||||
super.parse();
|
super.parse();
|
||||||
|
|
||||||
|
@ -66,9 +65,6 @@ public class CreateOfferOptionParser extends AbstractMethodOptionParser implemen
|
||||||
if (options.has(helpOpt))
|
if (options.has(helpOpt))
|
||||||
return this;
|
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())
|
if (!options.has(directionOpt) || options.valueOf(directionOpt).isEmpty())
|
||||||
throw new IllegalArgumentException("no direction (buy|sell) specified");
|
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())
|
if (!options.has(amountOpt) || options.valueOf(amountOpt).isEmpty())
|
||||||
throw new IllegalArgumentException("no btc amount specified");
|
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");
|
throw new IllegalArgumentException("no market price margin or fixed price specified");
|
||||||
|
|
||||||
if (options.has(mktPriceMarginOpt) && options.valueOf(mktPriceMarginOpt).isEmpty())
|
if (options.has(mktPriceMarginPctOpt)) {
|
||||||
throw new IllegalArgumentException("no market price margin specified");
|
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())
|
if (options.has(fixedPriceOpt) && options.valueOf(fixedPriceOpt).isEmpty())
|
||||||
throw new IllegalArgumentException("no fixed price specified");
|
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");
|
throw new IllegalArgumentException("no security deposit specified");
|
||||||
|
else
|
||||||
|
verifyStringIsValidDouble(options.valueOf(securityDepositPctOpt));
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -114,23 +120,18 @@ public class CreateOfferOptionParser extends AbstractMethodOptionParser implemen
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUsingMktPriceMargin() {
|
public boolean isUsingMktPriceMargin() {
|
||||||
return options.has(mktPriceMarginOpt);
|
return options.has(mktPriceMarginPctOpt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
public double getMktPriceMarginPct() {
|
||||||
public String getMktPriceMargin() {
|
return isUsingMktPriceMargin() ? Double.parseDouble(options.valueOf(mktPriceMarginPctOpt)) : 0.00d;
|
||||||
return isUsingMktPriceMargin() ? options.valueOf(mktPriceMarginOpt) : "0.00";
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getMktPriceMarginAsBigDecimal() {
|
|
||||||
return isUsingMktPriceMargin() ? new BigDecimal(options.valueOf(mktPriceMarginOpt)) : BigDecimal.ZERO;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFixedPrice() {
|
public String getFixedPrice() {
|
||||||
return options.has(fixedPriceOpt) ? options.valueOf(fixedPriceOpt) : "0.00";
|
return options.has(fixedPriceOpt) ? options.valueOf(fixedPriceOpt) : "0.00";
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSecurityDeposit() {
|
public double getSecurityDepositPct() {
|
||||||
return options.valueOf(securityDepositOpt);
|
return Double.valueOf(options.valueOf(securityDepositPctOpt));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ import static java.lang.String.format;
|
||||||
public class CreatePaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
public class CreatePaymentAcctOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
||||||
|
|
||||||
final OptionSpec<String> paymentAcctFormPathOpt = parser.accepts(OPT_PAYMENT_ACCOUNT_FORM,
|
final OptionSpec<String> paymentAcctFormPathOpt = parser.accepts(OPT_PAYMENT_ACCOUNT_FORM,
|
||||||
"path to json payment account form")
|
"path to json payment account form")
|
||||||
.withRequiredArg();
|
.withRequiredArg();
|
||||||
|
|
||||||
public CreatePaymentAcctOptionParser(String[] args) {
|
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)")
|
final OptionSpec<String> directionOpt = parser.accepts(OPT_DIRECTION, "offer direction (buy|sell)")
|
||||||
.withRequiredArg();
|
.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();
|
.withRequiredArg();
|
||||||
|
|
||||||
public GetOffersOptionParser(String[] args) {
|
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 {
|
public class GetPaymentAcctFormOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
||||||
|
|
||||||
final OptionSpec<String> paymentMethodIdOpt = parser.accepts(OPT_PAYMENT_METHOD_ID,
|
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();
|
.withRequiredArg();
|
||||||
|
|
||||||
public GetPaymentAcctFormOptionParser(String[] args) {
|
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_ACCOUNT_NAME = "account-name";
|
||||||
public final static String OPT_ADDRESS = "address";
|
public final static String OPT_ADDRESS = "address";
|
||||||
public final static String OPT_AMOUNT = "amount";
|
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_CURRENCY_CODE = "currency-code";
|
||||||
public final static String OPT_DIRECTION = "direction";
|
public final static String OPT_DIRECTION = "direction";
|
||||||
public final static String OPT_DISPUTE_AGENT_TYPE = "dispute-agent-type";
|
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_FEE_CURRENCY = "fee-currency";
|
||||||
public final static String OPT_FIXED_PRICE = "fixed-price";
|
public final static String OPT_FIXED_PRICE = "fixed-price";
|
||||||
public final static String OPT_HELP = "help";
|
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_MIN_AMOUNT = "min-amount";
|
||||||
public final static String OPT_OFFER_ID = "offer-id";
|
public final static String OPT_OFFER_ID = "offer-id";
|
||||||
public final static String OPT_PASSWORD = "password";
|
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_ACCOUNT_FORM = "payment-account-form";
|
||||||
public final static String OPT_PAYMENT_METHOD_ID = "payment-method-id";
|
public final static String OPT_PAYMENT_METHOD_ID = "payment-method-id";
|
||||||
public final static String OPT_PORT = "port";
|
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_TRADE_INSTANT = "trade-instant";
|
||||||
public final static String OPT_TIMEOUT = "timeout";
|
public final static String OPT_TIMEOUT = "timeout";
|
||||||
public final static String OPT_TRANSACTION_ID = "transaction-id";
|
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_TX_FEE_RATE = "tx-fee-rate";
|
||||||
public final static String OPT_WALLET_PASSWORD = "wallet-password";
|
public final static String OPT_WALLET_PASSWORD = "wallet-password";
|
||||||
public final static String OPT_NEW_WALLET_PASSWORD = "new-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 {
|
public class SetTxFeeRateOptionParser extends AbstractMethodOptionParser implements MethodOpts {
|
||||||
|
|
||||||
final OptionSpec<String> feeRateOpt = parser.accepts(OPT_TX_FEE_RATE,
|
final OptionSpec<String> feeRateOpt = parser.accepts(OPT_TX_FEE_RATE,
|
||||||
"tx fee rate preference (sats/byte)")
|
"tx fee rate preference (sats/byte)")
|
||||||
.withRequiredArg();
|
.withRequiredArg();
|
||||||
|
|
||||||
public SetTxFeeRateOptionParser(String[] args) {
|
public SetTxFeeRateOptionParser(String[] args) {
|
||||||
|
|
|
@ -21,30 +21,21 @@ package bisq.cli.opts;
|
||||||
import joptsimple.OptionSpec;
|
import joptsimple.OptionSpec;
|
||||||
|
|
||||||
import static bisq.cli.opts.OptLabel.OPT_FEE_CURRENCY;
|
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_ID;
|
||||||
import static bisq.cli.opts.OptLabel.OPT_PAYMENT_ACCOUNT;
|
|
||||||
|
|
||||||
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")
|
final OptionSpec<String> paymentAccountIdOpt = parser.accepts(OPT_PAYMENT_ACCOUNT_ID, "id of payment account used for trade")
|
||||||
.withRequiredArg();
|
|
||||||
|
|
||||||
final OptionSpec<String> paymentAccountIdOpt = parser.accepts(OPT_PAYMENT_ACCOUNT, "id of payment account used for trade")
|
|
||||||
.withRequiredArg();
|
.withRequiredArg();
|
||||||
|
|
||||||
public TakeOfferOptionParser(String[] args) {
|
public TakeOfferOptionParser(String[] args) {
|
||||||
super(args);
|
super(args, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TakeOfferOptionParser parse() {
|
public TakeOfferOptionParser parse() {
|
||||||
super.parse();
|
super.parse();
|
||||||
|
|
||||||
// Short circuit opt validation if user just wants help.
|
// Super class will short-circuit parsing if help option is present.
|
||||||
if (options.has(helpOpt))
|
|
||||||
return this;
|
|
||||||
|
|
||||||
if (!options.has(offerIdOpt) || options.valueOf(offerIdOpt).isEmpty())
|
|
||||||
throw new IllegalArgumentException("no offer id specified");
|
|
||||||
|
|
||||||
if (!options.has(paymentAccountIdOpt) || options.valueOf(paymentAccountIdOpt).isEmpty())
|
if (!options.has(paymentAccountIdOpt) || options.valueOf(paymentAccountIdOpt).isEmpty())
|
||||||
throw new IllegalArgumentException("no payment account id specified");
|
throw new IllegalArgumentException("no payment account id specified");
|
||||||
|
@ -52,10 +43,6 @@ public class TakeOfferOptionParser extends AbstractMethodOptionParser implements
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getOfferId() {
|
|
||||||
return options.valueOf(offerIdOpt);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPaymentAccountId() {
|
public String getPaymentAccountId() {
|
||||||
return options.valueOf(paymentAccountIdOpt);
|
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…
Add table
Add a link
Reference in a new issue