mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex.git
synced 2026-09-22 01:02:00 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7301df1880 | ||
|
|
e77dbb16e3 | ||
|
|
8f7d997b3e | ||
|
|
5a4cd8a125 | ||
|
|
fdaae3f52e | ||
|
|
afebdc5448 | ||
|
|
ad744e807f | ||
|
|
4fe89a1190 | ||
|
|
a904cbb473 | ||
|
|
84dfd56918 | ||
|
|
d62fc3b575 | ||
|
|
a7f8a520a2 | ||
|
|
87b9046af8 | ||
|
|
a6b4677571 | ||
|
|
bf71810974 | ||
|
|
e68514f542 | ||
|
|
5fc26dcc7c |
+45
-10
@@ -10,16 +10,32 @@ There are three major pieces of software behind the bch-dex concept. They work t
|
||||
|
||||

|
||||
|
||||
- _Client_ could be a web browser, or a command-line client like [psf-bch-wallet](https://github.com/Permissionless-Software-Foundation/psf-bch-wallet) or [psf-avax-wallet](https://github.com/Permissionless-Software-Foundation/psf-avax-wallet).
|
||||
- _Client_ could be a web browser like [wallet.fullstack.cash](https://bchn-wallet.fullstack.cash), or a command-line client like [psf-bch-wallet](https://github.com/Permissionless-Software-Foundation/psf-bch-wallet) or [psf-avax-wallet](https://github.com/Permissionless-Software-Foundation/psf-avax-wallet).
|
||||
- [bch-dex](https://github.com/Permissionless-Software-Foundation/bch-dex) is the back end REST API that maintains a local database of information that the client reads from.
|
||||
- [P2WDB](https://github.com/Permissionless-Software-Foundation/ipfs-p2wdb-service) is the pay-to-write global database with a REST API for interfacing with the other two pieces of software.
|
||||
|
||||
The arrows in the image represent the information flow between the three pieces of software:
|
||||
|
||||
- The _Client_ is essentially a 'dummy terminal' with a bidirectional interface to bch-dex. bch-dex does the heavy lifting, and the _Client_ is a 'thin' UI wrapper.
|
||||
- `bch-dex` imports data from the global P2WDB database into its local database, using a [webhook](https://en.wikipedia.org/wiki/Webhook) (dashed line). It can also custody funds, pay transaction fees, and create an _Offer_ by submitting the data to the P2WDB to generate an _Order_ (solid line).
|
||||
- `bch-dex` imports data from the global P2WDB database into its local database, using a [webhook](https://en.wikipedia.org/wiki/Webhook) (dashed line). It can also custody funds, pay transaction fees, and create an _Offer_ by creating an _Order_ and submitting the data to the P2WDB (solid line).
|
||||
|
||||
This architecture keeps the global database highly censorship resistant, while allowing local installations to maintain tight control over the user experience. The goal is to have many redundant copies of `bch-dex` on the network, and to empower individual traders to run their own, private copy.
|
||||
This architecture keeps the global database highly censorship resistant, while allowing local installations to maintain tight control over the user experience. The goal is to have many redundant copies of `bch-dex` on the network, and to empower individual traders to run their own, private copy, while maintaining a single source of truth via the P2WDB.
|
||||
|
||||
# Definitions
|
||||
|
||||
The workflow of a token trade has three parts:
|
||||
1. **Make** - An *Offer* to buy or sell tokens is generated by a user, known as the *Maker*.
|
||||
2. **Take** - A second user, known as a *Taker*, will *take* the *Offer* by issues a *Counter Offer*
|
||||
3. **Accept** - The original *Maker* checks the *Counter Offer* and *Accepts* it by signing and then broadcasting the transaction.
|
||||
|
||||
Trades done in this way are both *trustless* and *atomic*:
|
||||
- **Trustless** - This means that neither party needs to trust the other. The *Maker* gets to review the *Counter Offer* before broadcasting it. The *Maker* can not alter the *Counter Offer* after the *Taker* has signed it.
|
||||
- **Atomic** - The trade happens in a single transaction. There is no middle-state where the trade can get stuck. It either happens or doesn't, it's state is binary and atomic.
|
||||
|
||||
Specific *Entities* are defined in the [specification](./specification.md), but here is a brief summary:
|
||||
- **Order** represents the *Maker* side of the trade. This entity is internal to `bch-dex`. It is used to track tokens set aside for sale and managed by the wallet controlled by `bch-dex`.
|
||||
- **Offer** contains most of the same information as an **Order**, but is external to `bch-dex`. This is data submitted to the P2WDB and visible to all users on the network.
|
||||
- **Counter Offer** is generated by a *Taker*, in order to take the other side of the trade. It contains a partially-signed transaction, ready for review by the *Maker*.
|
||||
|
||||
# Back End
|
||||
|
||||
@@ -45,19 +61,38 @@ This section describes the protocols for the database interactions between the t
|
||||
|
||||
These are just a brief, high-level overview. Review the [Specifications](./specification.md) for more details.
|
||||
|
||||
## Writing to the Global Database
|
||||
## Reading from the Local Database
|
||||
|
||||
The *Client* reads data from the local database stored by `bch-dex`, and does not read the P2WDB global database directly. This gives `bch-dex` the opportunity to filter and modify the data locally, for a more controlled user experience.
|
||||
|
||||
## Making an Offer
|
||||
|
||||
Adding data to the global P2WDB is triggered by the _Client_ calling a REST API endpoint on `bch-dex`. The [p2wdb npm library](https://www.npmjs.com/package/p2wdb) can be leveraged for easy reading and writing to the P2WDB.
|
||||
|
||||
Writing data follows these steps:
|
||||
|
||||
- Tokens and BCH are held by a wallet which is under the controlled of `bch-dex`.
|
||||
- The _Client_ submits data to the POST `/offer` REST API endpoint to create a new Offer.
|
||||
- `bch-dex` will move the funds into a segregated UTXO, and will use that UTXO to create an Offer. The Offer data is written to the P2WDB. The Offer data is also saved to the local MongoDB.
|
||||
- After submitting the data to the P2WDB, `bch-dex` will receive a webhook call to its POST `/order` endpoint by the P2WDB. This event will trigger the import of the new data into the apps local Mongo database, and generate a new Order model.
|
||||
- This webhook event is mirrored by every instance of `bch-dex` on the network. Each P2WDB peer on the network will independently validate the new database entry and create a new Order model.
|
||||
- The _Client_ submits data to the POST `/order` REST API endpoint to create a new Order.
|
||||
- `bch-dex` will move the funds into a segregated UTXO, and will use that UTXO to create an Order. The Order data is written to the P2WDB. The Order data is also saved to the local MongoDB.
|
||||
- After submitting the data to the P2WDB, `bch-dex` will receive a webhook call to its POST `/offer` endpoint by the P2WDB. This event will trigger the import of the new data into the apps local Mongo database, and generate a new Offer model.
|
||||
- This webhook event is mirrored by every instance of `bch-dex` on the network. Each P2WDB peer on the network will independently validate the new database entry and create a new Offer model.
|
||||
|
||||
## Taking an Offer
|
||||
|
||||
## Reading from the Local Database
|
||||
Users can browse the Offers tracked by a local `bch-dex` by using a *Client*. When they find an Offer they want to to take, they'll use some UI element that will send data to the POST `/offer/take` REST API endpoint. These series of steps happen:
|
||||
|
||||
The *Client* reads data from the local database stored by `bch-dex`, and does not read the P2WDB global database directly. This gives `bch-dex` the opportunity to filter and modify the data locally, for a more controlled user experience.
|
||||
- The `bch-dex` checks to see if the wallet it controls has enough BCH to take the other side of the Offer. If it does, the funds for the offer are moved to a segregated UTXO.
|
||||
- The new UTXO is used to generate a *Counter Offer*, which contain a partially signed transaction saved as a hex string.
|
||||
- The *Counter Offer* is submitted to the P2WDB. This triggers a webhook event in every running instance of `bch-dex` on the network.
|
||||
- When the webhook is triggered, `bch-dex` will check to see if the *Counter Offer* matches an *Order* under its control. If a match is found, it will trigger an *Accept* event.
|
||||
|
||||
## Accepting a Counter Offer
|
||||
|
||||
This part of the process is automated and does not require input from the user.
|
||||
|
||||
- When a *Counter Offer* is received that matches an *Order* tracked by the local copy of `bch-dex`, it will trigger the *Acceptance* phase.
|
||||
- In the *Acceptance* phase, the transaction will be checked to see if it matches the conditions in the original *Order*. If all checks pass, `bch-dex` will sign the transaction and broadcast it, completing the trade.
|
||||
|
||||
## Maintenance
|
||||
|
||||
Occasional maintenance functions will be called by an interval timer. The primary purpose of these functions is to check the UTXOs in the Order, Offer, and Counter Offer entities. If any of these UTXOs are spent, the entity is deleted from the local Mongo database.
|
||||
|
||||
+85
-59
@@ -15,89 +15,115 @@ Entities make up the core business concepts. If these entities change, they fund
|
||||
|
||||
### Order
|
||||
|
||||
An order is created from data passed to the app by the P2WDB webhook.
|
||||
It is destroyed when the UTXO described in the Signal has been detected as spent.
|
||||
An Order Entity is nearly the same as an Offer. The Order is generated first, but is always internal to the bch-dex system. Most of the data in an Order is submitted to the P2WDB, which generates an Offer (external) Entity.
|
||||
|
||||
The Order tracks the [HD index address](https://github.com/bitcoinbook/bitcoinbook/blob/develop/ch05.asciidoc#hd-wallets-bip-32bip-44) used to hold tokens or BCH for sale. This is the part of the app concerned with the custody of the funds. It creates a segregated [UTXO](https://github.com/bitcoinbook/bitcoinbook/blob/develop/ch06.asciidoc#transaction-outputs-and-inputs) to hold the offered asset. The Order is automatically destroyed if the UTXO is spent.
|
||||
|
||||
Order entities have the following properties:
|
||||
|
||||
- _tokenId_ - The unique ID that identifies the class of token being offered for sale.
|
||||
- _buyOrSell_ - A string with a value `buy` or `sell` indicating which type of offer this is.
|
||||
- _rateInSats_ - The rate in terms of tokens-per-currency-unit.
|
||||
- For Bitcoin, the min currency is sats.
|
||||
- For AVAX, the min currency is nano-Avax.
|
||||
- for eCash, the min currency is bits.
|
||||
- _minSatsToExchange_ - The minimum order size accepted.
|
||||
- _signature_ - A message signed by the address which created the order.
|
||||
- _sigMsg_ - The clear-text message used to generate the signature.
|
||||
- _utxoTxid_ - The TXID of the UTXO used in the order.
|
||||
- _utxoVout_ - The vout of the UTXO used in the order.
|
||||
- _numTokens_ - The maximum number of tokens offered for sale.
|
||||
- _timestamp_ - The ISO time when the order was created.
|
||||
- _localTimestamp_ - The localized time when the order was created.
|
||||
- _p2wdbTxid_ - The TXID proof-of-burn used to add the order to the P2WDB.
|
||||
- _p2wdbHash_ - The hash used to identify the order entry in the P2WDB.
|
||||
- _lokadId_ - Not used. Provided for future functionality.
|
||||
- _messageType_ - Not used. Provided for future functionality.
|
||||
- _messageClass_ - Not used. Provided for future functionality.
|
||||
- Token Data:
|
||||
- _tokenId_ - The unique ID that identifies the class of token being offered for sale.
|
||||
- _utxoTxid_ - The TXID of the UTXO representing the token or BCH being offered for sale.
|
||||
- _utxoVout_ - The vout of the UTXO representing the token or BCH being offered for sale.
|
||||
|
||||
- Trade Data:
|
||||
- _buyOrSell_ - A string with a value `buy` or `sell` indicating which type of offer this is.
|
||||
- _numTokens_ - The maximum number of tokens offered for sale.
|
||||
- _rateInBaseUnit_ - The rate in terms of currency-unit-per-token. Ex: 1000 = 1000 sats per token
|
||||
- For Bitcoin, the min currency is sats.
|
||||
- For AVAX, the min currency is nano-Avax.
|
||||
- for eCash, the min currency is bits.
|
||||
- _minUnitsToExchange_ - The minimum order size accepted.
|
||||
|
||||
- Authentication Data:
|
||||
- _signature_ - A message signed by the address which created the order.
|
||||
- _sigMsg_ - The clear-text message used to generate the signature.
|
||||
- _offerBchAddr_ - The BCH address controlling the offer.
|
||||
- _offerPubKey_ - The public key used to generate the BCH address, used for encryption.
|
||||
|
||||
- Wallet Data:
|
||||
- _hdIndex_ - The HD index of the wallet used to generate the keypair to store the UTXO being offered for sale.
|
||||
|
||||
- SWaP Protocol properties:
|
||||
- _lokadId_ - Not used. Provided for future functionality.
|
||||
- _messageType_ - Not used. Provided for future functionality.
|
||||
- _messageClass_ - Not used. Provided for future functionality.
|
||||
|
||||
|
||||
### Offer
|
||||
|
||||
An Offer Entity is nearly the same as an Order. But while an Order is generated
|
||||
by a webhook from P2WDB, the Offer Entity is created internally. It is used
|
||||
to track an 'Order' generated and managed by this application.
|
||||
|
||||
The Offer tracks the [HD index address](https://github.com/bitcoinbook/bitcoinbook/blob/develop/ch05.asciidoc#hd-wallets-bip-32bip-44) used to hold tokens or BCH for sale. This is the part of the app concerned with the custody of the funds. It creates a segregated [UTXO](https://github.com/bitcoinbook/bitcoinbook/blob/develop/ch06.asciidoc#transaction-outputs-and-inputs) to hold the offered asset. The Offer is automatically destroyed if the UTXO is spent.
|
||||
An offer is created from data passed to the app by the P2WDB webhook.
|
||||
It is destroyed when the UTXO described in the Signal has been detected as spent.
|
||||
|
||||
Offer entities have the following properties:
|
||||
|
||||
- _offerIpfsId_ - The IPFS ID of the instance of `ipfs-swap-service` that is managing the offer.
|
||||
- _offerBchAddr_ - The BCH address controlling the offer.
|
||||
- _offerPubKey_ - The public key used to generate the BCH address, used for encryption.
|
||||
- _tokenId_ - The unique ID that identifies the class of token being offered for sale.
|
||||
- _buyOrSell_ - A string with a value `buy` or `sell` indicating which type of offer this is.
|
||||
- _rateInSats_ - The rate in terms of tokens-per-currency-unit.
|
||||
- For Bitcoin, the min currency is sats.
|
||||
- For AVAX, the min currency is nano-Avax.
|
||||
- for eCash, the min currency is bits.
|
||||
- _minSatsToExchange_ - The minimum order size accepted.
|
||||
- _signature_ - A message signed by the address which created the order.
|
||||
- _sigMsg_ - The clear-text message used to generate the signature.
|
||||
- _utxoTxid_ - The TXID of the UTXO used in the order.
|
||||
- _utxoVout_ - The vout of the UTXO used in the order.
|
||||
- _numTokens_ - The maximum number of tokens offered for sale.
|
||||
- _timestamp_ - The ISO time when the order was created.
|
||||
- _localTimestamp_ - The localized time when the order was created.
|
||||
- _p2wdbTxid_ - The TXID proof-of-burn used to add the order to the P2WDB.
|
||||
- _p2wdbHash_ - The hash used to identify the order entry in the P2WDB.
|
||||
- _lokadId_ - Not used. Provided for future functionality.
|
||||
- _messageType_ - Not used. Provided for future functionality.
|
||||
- _messageClass_ - Not used. Provided for future functionality.
|
||||
- Token Data:
|
||||
- _tokenId_ - The unique ID that identifies the class of token being offered for sale.
|
||||
- _utxoTxid_ - The TXID of the UTXO representing the token or BCH being offered for sale.
|
||||
- _utxoVout_ - The vout of the UTXO representing the token or BCH being offered for sale.
|
||||
|
||||
- Trade Data:
|
||||
- _buyOrSell_ - A string with a value `buy` or `sell` indicating which type of offer this is.
|
||||
- _numTokens_ - The maximum number of tokens offered for sale.
|
||||
- _rateInBaseUnit_ - The rate in terms of currency-unit-per-token. Ex: 1000 = 1000 sats per token
|
||||
- For Bitcoin, the min currency is sats.
|
||||
- For AVAX, the min currency is nano-Avax.
|
||||
- for eCash, the min currency is bits.
|
||||
- _minUnitsToExchange_ - The minimum order size accepted.
|
||||
- _p2wdbTxid_ - The TXID proof-of-burn used to add the order to the P2WDB.
|
||||
- _p2wdbHash_ - The CID used to identify the order entry in the P2WDB.
|
||||
|
||||
- Authentication Data:
|
||||
- _signature_ - A message signed by the address which created the order.
|
||||
- _sigMsg_ - The clear-text message used to generate the signature.
|
||||
- _offerBchAddr_ - The BCH address controlling the offer.
|
||||
- _offerPubKey_ - The public key used to generate the BCH address, used for encryption.
|
||||
|
||||
- Utility Data:
|
||||
- _timestamp_ - The ISO time when the order was created.
|
||||
- _localTimestamp_ - The localized time when the order was created.
|
||||
|
||||
- SWaP Protocol properties:
|
||||
- _lokadId_ - Not used. Provided for future functionality.
|
||||
- _messageType_ - Not used. Provided for future functionality.
|
||||
- _messageClass_ - Not used. Provided for future functionality.
|
||||
|
||||
|
||||
### Counter Offer
|
||||
|
||||
A Counter Offer is the other side of an Offer. It contains a partially signed transaction, created by the Taker. The Maker will review the Counter Offer before accepting and finalizing the trade.
|
||||
|
||||
Details TBD.
|
||||
|
||||
## Use Cases
|
||||
|
||||
Use cases are verbs or actions that is done _to_ an Entity or _between_ Entities.
|
||||
|
||||
### Order
|
||||
|
||||
- **`createOrder()`** - This method is triggered by a webhook from the P2WDB. It will take the data provided by the P2WDB and create a new Order entity in the local database.
|
||||
|
||||
### Offer
|
||||
|
||||
- **`createOffer()`** - This method is triggered by a webhook from the P2WDB. It will take the data provided by the P2WDB and create a new Order entity in the local database.
|
||||
|
||||
### Order
|
||||
|
||||
- **`ensureFunds()`** - Ensure that the wallet has enough BCH and tokens to complete the requested trade.
|
||||
- **`moveTokens()`** - Move the tokens indicated in the offer to a temporary holding address. This will generate the UTXO used in the webhook message. This function moves the funds and returns the UTXO information.
|
||||
- **`createOffer()`** - A macro command that leverages `ensureFunds()` and `moveTokens()`, to create a new Offer and submit it to the P2WDB.
|
||||
- **`moveTokens()`** - Move the tokens indicated in the order to a temporary holding address. This will generate the UTXO used in the webhook message. This function moves the funds and returns the UTXO information.
|
||||
- **`createOrder()`** - A macro command that leverages `ensureFunds()` and `moveTokens()`, to create a new Order and submit it to the P2WDB.
|
||||
|
||||
### Counter Offer
|
||||
|
||||
TBD
|
||||
|
||||
## Controllers
|
||||
|
||||
Controllers are inputs to the system. When a controller is activated, it causes the system to react in some way.
|
||||
|
||||
### Orders
|
||||
|
||||
- **POST /order** - This POST REST API endpoint will be triggered by a webhook generated from P2WDB. This will notify the `bch-dex` that a new entry has been added to the P2WDB that matches the `appId` of `swap-<chain>`, where `<chain>` has a value of `avax`, `bch`, or `ecash`. It's a new entry that should be evaluated for inclusion in the `ipfs-swap-service` local database.
|
||||
|
||||
### Offers
|
||||
|
||||
- **POST /offer** - This POST REST API endpoint can be triggered by the Client or a simple curl call. It passes in the data needed for `ipfs-swap-service` to generate and track a new Offer, then submit the data to the P2WDB to generate an Order that is tracked by all other instances of `ipfs-swap-service`.
|
||||
- **POST /offer** - This POST REST API endpoint will be triggered by a webhook generated from P2WDB. This will notify the `bch-dex` that a new entry has been added to the P2WDB that matches the `appId` of `swap-<chain>`, where `<chain>` has a value of `avax`, `bch`, or `ecash`. It's a new entry that should be evaluated for inclusion in the `ipfs-swap-service` local database.
|
||||
|
||||
### Orders
|
||||
|
||||
- **POST /order** - This POST REST API endpoint can be triggered by the Client or a simple curl call. It passes in the data needed for `bch-dex` to generate and track a new Order, then submit the data to the P2WDB to generate an Offer that is tracked by all other instances of `bch-dex`.
|
||||
|
||||
## Adapters
|
||||
|
||||
|
||||
Generated
+191
-35
@@ -9,13 +9,13 @@
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@psf/bch-js": "5.3.2",
|
||||
"@psf/bch-js": "5.4.0",
|
||||
"axios": "0.21.1",
|
||||
"bch-message-lib": "2.1.4",
|
||||
"bcryptjs": "2.4.3",
|
||||
"glob": "7.1.6",
|
||||
"ipfs": "0.60.0",
|
||||
"ipfs-coord": "7.1.4",
|
||||
"ipfs-coord": "7.1.6",
|
||||
"ipfs-http-client": "55.0.0",
|
||||
"jsonrpc-lite": "2.2.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
@@ -33,7 +33,7 @@
|
||||
"koa2-ratelimit": "0.9.1",
|
||||
"libp2p": "^0.36.2",
|
||||
"line-reader": "0.4.0",
|
||||
"minimal-slp-wallet": "4.4.2",
|
||||
"minimal-slp-wallet": "4.4.3",
|
||||
"mongoose": "5.13.13",
|
||||
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
|
||||
"nodemailer": "6.4.17",
|
||||
@@ -45,6 +45,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"apidoc": "0.26.0",
|
||||
"bch-token-sweep": "1.5.16",
|
||||
"chai": "4.3.0",
|
||||
"coveralls": "2.11.4",
|
||||
"eslint": "7.19.0",
|
||||
@@ -74,8 +75,6 @@
|
||||
"@psf/bitcoincash-ops": "2.0.0",
|
||||
"@psf/bitcoincashjs-lib": "4.0.2",
|
||||
"@psf/coininfo": "4.0.0",
|
||||
"@uppy/core": "1.10.4",
|
||||
"@uppy/tus": "1.5.12",
|
||||
"axios": "^0.21.4",
|
||||
"bc-bip68": "1.0.5",
|
||||
"bchaddrjs-slp": "0.2.5",
|
||||
@@ -97,7 +96,7 @@
|
||||
"wif": "2.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"apidoc": "0.17.7",
|
||||
"apidoc": "^0.50.5",
|
||||
"assert": "2.0.0",
|
||||
"chai": "4.1.2",
|
||||
"coveralls": "3.0.2",
|
||||
@@ -109,10 +108,10 @@
|
||||
"eslint-plugin-standard": "4.0.0",
|
||||
"husky": "^4.3.8",
|
||||
"lodash.clonedeep": "4.5.0",
|
||||
"mocha": "9.1.3",
|
||||
"mocha": "^9.2.1",
|
||||
"node-mocks-http": "1.7.0",
|
||||
"nyc": "15.1.0",
|
||||
"semantic-release": "18.0.0",
|
||||
"semantic-release": "^19.0.2",
|
||||
"sinon": "9.2.2",
|
||||
"standard": "^16.0.4"
|
||||
}
|
||||
@@ -122,7 +121,7 @@
|
||||
"extraneous": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@psf/bch-js": "5.3.2",
|
||||
"@psf/bch-js": "../bch-js",
|
||||
"apidoc": "0.25.0",
|
||||
"bch-consumer": "1.2.0",
|
||||
"bch-donation": "1.1.1",
|
||||
@@ -140,9 +139,9 @@
|
||||
"eslint-plugin-standard": "4.0.1",
|
||||
"husky": "4.3.8",
|
||||
"lodash.clonedeep": "4.5.0",
|
||||
"mocha": "8.2.0",
|
||||
"mocha": "^9.2.1",
|
||||
"nyc": "15.1.0",
|
||||
"semantic-release": "17.4.4",
|
||||
"semantic-release": "^19.0.2",
|
||||
"sinon": "9.2.0",
|
||||
"standard": "16.0.4",
|
||||
"tinyify": "3.0.0"
|
||||
@@ -1604,9 +1603,9 @@
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@psf/bch-js": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-5.3.2.tgz",
|
||||
"integrity": "sha512-LzYekwuee+T8AEZ1MKBe5KnZKXzpRZ57SpXRL2S8qsHefokTIN+XnDPX7dsOFkN4VGGgyjYyZ33iN6xhOSHWJw==",
|
||||
"version": "5.4.0",
|
||||
"resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-5.4.0.tgz",
|
||||
"integrity": "sha512-HyBCpobC9moxSbQl4k/tb/GW9p8L12Fu+8yR1x8eflxBe7c2bwBlG218LmuoNcvjJMAdDxk0VEHvtO64vpltsw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@chris.troutner/bip32-utils": "1.0.5",
|
||||
@@ -1614,8 +1613,6 @@
|
||||
"@psf/bitcoincash-ops": "2.0.0",
|
||||
"@psf/bitcoincashjs-lib": "4.0.2",
|
||||
"@psf/coininfo": "4.0.0",
|
||||
"@uppy/core": "1.10.4",
|
||||
"@uppy/tus": "1.5.12",
|
||||
"axios": "^0.21.4",
|
||||
"bc-bip68": "1.0.5",
|
||||
"bchaddrjs-slp": "0.2.5",
|
||||
@@ -2972,6 +2969,17 @@
|
||||
"minimal-slp-wallet": "4.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/bch-token-sweep": {
|
||||
"version": "1.5.16",
|
||||
"resolved": "http://94.130.170.209:4873/bch-token-sweep/-/bch-token-sweep-1.5.16.tgz",
|
||||
"integrity": "sha512-9Fsb0qXUZ62OUDdH/tIF8QZDs12vWduoGDtU9txpSEF6GEyK0NOHfu8YYx5henoHz7SGS/fMUWDCibmedSpsmA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bch-donation": "1.1.1",
|
||||
"bignumber.js": "9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bchaddrjs-slp": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "http://94.130.170.209:4873/bchaddrjs-slp/-/bchaddrjs-slp-0.2.5.tgz",
|
||||
@@ -8411,9 +8419,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ipfs-coord": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/ipfs-coord/-/ipfs-coord-7.1.4.tgz",
|
||||
"integrity": "sha512-YsCdrQ30JW03uOLNrpZKqNmWr5zmxF9AyKeXSQA/OTPBoOOqM2nomEYhTPVJjGxee9Qa2dtxCQzw3JwsLDCe4Q==",
|
||||
"version": "7.1.6",
|
||||
"resolved": "http://94.130.170.209:4873/ipfs-coord/-/ipfs-coord-7.1.6.tgz",
|
||||
"integrity": "sha512-wtdJ/PgRMv3r2pOZ1WIc7blTkk3YftMbXTNX9hN9Lu+IblUhNoWUyjq7E4i7J3zBZQVQs7J3TyckRdLCekmc9A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "0.21.4",
|
||||
"bch-encrypt-lib": "2.0.0",
|
||||
@@ -13012,12 +13021,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimal-slp-wallet": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "http://94.130.170.209:4873/minimal-slp-wallet/-/minimal-slp-wallet-4.4.2.tgz",
|
||||
"integrity": "sha512-6JNRnH8mQQb/gmfwdd0KgJeHFJycK60EDmeEdjghvm1I2J0z2HVI3gZzrVqjBC/tBDYKtuDuvRtix7q2LqKqhw==",
|
||||
"version": "4.4.3",
|
||||
"resolved": "http://94.130.170.209:4873/minimal-slp-wallet/-/minimal-slp-wallet-4.4.3.tgz",
|
||||
"integrity": "sha512-wpELTh4WYems96SxyRW/KdglJ6T3ieR7VVDCLcqq7bwZ+tJXFznOxDRggS5sREI9tTxiAHIyVSg+nbXZZk8buQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@psf/bch-js": "5.3.2",
|
||||
"@psf/bch-js": "5.4.0",
|
||||
"apidoc": "0.25.0",
|
||||
"bch-consumer": "1.2.0",
|
||||
"bch-donation": "1.1.1",
|
||||
@@ -17923,6 +17932,49 @@
|
||||
"minimal-slp-wallet": "4.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/p2wdb/node_modules/@psf/bch-js": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-5.3.2.tgz",
|
||||
"integrity": "sha512-LzYekwuee+T8AEZ1MKBe5KnZKXzpRZ57SpXRL2S8qsHefokTIN+XnDPX7dsOFkN4VGGgyjYyZ33iN6xhOSHWJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@chris.troutner/bip32-utils": "1.0.5",
|
||||
"@psf/bip21": "2.0.1",
|
||||
"@psf/bitcoincash-ops": "2.0.0",
|
||||
"@psf/bitcoincashjs-lib": "4.0.2",
|
||||
"@psf/coininfo": "4.0.0",
|
||||
"@uppy/core": "1.10.4",
|
||||
"@uppy/tus": "1.5.12",
|
||||
"axios": "^0.21.4",
|
||||
"bc-bip68": "1.0.5",
|
||||
"bchaddrjs-slp": "0.2.5",
|
||||
"bigi": "1.4.2",
|
||||
"bignumber.js": "9.0.0",
|
||||
"bip-schnorr": "0.3.0",
|
||||
"bip38": "2.0.2",
|
||||
"bip39": "3.0.2",
|
||||
"bip66": "1.1.5",
|
||||
"bitcoinjs-message": "2.0.0",
|
||||
"bs58": "4.0.1",
|
||||
"ecashaddrjs": "1.0.7",
|
||||
"ini": "1.3.8",
|
||||
"randombytes": "2.0.6",
|
||||
"safe-buffer": "5.1.2",
|
||||
"satoshi-bitcoin": "1.0.4",
|
||||
"slp-mdm": "0.0.6",
|
||||
"slp-parser": "0.0.4",
|
||||
"wif": "2.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/p2wdb/node_modules/@psf/bch-js/node_modules/axios": {
|
||||
"version": "0.21.4",
|
||||
"resolved": "http://94.130.170.209:4873/axios/-/axios-0.21.4.tgz",
|
||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/p2wdb/node_modules/apidoc": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "http://94.130.170.209:4873/apidoc/-/apidoc-0.25.0.tgz",
|
||||
@@ -17954,6 +18006,34 @@
|
||||
"follow-redirects": "^1.14.4"
|
||||
}
|
||||
},
|
||||
"node_modules/p2wdb/node_modules/minimal-slp-wallet": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "http://94.130.170.209:4873/minimal-slp-wallet/-/minimal-slp-wallet-4.4.2.tgz",
|
||||
"integrity": "sha512-6JNRnH8mQQb/gmfwdd0KgJeHFJycK60EDmeEdjghvm1I2J0z2HVI3gZzrVqjBC/tBDYKtuDuvRtix7q2LqKqhw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@psf/bch-js": "5.3.2",
|
||||
"apidoc": "0.25.0",
|
||||
"bch-consumer": "1.2.0",
|
||||
"bch-donation": "1.1.1",
|
||||
"crypto-js": "4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/p2wdb/node_modules/randombytes": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "http://94.130.170.209:4873/randombytes/-/randombytes-2.0.6.tgz",
|
||||
"integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/p2wdb/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "http://94.130.170.209:4873/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/package-hash": {
|
||||
"version": "4.0.0",
|
||||
"dev": true,
|
||||
@@ -23321,17 +23401,15 @@
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"@psf/bch-js": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-5.3.2.tgz",
|
||||
"integrity": "sha512-LzYekwuee+T8AEZ1MKBe5KnZKXzpRZ57SpXRL2S8qsHefokTIN+XnDPX7dsOFkN4VGGgyjYyZ33iN6xhOSHWJw==",
|
||||
"version": "5.4.0",
|
||||
"resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-5.4.0.tgz",
|
||||
"integrity": "sha512-HyBCpobC9moxSbQl4k/tb/GW9p8L12Fu+8yR1x8eflxBe7c2bwBlG218LmuoNcvjJMAdDxk0VEHvtO64vpltsw==",
|
||||
"requires": {
|
||||
"@chris.troutner/bip32-utils": "1.0.5",
|
||||
"@psf/bip21": "2.0.1",
|
||||
"@psf/bitcoincash-ops": "2.0.0",
|
||||
"@psf/bitcoincashjs-lib": "4.0.2",
|
||||
"@psf/coininfo": "4.0.0",
|
||||
"@uppy/core": "1.10.4",
|
||||
"@uppy/tus": "1.5.12",
|
||||
"axios": "^0.21.4",
|
||||
"bc-bip68": "1.0.5",
|
||||
"bchaddrjs-slp": "0.2.5",
|
||||
@@ -24320,6 +24398,16 @@
|
||||
"version": "2.1.4",
|
||||
"requires": {}
|
||||
},
|
||||
"bch-token-sweep": {
|
||||
"version": "1.5.16",
|
||||
"resolved": "http://94.130.170.209:4873/bch-token-sweep/-/bch-token-sweep-1.5.16.tgz",
|
||||
"integrity": "sha512-9Fsb0qXUZ62OUDdH/tIF8QZDs12vWduoGDtU9txpSEF6GEyK0NOHfu8YYx5henoHz7SGS/fMUWDCibmedSpsmA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bch-donation": "1.1.1",
|
||||
"bignumber.js": "9.0.0"
|
||||
}
|
||||
},
|
||||
"bchaddrjs-slp": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "http://94.130.170.209:4873/bchaddrjs-slp/-/bchaddrjs-slp-0.2.5.tgz",
|
||||
@@ -28077,9 +28165,9 @@
|
||||
}
|
||||
},
|
||||
"ipfs-coord": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/ipfs-coord/-/ipfs-coord-7.1.4.tgz",
|
||||
"integrity": "sha512-YsCdrQ30JW03uOLNrpZKqNmWr5zmxF9AyKeXSQA/OTPBoOOqM2nomEYhTPVJjGxee9Qa2dtxCQzw3JwsLDCe4Q==",
|
||||
"version": "7.1.6",
|
||||
"resolved": "http://94.130.170.209:4873/ipfs-coord/-/ipfs-coord-7.1.6.tgz",
|
||||
"integrity": "sha512-wtdJ/PgRMv3r2pOZ1WIc7blTkk3YftMbXTNX9hN9Lu+IblUhNoWUyjq7E4i7J3zBZQVQs7J3TyckRdLCekmc9A==",
|
||||
"requires": {
|
||||
"axios": "0.21.4",
|
||||
"bch-encrypt-lib": "2.0.0",
|
||||
@@ -31595,11 +31683,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"minimal-slp-wallet": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "http://94.130.170.209:4873/minimal-slp-wallet/-/minimal-slp-wallet-4.4.2.tgz",
|
||||
"integrity": "sha512-6JNRnH8mQQb/gmfwdd0KgJeHFJycK60EDmeEdjghvm1I2J0z2HVI3gZzrVqjBC/tBDYKtuDuvRtix7q2LqKqhw==",
|
||||
"version": "4.4.3",
|
||||
"resolved": "http://94.130.170.209:4873/minimal-slp-wallet/-/minimal-slp-wallet-4.4.3.tgz",
|
||||
"integrity": "sha512-wpELTh4WYems96SxyRW/KdglJ6T3ieR7VVDCLcqq7bwZ+tJXFznOxDRggS5sREI9tTxiAHIyVSg+nbXZZk8buQ==",
|
||||
"requires": {
|
||||
"@psf/bch-js": "5.3.2",
|
||||
"@psf/bch-js": "5.4.0",
|
||||
"apidoc": "0.25.0",
|
||||
"bch-consumer": "1.2.0",
|
||||
"bch-donation": "1.1.1",
|
||||
@@ -34958,6 +35046,49 @@
|
||||
"minimal-slp-wallet": "4.4.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@psf/bch-js": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-5.3.2.tgz",
|
||||
"integrity": "sha512-LzYekwuee+T8AEZ1MKBe5KnZKXzpRZ57SpXRL2S8qsHefokTIN+XnDPX7dsOFkN4VGGgyjYyZ33iN6xhOSHWJw==",
|
||||
"requires": {
|
||||
"@chris.troutner/bip32-utils": "1.0.5",
|
||||
"@psf/bip21": "2.0.1",
|
||||
"@psf/bitcoincash-ops": "2.0.0",
|
||||
"@psf/bitcoincashjs-lib": "4.0.2",
|
||||
"@psf/coininfo": "4.0.0",
|
||||
"@uppy/core": "1.10.4",
|
||||
"@uppy/tus": "1.5.12",
|
||||
"axios": "^0.21.4",
|
||||
"bc-bip68": "1.0.5",
|
||||
"bchaddrjs-slp": "0.2.5",
|
||||
"bigi": "1.4.2",
|
||||
"bignumber.js": "9.0.0",
|
||||
"bip-schnorr": "0.3.0",
|
||||
"bip38": "2.0.2",
|
||||
"bip39": "3.0.2",
|
||||
"bip66": "1.1.5",
|
||||
"bitcoinjs-message": "2.0.0",
|
||||
"bs58": "4.0.1",
|
||||
"ecashaddrjs": "1.0.7",
|
||||
"ini": "1.3.8",
|
||||
"randombytes": "2.0.6",
|
||||
"safe-buffer": "5.1.2",
|
||||
"satoshi-bitcoin": "1.0.4",
|
||||
"slp-mdm": "0.0.6",
|
||||
"slp-parser": "0.0.4",
|
||||
"wif": "2.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": {
|
||||
"version": "0.21.4",
|
||||
"resolved": "http://94.130.170.209:4873/axios/-/axios-0.21.4.tgz",
|
||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"apidoc": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "http://94.130.170.209:4873/apidoc/-/apidoc-0.25.0.tgz",
|
||||
@@ -34980,6 +35111,31 @@
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.4"
|
||||
}
|
||||
},
|
||||
"minimal-slp-wallet": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "http://94.130.170.209:4873/minimal-slp-wallet/-/minimal-slp-wallet-4.4.2.tgz",
|
||||
"integrity": "sha512-6JNRnH8mQQb/gmfwdd0KgJeHFJycK60EDmeEdjghvm1I2J0z2HVI3gZzrVqjBC/tBDYKtuDuvRtix7q2LqKqhw==",
|
||||
"requires": {
|
||||
"@psf/bch-js": "5.3.2",
|
||||
"apidoc": "0.25.0",
|
||||
"bch-consumer": "1.2.0",
|
||||
"bch-donation": "1.1.1",
|
||||
"crypto-js": "4.0.0"
|
||||
}
|
||||
},
|
||||
"randombytes": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "http://94.130.170.209:4873/randombytes/-/randombytes-2.0.6.tgz",
|
||||
"integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==",
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "http://94.130.170.209:4873/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+4
-3
@@ -27,13 +27,13 @@
|
||||
},
|
||||
"repository": "Permissionless-Software-Foundation/bch-dex",
|
||||
"dependencies": {
|
||||
"@psf/bch-js": "5.3.2",
|
||||
"@psf/bch-js": "5.4.0",
|
||||
"axios": "0.21.1",
|
||||
"bch-message-lib": "2.1.4",
|
||||
"bcryptjs": "2.4.3",
|
||||
"glob": "7.1.6",
|
||||
"ipfs": "0.60.0",
|
||||
"ipfs-coord": "7.1.4",
|
||||
"ipfs-coord": "7.1.6",
|
||||
"ipfs-http-client": "55.0.0",
|
||||
"jsonrpc-lite": "2.2.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
@@ -51,7 +51,7 @@
|
||||
"koa2-ratelimit": "0.9.1",
|
||||
"libp2p": "^0.36.2",
|
||||
"line-reader": "0.4.0",
|
||||
"minimal-slp-wallet": "4.4.2",
|
||||
"minimal-slp-wallet": "4.4.3",
|
||||
"mongoose": "5.13.13",
|
||||
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
|
||||
"nodemailer": "6.4.17",
|
||||
@@ -63,6 +63,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"apidoc": "0.26.0",
|
||||
"bch-token-sweep": "1.5.16",
|
||||
"chai": "4.3.0",
|
||||
"coveralls": "2.11.4",
|
||||
"eslint": "7.19.0",
|
||||
|
||||
@@ -1,25 +1,33 @@
|
||||
const mongoose = require('mongoose')
|
||||
|
||||
const Offer = new mongoose.Schema({
|
||||
|
||||
// Token data
|
||||
tokenId: { type: String },
|
||||
utxoTxid: { type: String },
|
||||
utxoVout: { type: Number },
|
||||
|
||||
// Trade data
|
||||
buyOrSell: { type: String },
|
||||
numTokens: { type: Number },
|
||||
rateInBaseUnit: { type: String },
|
||||
minUnitsToExchange: { type: String },
|
||||
p2wdbTxid: { type: String },
|
||||
p2wdbHash: { type: String },
|
||||
|
||||
// Authentication data
|
||||
signature: { type: String },
|
||||
sigMsg: { type: String },
|
||||
|
||||
// Utility data
|
||||
timestamp: { type: String },
|
||||
localTimestamp: { type: String },
|
||||
|
||||
// SWaP Protocol Properties
|
||||
lokadId: { type: String },
|
||||
messageType: { type: Number },
|
||||
messageClass: { type: Number },
|
||||
tokenId: { type: String },
|
||||
buyOrSell: { type: String },
|
||||
rateInSats: { type: String },
|
||||
minSatsToExchange: { type: String },
|
||||
signature: { type: String },
|
||||
sigMsg: { type: String },
|
||||
utxoTxid: { type: String },
|
||||
utxoVout: { type: Number },
|
||||
numTokens: { type: Number },
|
||||
hdIndex: { type: Number }, // HD index address holding the UTXO for this offer.
|
||||
messageClass: { type: Number }
|
||||
|
||||
//
|
||||
offerIpfsId: { type: String },
|
||||
offerBchAddr: { type: String },
|
||||
offerPubKey: { type: String }
|
||||
})
|
||||
|
||||
module.exports = mongoose.model('offer', Offer)
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
const mongoose = require('mongoose')
|
||||
|
||||
const Order = new mongoose.Schema({
|
||||
// Token data
|
||||
tokenId: { type: String },
|
||||
utxoTxid: { type: String },
|
||||
utxoVout: { type: Number },
|
||||
|
||||
// Trade data
|
||||
buyOrSell: { type: String },
|
||||
numTokens: { type: Number },
|
||||
rateInBaseUnit: { type: String },
|
||||
minUnitsToExchange: { type: String },
|
||||
p2wdbTxid: { type: String },
|
||||
p2wdbHash: { type: String },
|
||||
|
||||
// Authentication data
|
||||
signature: { type: String },
|
||||
sigMsg: { type: String },
|
||||
offerBchAddr: { type: String },
|
||||
offerPubKey: { type: String },
|
||||
|
||||
// Wallet Data
|
||||
hdIndex: { type: Number }, // HD index address holding the UTXO for this offer.
|
||||
|
||||
// SWaP Protocol Properties
|
||||
lokadId: { type: String },
|
||||
messageType: { type: Number },
|
||||
messageClass: { type: Number },
|
||||
tokenId: { type: String },
|
||||
buyOrSell: { type: String },
|
||||
rateInSats: { type: String },
|
||||
minSatsToExchange: { type: String },
|
||||
signature: { type: String },
|
||||
sigMsg: { type: String },
|
||||
utxoTxid: { type: String },
|
||||
utxoVout: { type: Number },
|
||||
numTokens: { type: Number },
|
||||
timestamp: { type: String },
|
||||
localTimestamp: { type: String },
|
||||
p2wdbTxid: { type: String },
|
||||
p2wdbHash: { type: String }
|
||||
messageClass: { type: Number }
|
||||
|
||||
})
|
||||
|
||||
module.exports = mongoose.model('order', Order)
|
||||
|
||||
+125
-57
@@ -178,63 +178,131 @@ class WalletAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
// Burn enough PSF to generate a valide proof-of-burn for writing to the P2WDB.
|
||||
// async burnPsf () {
|
||||
// try {
|
||||
// // TODO: Throw error if this.bchWallet has not been instantiated.
|
||||
//
|
||||
// // console.log('walletData: ', walletData)
|
||||
// // console.log(
|
||||
// // `walletData.utxos.utxoStore.slpUtxos: ${JSON.stringify(
|
||||
// // walletData.utxos.utxoStore.slpUtxos,
|
||||
// // null,
|
||||
// // 2,
|
||||
// // )}`,
|
||||
// // )
|
||||
//
|
||||
// // Get token UTXOs held by the wallet.
|
||||
// const tokenUtxos = this.bchWallet.utxos.utxoStore.slpUtxos.type1.tokens
|
||||
// // console.log(`tokenUtxos: ${JSON.stringify(tokenUtxos, null, 2)}`)
|
||||
//
|
||||
// // Find a token UTXO that contains PSF with a quantity higher than needed
|
||||
// // to generate a proof-of-burn.
|
||||
// let tokenUtxo = {}
|
||||
// for (let i = 0; i < tokenUtxos.length; i++) {
|
||||
// const thisUtxo = tokenUtxos[i]
|
||||
//
|
||||
// // If token ID matches.
|
||||
// if (thisUtxo.tokenId === P2WDB_TOKEN_ID) {
|
||||
// if (parseFloat(thisUtxo.qtyStr) >= PROOF_OF_BURN_QTY) {
|
||||
// tokenUtxo = thisUtxo
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (tokenUtxo.tokenId !== P2WDB_TOKEN_ID) {
|
||||
// throw new Error(
|
||||
// `Token UTXO of with ID of ${P2WDB_TOKEN_ID} and quantity greater than ${PROOF_OF_BURN_QTY} could not be found in wallet.`
|
||||
// )
|
||||
// }
|
||||
// // console.log(`tokenUtxo: ${JSON.stringify(tokenUtxo, null, 2)}`)
|
||||
//
|
||||
// const result = await this.bchWallet.burnTokens(
|
||||
// PROOF_OF_BURN_QTY,
|
||||
// P2WDB_TOKEN_ID
|
||||
// )
|
||||
// // console.log('walletData.burnTokens() result: ', result)
|
||||
//
|
||||
// return result
|
||||
//
|
||||
// // return {
|
||||
// // success: true,
|
||||
// // txid: 'fakeTxid',
|
||||
// // }
|
||||
// } catch (err) {
|
||||
// console.error('Error in burnPsf(): ', err)
|
||||
// throw err
|
||||
// }
|
||||
// }
|
||||
// Generate a partial transcation to *take* a 'sell' order.
|
||||
async generatePartialTx (orderInfo) {
|
||||
try {
|
||||
console.log(`orderInfo: ${JSON.stringify(orderInfo, null, 2)}`)
|
||||
|
||||
const bchjs = this.bchWallet.bchjs
|
||||
|
||||
// instance of transaction builder
|
||||
const transactionBuilder = new bchjs.TransactionBuilder()
|
||||
|
||||
// Get a payment UTXO.
|
||||
// TODO: Create a segregated UTXO.
|
||||
const bchUtxos = this.bchWallet.utxos.utxoStore.bchUtxos
|
||||
const paymentUtxo = await bchjs.Utxo.findBiggestUtxo(bchUtxos)
|
||||
console.log('paymentUtxo: ', paymentUtxo)
|
||||
|
||||
// Get token info on the offered UTXO
|
||||
const txData = await this.bchWallet.getTxData([orderInfo.utxoTxid])
|
||||
console.log(`txData: ${JSON.stringify(txData, null, 2)}`)
|
||||
|
||||
// Construct the UTXO being offered for sale.
|
||||
const offeredUtxo = {
|
||||
txid: orderInfo.utxoTxid,
|
||||
vout: orderInfo.utxoVout,
|
||||
tokenId: orderInfo.tokenId,
|
||||
decimals: txData[0].tokenDecimals,
|
||||
tokenQty: orderInfo.numTokens.toString()
|
||||
}
|
||||
console.log(`offeredUtxo: ${JSON.stringify(offeredUtxo, null, 2)}`)
|
||||
|
||||
// Build First part of the collaborative Tx a.k.a. Alice
|
||||
// Generate the OP_RETURN code.
|
||||
const slpSendObj = bchjs.SLP.TokenType1.generateSendOpReturn(
|
||||
[offeredUtxo],
|
||||
orderInfo.numTokens.toString()
|
||||
)
|
||||
const slpData = slpSendObj.script
|
||||
console.log(`slpOutputs: ${slpSendObj.outputs}`)
|
||||
|
||||
// Currently this app only supports a single SLP token UTXO for exact
|
||||
// token quantities (no token change). e.g. 1 UTXO representing the
|
||||
// exact number of 'numTokens'
|
||||
if (slpSendObj.outputs > 1) {
|
||||
console.log('WARNING: choose one UTXO with all tokens to exchange')
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate sats needed to pay the offer.
|
||||
const satsNeeded = orderInfo.numTokens * parseInt(orderInfo.rateInSats)
|
||||
|
||||
// Calculate miner fees.
|
||||
// Get byte count (minimum 2 inputs, 3 outputs)
|
||||
const opReturnBufLength = slpData.byteLength + 32 // add padding
|
||||
const byteCount =
|
||||
bchjs.BitcoinCash.getByteCount({ P2PKH: 2 }, { P2PKH: 4 }) +
|
||||
opReturnBufLength
|
||||
const totalSatsNeeded = byteCount + satsNeeded
|
||||
// console.log(`satoshis needed: ${satsNeeded}`)
|
||||
|
||||
// One last check to ensure the app wallet has enough BCH to complete
|
||||
// the trade.
|
||||
if (totalSatsNeeded > paymentUtxo.value) {
|
||||
console.log(`Selected payment UTXO is not big enough. Sats needed: ${totalSatsNeeded}, UTXO value: ${paymentUtxo.value}`)
|
||||
}
|
||||
|
||||
// add UTXO for sell(STILL CANNOT SPEND - not signed yet)
|
||||
transactionBuilder.addInput(orderInfo.utxoTxid, orderInfo.utxoVout)
|
||||
|
||||
// add payment UTXO
|
||||
transactionBuilder.addInput(paymentUtxo.tx_hash, paymentUtxo.tx_pos)
|
||||
|
||||
const originalAmount = paymentUtxo.value
|
||||
const dust = 546
|
||||
const remainder = originalAmount - satsNeeded - dust // exchange fee + token UTXO dust
|
||||
|
||||
// Add the SLP OP_RETURN data as the first output.
|
||||
transactionBuilder.addOutput(slpData, 0)
|
||||
|
||||
const buyerAddr = this.bchWallet.walletInfo.legacyAddress
|
||||
// console.log(`buyAddr: ${JSON.stringify(buyAddr, null, 2)}`)
|
||||
|
||||
// Send dust transaction representing tokens being sent.
|
||||
transactionBuilder.addOutput(
|
||||
buyerAddr,
|
||||
dust
|
||||
)
|
||||
|
||||
// Get seller address
|
||||
// TODO: Seller should explicitly define the address to send to in the
|
||||
// orderInfo object. Right now it retrieves the address from the UTXO
|
||||
// for sale, which is not ideal.
|
||||
const sellerAddr = txData[0].vout[1].scriptPubKey.addresses[0]
|
||||
|
||||
// Send payment to the offer side
|
||||
transactionBuilder.addOutput(sellerAddr, satsNeeded)
|
||||
|
||||
// Send the BCH change back to the buyer
|
||||
if (remainder > 550) {
|
||||
transactionBuilder.addOutput(buyerAddr, remainder)
|
||||
}
|
||||
|
||||
const buyerECPair = bchjs.ECPair.fromWIF(this.bchWallet.walletInfo.privateKey)
|
||||
|
||||
// Sign the buyers input UTXO for spending.
|
||||
transactionBuilder.sign(
|
||||
1,
|
||||
buyerECPair,
|
||||
null,
|
||||
transactionBuilder.hashTypes.SIGHASH_ALL,
|
||||
originalAmount
|
||||
)
|
||||
|
||||
const tx = transactionBuilder.transaction.buildIncomplete()
|
||||
|
||||
const hex = tx.toHex()
|
||||
console.log('hex: ', hex)
|
||||
|
||||
return hex
|
||||
} catch (err) {
|
||||
console.error('Error in wallet.js/generatePartialTx(): ', err)
|
||||
throw err
|
||||
}
|
||||
|
||||
// return true
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WalletAdapter
|
||||
|
||||
@@ -61,6 +61,27 @@ class OrderRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
// Currently only supports 'sell' orders, and will only buy the 'numTokens'
|
||||
// listed in the order.
|
||||
async takeOrder (ctx) {
|
||||
try {
|
||||
console.log('body: ', ctx.request.body)
|
||||
|
||||
const orderCid = ctx.request.body.orderCid
|
||||
|
||||
// Find the Order.
|
||||
// const orderEntity = await _this.useCases.order.findOrder(orderId)
|
||||
|
||||
// 'Take' the Order.
|
||||
const hash = await _this.useCases.order.takeOrder(orderCid)
|
||||
|
||||
ctx.body = { hash }
|
||||
} catch (err) {
|
||||
console.log('Error in takeOrder REST API handler.')
|
||||
_this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
console.log('err', err.message)
|
||||
|
||||
@@ -51,6 +51,7 @@ class OrderRouter {
|
||||
// Define the routes and attach the controller.
|
||||
this.router.post('/', _this.orderRESTController.createOrder)
|
||||
this.router.get('/list', _this.orderRESTController.listOrders)
|
||||
this.router.post('/take', _this.orderRESTController.takeOrder)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(_this.router.routes())
|
||||
|
||||
+10
-12
@@ -4,6 +4,10 @@
|
||||
It's destroyed when the UTXO described in the Signal has been detected as spent.
|
||||
*/
|
||||
class OrderEntity {
|
||||
constructor () {
|
||||
this.orderStatus = ['posted', 'taken', 'completed']
|
||||
}
|
||||
|
||||
validate (orderData = {}) {
|
||||
// Throw an error if input object does not have a data property
|
||||
if (!orderData.data) {
|
||||
@@ -12,17 +16,7 @@ class OrderEntity {
|
||||
)
|
||||
}
|
||||
|
||||
const {
|
||||
messageType,
|
||||
messageClass,
|
||||
tokenId,
|
||||
buyOrSell,
|
||||
rateInSats,
|
||||
minSatsToExchange,
|
||||
numTokens,
|
||||
utxoTxid,
|
||||
utxoVout
|
||||
} = orderData.data
|
||||
const { messageType, messageClass, tokenId, buyOrSell, rateInSats, minSatsToExchange, numTokens, utxoTxid, utxoVout, orderStatus } = orderData.data
|
||||
|
||||
// Input Validation
|
||||
if (!messageType || typeof messageType !== 'number') {
|
||||
@@ -52,6 +46,9 @@ class OrderEntity {
|
||||
if (typeof utxoVout !== 'number') {
|
||||
throw new Error("Property 'utxoVout' must be an integer number.")
|
||||
}
|
||||
if (orderStatus && !this.orderStatus.includes(orderStatus)) {
|
||||
throw new Error("Property 'orderStatus' must be a valid string")
|
||||
}
|
||||
|
||||
const validatedOrderData = {
|
||||
messageType,
|
||||
@@ -66,7 +63,8 @@ class OrderEntity {
|
||||
timestamp: orderData.timestamp,
|
||||
localTimestamp: orderData.localTimeStamp,
|
||||
txid: orderData.txid,
|
||||
p2wdbHash: orderData.hash
|
||||
p2wdbHash: orderData.hash,
|
||||
orderStatus: orderStatus || this.orderStatus[0]
|
||||
}
|
||||
|
||||
return validatedOrderData
|
||||
|
||||
@@ -90,7 +90,7 @@ class OfferLib {
|
||||
|
||||
const utxoInfo = {
|
||||
txid,
|
||||
vout: 0,
|
||||
vout: 1,
|
||||
hdIndex: keyPair.hdIndex
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
local Offers are no different than Orders generated by other peers.
|
||||
*/
|
||||
|
||||
// Local libraries
|
||||
const OrderEntity = require('../../entities/order')
|
||||
const config = require('../../../config')
|
||||
|
||||
class OrderUseCases {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -20,6 +22,9 @@ class OrderUseCases {
|
||||
)
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.config = config
|
||||
|
||||
this.orderEntity = new OrderEntity()
|
||||
this.OrderModel = this.adapters.localdb.Order
|
||||
}
|
||||
@@ -43,6 +48,9 @@ class OrderUseCases {
|
||||
console.log('utxoStatus: ', utxoStatus)
|
||||
if (utxoStatus === null) return false
|
||||
|
||||
// A new order gets a status of 'posted'
|
||||
orderObj.data.orderStatus = 'posted'
|
||||
|
||||
const orderEntity = this.orderEntity.validate(orderObj)
|
||||
console.log('orderEntity: ', orderEntity)
|
||||
|
||||
@@ -65,6 +73,133 @@ class OrderUseCases {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Generate phase 2 of 3 - take the other side of an Order.
|
||||
// Based on this example:
|
||||
// https://github.com/Permissionless-Software-Foundation/bch-js-examples/blob/master/bch/applications/collaborate/sell-slp/e2e-exchange/step2-purchase-tx.js
|
||||
async takeOrder (orderCid) {
|
||||
try {
|
||||
console.log('orderCid: ', orderCid)
|
||||
|
||||
// Get the Order information
|
||||
const orderInfo = await this.findOrderByHash(orderCid)
|
||||
console.log(`orderInfo: ${JSON.stringify(orderInfo, null, 2)}`)
|
||||
|
||||
// Ensure the order is in a 'posted' state and not already 'taken'
|
||||
if (orderInfo.orderStatus && orderInfo.orderStatus !== 'posted') {
|
||||
throw new Error('order already taken')
|
||||
}
|
||||
|
||||
// Verify that UTXO for sale is unspent. Abort if it's been spent.
|
||||
const txid = orderInfo.utxoTxid
|
||||
const vout = orderInfo.utxoVout
|
||||
const utxoStatus = await this.adapters.bchjs.Blockchain.getTxOut(
|
||||
txid,
|
||||
vout
|
||||
)
|
||||
console.log('utxoStatus: ', utxoStatus)
|
||||
if (utxoStatus === null) {
|
||||
console.log(`utxo txid: ${txid}, vout: ${vout}`)
|
||||
throw new Error('UTXO does not exist. Aborting.')
|
||||
}
|
||||
|
||||
// Ensure the app has enough funds to complete the trade.
|
||||
await this.ensureFunds(orderInfo)
|
||||
|
||||
// Get UTXOs.
|
||||
const utxos = this.adapters.wallet.bchWallet.utxos.utxoStore
|
||||
console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||
|
||||
// TODO: Move funds to create a segrated UTXO for taking the order
|
||||
|
||||
// Create a partially signed transaction.
|
||||
// https://github.com/Permissionless-Software-Foundation/bch-js-examples/blob/master/bch/applications/collaborate/sell-slp/e2e-exchange/step2-purchase-tx.js#L59
|
||||
const partialTxHex = await this.adapters.wallet.generatePartialTx(orderInfo)
|
||||
// return partialTxHex
|
||||
|
||||
// Create valid Order object
|
||||
const takenOrderInfo = Object.assign({}, orderInfo)
|
||||
takenOrderInfo.patialTxHex = partialTxHex
|
||||
delete takenOrderInfo.p2wdbHash
|
||||
delete takenOrderInfo._id
|
||||
takenOrderInfo.offerHash = orderInfo.p2wdbHash
|
||||
|
||||
// Write order info to the P2WDB
|
||||
// TODO: This will trigger the webhook. Find some way of triggering the
|
||||
// webhook on new orders, but not on counteroffers
|
||||
const p2wdbObj = {
|
||||
wif: this.adapters.wallet.bchWallet.walletInfo.privateKey,
|
||||
data: takenOrderInfo,
|
||||
appId: this.config.p2wdbAppId
|
||||
}
|
||||
const hash = await this.adapters.p2wdb.write(p2wdbObj)
|
||||
|
||||
// Return the P2WDB CID
|
||||
return hash
|
||||
} catch (err) {
|
||||
console.error('Error in use-cases/order/takeOrder(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the wallet has enough BCH and tokens to complete the requested
|
||||
// trade. Will return true if it does. Will throw an error if it doesn't.
|
||||
async ensureFunds (orderEntity) {
|
||||
try {
|
||||
// console.log('this.adapters.wallet: ', this.adapters.wallet.bchWallet)
|
||||
// console.log(`walletInfo: ${JSON.stringify(this.adapters.wallet.bchWallet.walletInfo, null, 2)}`)
|
||||
|
||||
await this.adapters.wallet.bchWallet.walletInfoPromise
|
||||
|
||||
// Ensure the app wallet has enough funds to write to the P2WDB.
|
||||
const wif = this.adapters.wallet.bchWallet.walletInfo.privateKey
|
||||
const canWriteToP2WDB = await this.adapters.p2wdb.checkForSufficientFunds(wif)
|
||||
if (!canWriteToP2WDB) throw new Error('App wallet does not have funds for writing to the P2WDB.')
|
||||
|
||||
if (orderEntity.buyOrSell.includes('sell')) {
|
||||
// Sell Offer
|
||||
|
||||
// Ensure the app wallet controlls enough BCH to pay for the tokens.
|
||||
const satsNeeded = orderEntity.numTokens * parseInt(orderEntity.rateInSats)
|
||||
const balance = await this.adapters.wallet.bchWallet.getBalance()
|
||||
console.log(`wallet balance: ${balance}, sats needed: ${satsNeeded}`)
|
||||
const SATS_MARGIN = 5000
|
||||
if (satsNeeded + SATS_MARGIN > balance) { throw new Error('App wallet does not control enough BCH to purchase the tokens.') }
|
||||
|
||||
//
|
||||
} else {
|
||||
// Buy Offer
|
||||
throw new Error('Buy orders are not supported yet.')
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in ensureFunds()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async findOrderByHash (p2wdbHash) {
|
||||
try {
|
||||
if (typeof p2wdbHash !== 'string' || !p2wdbHash) {
|
||||
throw new Error('p2wdbHash must be a string')
|
||||
}
|
||||
|
||||
const order = await this.OrderModel.findOne({ p2wdbHash })
|
||||
|
||||
if (!order) {
|
||||
throw new Error('order not found')
|
||||
}
|
||||
|
||||
const orderObject = order.toObject()
|
||||
// return this.orderEntity.validateFromModel(orderObject)
|
||||
|
||||
return orderObject
|
||||
} catch (err) {
|
||||
console.error('Error in findOrder(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OrderUseCases
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Part 2 of 3: Take an order
|
||||
*/
|
||||
|
||||
const axios = require('axios')
|
||||
|
||||
const LOCALHOST = 'http://localhost:5700'
|
||||
|
||||
async function start () {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/order/take`,
|
||||
data: {
|
||||
orderCid: 'zdpuAkp98gTuivaNzGP31jTQi3ADXrFA6uANrceQcrQkTXy2j'
|
||||
}
|
||||
}
|
||||
|
||||
const result = await axios(options)
|
||||
console.log('result.data: ', result.data)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
start()
|
||||
@@ -90,7 +90,7 @@ describe('#offer-use-case', () => {
|
||||
assert.property(result, 'vout')
|
||||
|
||||
assert.equal(result.txid, 'fakeTxid')
|
||||
assert.equal(result.vout, 0)
|
||||
assert.equal(result.vout, 1)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
This script will travers the HD wallet and sweep funds and tokens back
|
||||
into the root address (index 0). That root address needs to have funds
|
||||
to pay for the transactions.
|
||||
|
||||
The root address will make a final transaction to consolidate all tokens.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
const BchTokenSweep = require('bch-token-sweep/index')
|
||||
|
||||
// Local libraries
|
||||
const WalletAdapter = require('../../src/adapters/wallet')
|
||||
|
||||
async function sweepFunds () {
|
||||
try {
|
||||
// Open the wallet files.
|
||||
const wallet = new WalletAdapter()
|
||||
const walletInfo = await wallet.openWallet()
|
||||
console.log('walletInfo: ', walletInfo)
|
||||
|
||||
const rootAddr = walletInfo.cashAddress
|
||||
const rootWif = walletInfo.privateKey
|
||||
console.log(`Sweeping all funds into root address ${rootAddr}...`)
|
||||
|
||||
// Generate an HD tree
|
||||
const bchjs = new BCHJS()
|
||||
const rootSeed = await bchjs.Mnemonic.toSeed(walletInfo.mnemonic)
|
||||
const masterHDNode = bchjs.HDNode.fromSeed(rootSeed)
|
||||
|
||||
let emptyAddrCnt = 0
|
||||
let hdIndex = 1
|
||||
|
||||
do {
|
||||
// Generate a keypair from the HD wallet.
|
||||
const childNode = masterHDNode.derivePath(`m/44'/245'/0'/0/${hdIndex}`)
|
||||
// const cashAddress = bchjs.HDNode.toCashAddress(childNode)
|
||||
const wifToSweep = bchjs.HDNode.toWIF(childNode)
|
||||
|
||||
try {
|
||||
// Sweep tokens from address
|
||||
const sweeper = new BchTokenSweep(
|
||||
wifToSweep,
|
||||
rootWif,
|
||||
bchjs,
|
||||
550,
|
||||
rootAddr
|
||||
)
|
||||
await sweeper.populateObjectFromNetwork()
|
||||
|
||||
const hex = await sweeper.sweepTo(rootAddr)
|
||||
// console.log(`hex: ${hex}`)
|
||||
|
||||
const txid = await sweeper.blockchain.broadcast(hex)
|
||||
|
||||
// console.log('Transaction ID', txid)
|
||||
console.log(`Swept HD index ${hdIndex}. TXID: ${txid}`)
|
||||
|
||||
emptyAddrCnt = 0
|
||||
|
||||
// Wait between loop iterations.
|
||||
await bchjs.Util.sleep(3000)
|
||||
} catch (err) {
|
||||
console.log(`error message: ${err.message}`)
|
||||
emptyAddrCnt++
|
||||
}
|
||||
|
||||
hdIndex++
|
||||
} while (emptyAddrCnt < 5)
|
||||
|
||||
console.log('5 empty addresses detected. Exiting.')
|
||||
} catch (err) {
|
||||
console.error('Error in sweepFunds(): ', err)
|
||||
}
|
||||
}
|
||||
sweepFunds()
|
||||
Reference in New Issue
Block a user